use super::editor::{TextEditor, TextProcessingResult}; use eframe::egui; impl TextEditor { pub fn process_text_for_rendering(&mut self, content: &str, ui: &egui::Ui) { let line_count = content.bytes().filter(|&b| b == b'\n').count() + 1; let lines: Vec<&str> = content.lines().collect(); if content.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; } let mut longest_line_index = 0; let mut longest_line_length = 0; if lines.is_empty() { self.update_processing_result(TextProcessingResult { line_count, longest_line_index: 0, longest_line_length: 0, longest_line_pixel_width: 0.0, }); return; } 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; } } 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); } pub fn process_incremental_change( &mut self, old_content: &str, new_content: &str, old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui, ) { let line_change = self.calculate_cursor_line_change( old_content, new_content, old_cursor_pos, new_cursor_pos, ); self.current_cursor_line = (self.current_cursor_line as isize + line_change) as usize; if old_content.len() == new_content.len() { self.handle_character_replacement( old_content, new_content, old_cursor_pos, new_cursor_pos, ui, ); } else if new_content.len() > old_content.len() { self.handle_content_addition( old_content, new_content, old_cursor_pos, new_cursor_pos, ui, ); } else { self.handle_content_removal( old_content, new_content, old_cursor_pos, new_cursor_pos, ui, ); } self.previous_cursor_line = self.current_cursor_line; } fn calculate_cursor_line_change( &self, old_content: &str, new_content: &str, old_cursor_pos: usize, new_cursor_pos: usize, ) -> isize { 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 } fn handle_character_replacement( &mut self, _old_content: &str, new_content: &str, _old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui, ) { 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, ); } fn handle_content_addition( &mut self, old_content: &str, new_content: &str, _old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui, ) { let min_len = old_content.len().min(new_content.len()); let mut common_prefix = 0; let mut common_suffix = 0; for i in 0..min_len { if old_content.as_bytes()[i] == new_content.as_bytes()[i] { common_prefix += 1; } else { break; } } 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; } } 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 { let mut current_result = self.get_text_processing_result(); current_result.line_count += newlines_added; let addition_start_line = old_content[..added_start] .bytes() .filter(|&b| b == b'\n') .count(); let addition_end_line = old_content[..added_end.min(old_content.len())] .bytes() .filter(|&b| b == b'\n') .count(); if current_result.longest_line_index >= addition_start_line && current_result.longest_line_index <= addition_end_line { self.process_text_for_rendering(new_content, ui); } else { if addition_end_line < current_result.longest_line_index { current_result.longest_line_index += newlines_added; } self.update_processing_result(current_result); } } else { 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, ); } } fn handle_content_removal( &mut self, old_content: &str, new_content: &str, _old_cursor_pos: usize, new_cursor_pos: usize, ui: &egui::Ui, ) { let min_len = old_content.len().min(new_content.len()); let mut common_prefix = 0; let mut common_suffix = 0; for i in 0..min_len { if old_content.as_bytes()[i] == new_content.as_bytes()[i] { common_prefix += 1; } else { break; } } 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; } } 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 { let mut current_result = self.get_text_processing_result(); current_result.line_count = current_result.line_count.saturating_sub(newlines_removed); let removal_start_line = old_content[..removed_start] .bytes() .filter(|&b| b == b'\n') .count(); let removal_end_line = old_content[..removed_end] .bytes() .filter(|&b| b == b'\n') .count(); if current_result.longest_line_index >= removal_start_line && current_result.longest_line_index <= removal_end_line { self.process_text_for_rendering(new_content, ui); } else { if removal_end_line < current_result.longest_line_index { current_result.longest_line_index = current_result .longest_line_index .saturating_sub(newlines_removed); } self.update_processing_result(current_result); } } let current_line = self.extract_current_line(new_content, new_cursor_pos); let current_line_length = current_line.chars().count(); 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, ); } } fn extract_current_line(&self, content: &str, cursor_pos: usize) -> String { let bytes = content.as_bytes(); let mut line_start = cursor_pos; while line_start > 0 && bytes[line_start - 1] != b'\n' { line_start -= 1; } 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() } 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); } } pub fn get_text_processing_result(&self) -> TextProcessingResult { self.text_processing_result .lock() .map(|result| result.clone()) .unwrap_or_default() } fn update_processing_result(&self, result: TextProcessingResult) { if let Ok(mut processing_result) = self.text_processing_result.lock() { *processing_result = result; } } }