refactor(tui): split remaining app.rs into per-concern submodules

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-07-09 13:53:36 +08:00
co-authored by Sisyphus
parent 58ac4a65c7
commit 9854ef13b6
7 changed files with 1161 additions and 1125 deletions
+162
View File
@@ -0,0 +1,162 @@
use log_viewer_core::io::file_reader::AppendStatus;
use log_viewer_core::io::progressive_reader::{ReaderState, compute_line_visual_height};
use log_viewer_core::watcher::file_watcher::FileEvent;
use super::viewport_cache::gutter_width_for;
use super::{App, AppLoadingState};
impl App {
pub fn poll_file_watcher(&mut self) {
let events: Vec<FileEvent> = match &mut self.file_watcher {
Some(w) => std::iter::from_fn(|| w.try_recv()).collect(),
None => return,
};
for event in events {
match event {
FileEvent::Appended { new_size: _ } => {
self.handle_file_appended();
}
FileEvent::Truncated { new_size: _ } => {
self.handle_file_truncated();
}
FileEvent::Rotated { new_inode: _ } => {
// Don't auto-switch; old content preserved.
// User can reload manually if desired.
}
FileEvent::Removed => {
self.loading_state = AppLoadingState::Error("File has been deleted".into());
}
FileEvent::WatcherError { message: _ } => {}
}
}
}
pub(super) fn handle_file_appended(&mut self) {
let rebased = self.rebase_offset_for_invalidate();
match &mut self.loading_state {
AppLoadingState::Ready { reader } => {
let old_reader_line_count = reader.line_count();
let status = reader.update_for_append();
let width = {
let total = if self.content_width > 0 {
self.content_width as usize
} else {
80
};
total.saturating_sub(gutter_width_for(reader.line_count(), false))
};
match status {
Ok(AppendStatus::Appended(_new_lines)) => {
let _ = reader.save_cache();
let (old_line_count, can_extend) = {
match &reader.state {
ReaderState::Ready {
visual_height_index: Some(idx),
..
} => (idx.line_count(), idx.is_valid_for(self.json_format, width)),
_ => (0, false),
}
};
let new_line_count = reader.line_count();
if can_extend && old_line_count == old_reader_line_count {
if let ReaderState::Ready {
visual_height_index: Some(index),
reader: fr,
} = &mut reader.state
{
if old_line_count > 0 {
let last_old_line_text =
fr.get_line(old_line_count - 1).unwrap_or("");
let new_h = compute_line_visual_height(
last_old_line_text,
width,
self.json_format,
);
index.replace_last_line_height(new_h);
}
let mut new_heights = Vec::with_capacity(
new_line_count.saturating_sub(old_line_count),
);
for i in old_line_count..new_line_count {
let line_text = fr.get_line(i).unwrap_or("");
new_heights.push(compute_line_visual_height(
line_text,
width,
self.json_format,
));
}
index.extend_from_heights(&new_heights);
}
} else {
let (new_offset, new_sub) = rebased;
self.v_offset = new_offset;
self.v_sub_offset = new_sub;
self.cursor_sub_offset = 0;
reader.invalidate_visual_height_index();
reader.start_visual_height_rebuild(width, self.json_format);
}
self.viewport_cache.invalidate();
}
Ok(AppendStatus::Reloaded) => {
let _ = reader.save_cache();
let (new_offset, _new_sub) = rebased;
reader.invalidate_visual_height_index();
reader.start_visual_height_rebuild(width, self.json_format);
self.cursor_line =
self.cursor_line.min(self.total_lines().saturating_sub(1));
self.v_offset = new_offset;
self.v_sub_offset = 0;
self.cursor_sub_offset = 0;
self.viewport_cache.invalidate();
self.clamp_v_offset();
}
Ok(AppendStatus::Unchanged) | Err(_) => {}
}
}
AppLoadingState::Loading { .. } => {
self.reload_after_loading = true;
}
_ => {}
}
}
pub(super) fn reload_ready_reader(&mut self) {
let (new_offset, _new_sub) = self.rebase_offset_for_invalidate();
if let AppLoadingState::Ready { reader } = &mut self.loading_state {
let _ = reader.reload();
let width = {
let total = if self.content_width > 0 {
self.content_width as usize
} else {
80
};
total.saturating_sub(gutter_width_for(reader.line_count(), false))
};
let _ = reader.save_cache();
reader.invalidate_visual_height_index();
reader.start_visual_height_rebuild(width, self.json_format);
self.cursor_line = self.cursor_line.min(self.total_lines().saturating_sub(1));
self.v_offset = new_offset;
self.v_sub_offset = 0;
self.clamp_cursor_sub_offset_no_vhi();
self.viewport_cache.invalidate();
self.clamp_v_offset();
}
}
pub(super) fn handle_file_truncated(&mut self) {
match &mut self.loading_state {
AppLoadingState::Ready { .. } => {
self.reload_ready_reader();
}
AppLoadingState::Loading { .. } => {
self.reload_after_loading = true;
}
_ => {}
}
}
}