use super::editor::{TextEditor, TextProcessingResult}; use eframe::egui; impl TextEditor { /// Process text content and find the longest line (only used for initial scan) pub fn process_text_for_rendering(&mut self, content: &str, ui: &egui::Ui) { let lines: Vec<&str> = content.lines().collect(); let line_count = lines.len().max(1); if lines.is_empty() { self.update_processing_result(TextProcessingResult { line_count: 1, longest_line_index: 0, longest_line_length: 0, longest_line_pixel_width: 0.0, }); return; } // Find the longest line by character count first (fast) let mut longest_line_index = 0; let mut longest_line_length = 0; for (index, line) in lines.iter().enumerate() { let char_count = line.chars().count(); if char_count > longest_line_length { longest_line_length = char_count; longest_line_index = index; } } // Calculate pixel width for the longest line let font_id = self.get_font_id(); let longest_line_pixel_width = if longest_line_length > 0 { let longest_line_text = lines[longest_line_index]; ui.fonts(|fonts| { fonts.layout( longest_line_text.to_string(), font_id, egui::Color32::WHITE, f32::INFINITY, ).size().x }) } else { 0.0 }; let result = TextProcessingResult { line_count, longest_line_index, longest_line_length, longest_line_pixel_width, }; self.update_processing_result(result); } /// Efficiently detect and process line changes without full content iteration pub fn process_incremental_change(&mut self, old_content: &str, new_content: &str, old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui) { // Calculate cursor line change incrementally let line_change = self.calculate_cursor_line_change(old_content, new_content, old_cursor_pos, new_cursor_pos); // Update current cursor line self.current_cursor_line = (self.current_cursor_line as isize + line_change) as usize; // Detect the type of change and handle appropriately if old_content.len() == new_content.len() { // Same length - likely a character replacement self.handle_character_replacement(old_content, new_content, old_cursor_pos, new_cursor_pos, ui); } else if new_content.len() > old_content.len() { // Content added self.handle_content_addition(old_content, new_content, old_cursor_pos, new_cursor_pos, ui); } else { // Content removed self.handle_content_removal(old_content, new_content, old_cursor_pos, new_cursor_pos, ui); } self.previous_cursor_line = self.current_cursor_line; } /// Calculate the change in cursor line without full iteration fn calculate_cursor_line_change(&self, old_content: &str, new_content: &str, old_cursor_pos: usize, new_cursor_pos: usize) -> isize { // Count newlines up to the cursor position in both contents let old_newlines = old_content[..old_cursor_pos.min(old_content.len())] .bytes() .filter(|&b| b == b'\n') .count(); let new_newlines = new_content[..new_cursor_pos.min(new_content.len())] .bytes() .filter(|&b| b == b'\n') .count(); new_newlines as isize - old_newlines as isize } /// Handle character replacement (same length change) fn handle_character_replacement(&mut self, old_content: &str, new_content: &str, old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui) { // Extract the current line from new content let current_line = self.extract_current_line(new_content, new_cursor_pos); let current_line_length = current_line.chars().count(); self.update_line_if_longer(self.current_cursor_line, ¤t_line, current_line_length, ui); } /// Handle content addition fn handle_content_addition(&mut self, old_content: &str, new_content: &str, old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui) { // Find the common prefix and suffix to identify the added text let min_len = old_content.len().min(new_content.len()); let mut common_prefix = 0; let mut common_suffix = 0; // Find common prefix for i in 0..min_len { if old_content.as_bytes()[i] == new_content.as_bytes()[i] { common_prefix += 1; } else { break; } } // Find common suffix for i in 0..min_len - common_prefix { let old_idx = old_content.len() - 1 - i; let new_idx = new_content.len() - 1 - i; if old_content.as_bytes()[old_idx] == new_content.as_bytes()[new_idx] { common_suffix += 1; } else { break; } } // Extract the added text let added_start = common_prefix; let added_end = new_content.len() - common_suffix; let added_text = &new_content[added_start..added_end]; let newlines_added = added_text.bytes().filter(|&b| b == b'\n').count(); if newlines_added > 0 { // Lines were added, update line count let mut current_result = self.get_text_processing_result(); current_result.line_count += newlines_added; self.update_processing_result(current_result); } // Check if the current line is now longer let current_line = self.extract_current_line(new_content, new_cursor_pos); let current_line_length = current_line.chars().count(); self.update_line_if_longer(self.current_cursor_line, ¤t_line, current_line_length, ui); } /// Handle content removal fn handle_content_removal(&mut self, old_content: &str, new_content: &str, old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui) { // Find the common prefix and suffix to identify the removed text let min_len = old_content.len().min(new_content.len()); let mut common_prefix = 0; let mut common_suffix = 0; // Find common prefix for i in 0..min_len { if old_content.as_bytes()[i] == new_content.as_bytes()[i] { common_prefix += 1; } else { break; } } // Find common suffix for i in 0..min_len - common_prefix { let old_idx = old_content.len() - 1 - i; let new_idx = new_content.len() - 1 - i; if old_content.as_bytes()[old_idx] == new_content.as_bytes()[new_idx] { common_suffix += 1; } else { break; } } // Extract the removed text let removed_start = common_prefix; let removed_end = old_content.len() - common_suffix; let removed_text = &old_content[removed_start..removed_end]; let newlines_removed = removed_text.bytes().filter(|&b| b == b'\n').count(); if newlines_removed > 0 { // Lines were removed, update line count let mut current_result = self.get_text_processing_result(); current_result.line_count = current_result.line_count.saturating_sub(newlines_removed); // If we removed the longest line, we need to rescan (but only if necessary) if self.current_cursor_line <= current_result.longest_line_index { // The longest line might have been affected, but let's be conservative // and only rescan if we're sure it was the longest line if self.current_cursor_line == current_result.longest_line_index { self.process_text_for_rendering(new_content, ui); return; } } self.update_processing_result(current_result); } // Check if the current line changed let current_line = self.extract_current_line(new_content, new_cursor_pos); let current_line_length = current_line.chars().count(); // If this was the longest line and it got shorter, we might need to rescan let current_result = self.get_text_processing_result(); if self.current_cursor_line == current_result.longest_line_index && current_line_length < current_result.longest_line_length { self.process_text_for_rendering(new_content, ui); } else { self.update_line_if_longer(self.current_cursor_line, ¤t_line, current_line_length, ui); } } /// Extract the current line efficiently without full content scan fn extract_current_line(&self, content: &str, cursor_pos: usize) -> String { let bytes = content.as_bytes(); // Find line start (search backwards from cursor) let mut line_start = cursor_pos; while line_start > 0 && bytes[line_start - 1] != b'\n' { line_start -= 1; } // Find line end (search forwards from cursor) let mut line_end = cursor_pos; while line_end < bytes.len() && bytes[line_end] != b'\n' { line_end += 1; } content[line_start..line_end].to_string() } /// Update longest line info if the current line is longer fn update_line_if_longer(&mut self, line_index: usize, line_content: &str, line_length: usize, ui: &egui::Ui) { let current_result = self.get_text_processing_result(); if line_length > current_result.longest_line_length { let font_id = self.get_font_id(); let pixel_width = ui.fonts(|fonts| { fonts.layout( line_content.to_string(), font_id, egui::Color32::WHITE, f32::INFINITY, ).size().x }); let result = TextProcessingResult { line_count: current_result.line_count, longest_line_index: line_index, longest_line_length: line_length, longest_line_pixel_width: pixel_width, }; self.update_processing_result(result); } } /// Get the current text processing result pub fn get_text_processing_result(&self) -> TextProcessingResult { self.text_processing_result .lock() .map(|result| result.clone()) .unwrap_or_default() } /// Update the processing result atomically fn update_processing_result(&self, result: TextProcessingResult) { if let Ok(mut processing_result) = self.text_processing_result.lock() { *processing_result = result; } } }