ced/src/app/state/processing.rs
2025-07-16 15:28:55 -04:00

385 lines
12 KiB
Rust

use super::editor::{TextEditor, TextProcessingResult};
use eframe::egui;
impl TextEditor {
fn safe_slice_to_pos(content: &str, pos: usize) -> &str {
let pos = pos.min(content.len());
let mut boundary_pos = pos;
while boundary_pos > 0 && !content.is_char_boundary(boundary_pos) {
boundary_pos -= 1;
}
&content[..boundary_pos]
}
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 = Self::safe_slice_to_pos(old_content, old_cursor_pos)
.bytes()
.filter(|&b| b == b'\n')
.count();
let new_newlines = Self::safe_slice_to_pos(new_content, new_cursor_pos)
.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,
&current_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 = Self::safe_slice_to_pos(old_content, added_start)
.bytes()
.filter(|&b| b == b'\n')
.count();
let addition_end_line = Self::safe_slice_to_pos(old_content, added_end)
.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,
&current_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 = Self::safe_slice_to_pos(old_content, removed_start)
.bytes()
.filter(|&b| b == b'\n')
.count();
let removal_end_line = Self::safe_slice_to_pos(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,
&current_line,
current_line_length,
ui,
);
}
}
fn extract_current_line(&self, content: &str, cursor_pos: usize) -> String {
let bytes = content.as_bytes();
let safe_cursor_pos = cursor_pos.min(bytes.len());
let mut line_start = safe_cursor_pos;
while line_start > 0 && bytes[line_start - 1] != b'\n' {
line_start -= 1;
}
let mut line_end = safe_cursor_pos;
while line_end < bytes.len() && bytes[line_end] != b'\n' {
line_end += 1;
}
let line_start_boundary = line_start;
let line_end_boundary = line_end;
if content.is_char_boundary(line_start_boundary) && content.is_char_boundary(line_end_boundary) {
content[line_start_boundary..line_end_boundary].to_string()
} else {
Self::safe_slice_to_pos(content, line_end_boundary)[line_start_boundary..].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;
}
}
}