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 = 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; } _ => {} } } }