ced/src/app/state/processing.rs

332 lines
10 KiB
Rust
Raw Normal View History

2025-07-05 14:42:45 -04:00
use super::editor::{TextEditor, TextProcessingResult};
2025-07-15 00:42:01 -04:00
use eframe::egui;
2025-07-05 14:42:45 -04:00
impl TextEditor {
2025-07-15 00:42:01 -04:00
/// 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);
2025-07-15 00:42:01 -04:00
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,
2025-07-05 14:42:45 -04:00
});
2025-07-15 00:42:01 -04:00
return;
2025-07-05 14:42:45 -04:00
}
2025-07-15 00:42:01 -04:00
let mut longest_line_index = 0;
let mut longest_line_length = 0;
2025-07-15 00:42:01 -04:00
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;
}
}
2025-07-05 14:42:45 -04:00
2025-07-15 00:42:01 -04:00
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
2025-07-15 00:42:01 -04:00
})
2025-07-05 14:42:45 -04:00
} else {
2025-07-15 00:42:01 -04:00
0.0
2025-07-05 14:42:45 -04:00
};
let result = TextProcessingResult {
line_count,
2025-07-15 00:42:01 -04:00
longest_line_index,
longest_line_length,
longest_line_pixel_width,
2025-07-05 14:42:45 -04:00
};
2025-07-15 00:42:01 -04:00
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,
) {
let line_change = self.calculate_cursor_line_change(
old_content,
new_content,
old_cursor_pos,
new_cursor_pos,
);
2025-07-15 00:42:01 -04:00
self.current_cursor_line = (self.current_cursor_line as isize + line_change) as usize;
2025-07-15 00:42:01 -04:00
if old_content.len() == new_content.len() {
self.handle_character_replacement(
old_content,
new_content,
old_cursor_pos,
new_cursor_pos,
ui,
);
2025-07-15 00:42:01 -04:00
} else if new_content.len() > old_content.len() {
self.handle_content_addition(
old_content,
new_content,
old_cursor_pos,
new_cursor_pos,
ui,
);
2025-07-15 00:42:01 -04:00
} else {
self.handle_content_removal(
old_content,
new_content,
old_cursor_pos,
new_cursor_pos,
ui,
);
2025-07-05 14:42:45 -04:00
}
2025-07-15 00:42:01 -04:00
self.previous_cursor_line = self.current_cursor_line;
2025-07-05 14:42:45 -04:00
}
2025-07-15 00:42:01 -04:00
/// 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 {
2025-07-15 00:42:01 -04:00
let old_newlines = old_content[..old_cursor_pos.min(old_content.len())]
.bytes()
.filter(|&b| b == b'\n')
.count();
2025-07-15 00:42:01 -04:00
let new_newlines = new_content[..new_cursor_pos.min(new_content.len())]
.bytes()
.filter(|&b| b == b'\n')
.count();
2025-07-15 00:42:01 -04:00
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,
) {
2025-07-15 00:42:01 -04:00
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,
&current_line,
current_line_length,
ui,
);
2025-07-15 00:42:01 -04:00
}
/// 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,
) {
2025-07-15 00:42:01 -04:00
let min_len = old_content.len().min(new_content.len());
let mut common_prefix = 0;
let mut common_suffix = 0;
2025-07-15 00:42:01 -04:00
for i in 0..min_len {
if old_content.as_bytes()[i] == new_content.as_bytes()[i] {
common_prefix += 1;
} else {
break;
}
}
2025-07-15 00:42:01 -04:00
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;
}
}
2025-07-15 00:42:01 -04:00
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();
2025-07-15 00:42:01 -04:00
if newlines_added > 0 {
let mut current_result = self.get_text_processing_result();
current_result.line_count += newlines_added;
self.update_processing_result(current_result);
}
2025-07-15 00:42:01 -04:00
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,
&current_line,
current_line_length,
ui,
);
2025-07-15 00:42:01 -04:00
}
/// 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,
) {
2025-07-15 00:42:01 -04:00
let min_len = old_content.len().min(new_content.len());
let mut common_prefix = 0;
let mut common_suffix = 0;
2025-07-15 00:42:01 -04:00
for i in 0..min_len {
if old_content.as_bytes()[i] == new_content.as_bytes()[i] {
common_prefix += 1;
} else {
break;
}
}
2025-07-15 00:42:01 -04:00
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;
}
}
2025-07-15 00:42:01 -04:00
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();
2025-07-15 00:42:01 -04:00
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);
2025-07-15 00:42:01 -04:00
if self.current_cursor_line <= current_result.longest_line_index {
self.process_text_for_rendering(new_content, ui);
2025-07-15 00:42:01 -04:00
}
2025-07-15 00:42:01 -04:00
self.update_processing_result(current_result);
}
2025-07-15 00:42:01 -04:00
let current_line = self.extract_current_line(new_content, new_cursor_pos);
let current_line_length = current_line.chars().count();
2025-07-15 00:42:01 -04:00
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
{
2025-07-15 00:42:01 -04:00
self.process_text_for_rendering(new_content, ui);
} else {
self.update_line_if_longer(
self.current_cursor_line,
&current_line,
current_line_length,
ui,
);
2025-07-15 00:42:01 -04:00
}
}
/// 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();
2025-07-15 00:42:01 -04:00
let mut line_start = cursor_pos;
while line_start > 0 && bytes[line_start - 1] != b'\n' {
line_start -= 1;
}
2025-07-15 00:42:01 -04:00
let mut line_end = cursor_pos;
while line_end < bytes.len() && bytes[line_end] != b'\n' {
line_end += 1;
}
2025-07-15 00:42:01 -04:00
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,
) {
2025-07-15 00:42:01 -04:00
let current_result = self.get_text_processing_result();
2025-07-15 00:42:01 -04:00
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
2025-07-15 00:42:01 -04:00
});
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,
};
2025-07-15 00:42:01 -04:00
self.update_processing_result(result);
}
}
/// Get the current text processing result
2025-07-05 14:42:45 -04:00
pub fn get_text_processing_result(&self) -> TextProcessingResult {
self.text_processing_result
.lock()
.map(|result| result.clone())
.unwrap_or_default()
}
2025-07-15 00:42:01 -04:00
/// 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;
}
}
2025-07-05 14:42:45 -04:00
}