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-16 16:04:14 -04:00
|
|
|
pub(crate) fn safe_slice_to_pos(content: &str, pos: usize) -> &str {
|
2025-07-16 15:28:55 -04:00
|
|
|
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]
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
pub fn process_text_for_rendering(&mut self, content: &str, ui: &egui::Ui) {
|
2025-07-16 13:27:31 -04:00
|
|
|
let line_count = content.bytes().filter(|&b| b == b'\n').count() + 1;
|
|
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
let lines: Vec<&str> = content.lines().collect();
|
2025-07-15 11:07:41 -04:00
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
if content.is_empty() {
|
2025-07-15 00:42:01 -04:00
|
|
|
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 11:07:41 -04:00
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
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| {
|
2025-07-15 11:07:41 -04:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 11:07:41 -04:00
|
|
|
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 11:07:41 -04:00
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
if old_content.len() == new_content.len() {
|
2025-07-15 11:07:41 -04:00
|
|
|
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() {
|
2025-07-15 11:07:41 -04:00
|
|
|
self.handle_content_addition(
|
|
|
|
|
old_content,
|
|
|
|
|
new_content,
|
|
|
|
|
old_cursor_pos,
|
|
|
|
|
new_cursor_pos,
|
|
|
|
|
ui,
|
|
|
|
|
);
|
2025-07-15 00:42:01 -04:00
|
|
|
} else {
|
2025-07-15 11:07:41 -04:00
|
|
|
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 11:07:41 -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 11:07:41 -04:00
|
|
|
fn calculate_cursor_line_change(
|
|
|
|
|
&self,
|
|
|
|
|
old_content: &str,
|
|
|
|
|
new_content: &str,
|
|
|
|
|
old_cursor_pos: usize,
|
|
|
|
|
new_cursor_pos: usize,
|
|
|
|
|
) -> isize {
|
2025-07-16 15:28:55 -04:00
|
|
|
let old_newlines = Self::safe_slice_to_pos(old_content, old_cursor_pos)
|
2025-07-15 00:42:01 -04:00
|
|
|
.bytes()
|
|
|
|
|
.filter(|&b| b == b'\n')
|
|
|
|
|
.count();
|
2025-07-15 11:07:41 -04:00
|
|
|
|
2025-07-16 15:28:55 -04:00
|
|
|
let new_newlines = Self::safe_slice_to_pos(new_content, new_cursor_pos)
|
2025-07-15 00:42:01 -04:00
|
|
|
.bytes()
|
|
|
|
|
.filter(|&b| b == b'\n')
|
|
|
|
|
.count();
|
2025-07-15 11:07:41 -04:00
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
new_newlines as isize - old_newlines as isize
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-15 11:07:41 -04:00
|
|
|
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();
|
2025-07-15 11:07:41 -04:00
|
|
|
|
|
|
|
|
self.update_line_if_longer(
|
|
|
|
|
self.current_cursor_line,
|
|
|
|
|
¤t_line,
|
|
|
|
|
current_line_length,
|
|
|
|
|
ui,
|
|
|
|
|
);
|
2025-07-15 00:42:01 -04:00
|
|
|
}
|
|
|
|
|
|
2025-07-15 11:07:41 -04:00
|
|
|
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;
|
|
|
|
|
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 11:07:41 -04:00
|
|
|
|
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 11:07:41 -04:00
|
|
|
|
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 11:07:41 -04:00
|
|
|
|
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;
|
2025-07-15 11:07:41 -04:00
|
|
|
|
2025-07-16 15:28:55 -04:00
|
|
|
let addition_start_line = Self::safe_slice_to_pos(old_content, added_start)
|
2025-07-16 13:27:31 -04:00
|
|
|
.bytes()
|
|
|
|
|
.filter(|&b| b == b'\n')
|
|
|
|
|
.count();
|
2025-07-16 15:28:55 -04:00
|
|
|
let addition_end_line = Self::safe_slice_to_pos(old_content, added_end)
|
2025-07-16 13:27:31 -04:00
|
|
|
.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,
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-07-15 00:42:01 -04:00
|
|
|
}
|
|
|
|
|
|
2025-07-15 11:07:41 -04:00
|
|
|
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 11:07:41 -04:00
|
|
|
|
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 11:07:41 -04:00
|
|
|
|
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 11:07:41 -04:00
|
|
|
|
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 11:07:41 -04:00
|
|
|
|
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 11:07:41 -04:00
|
|
|
|
2025-07-16 15:28:55 -04:00
|
|
|
let removal_start_line = Self::safe_slice_to_pos(old_content, removed_start)
|
2025-07-16 13:27:31 -04:00
|
|
|
.bytes()
|
|
|
|
|
.filter(|&b| b == b'\n')
|
|
|
|
|
.count();
|
2025-07-16 15:28:55 -04:00
|
|
|
let removal_end_line = Self::safe_slice_to_pos(old_content, removed_end)
|
2025-07-16 13:27:31 -04:00
|
|
|
.bytes()
|
|
|
|
|
.filter(|&b| b == b'\n')
|
|
|
|
|
.count();
|
|
|
|
|
|
|
|
|
|
if current_result.longest_line_index >= removal_start_line
|
|
|
|
|
&& current_result.longest_line_index <= removal_end_line
|
|
|
|
|
{
|
2025-07-15 11:07:41 -04:00
|
|
|
self.process_text_for_rendering(new_content, ui);
|
2025-07-16 13:27:31 -04:00
|
|
|
} 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);
|
2025-07-15 00:42:01 -04:00
|
|
|
}
|
|
|
|
|
}
|
2025-07-15 11:07:41 -04:00
|
|
|
|
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 11:07:41 -04:00
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
let current_result = self.get_text_processing_result();
|
2025-07-15 11:07:41 -04:00
|
|
|
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 {
|
2025-07-15 11:07:41 -04:00
|
|
|
self.update_line_if_longer(
|
|
|
|
|
self.current_cursor_line,
|
|
|
|
|
¤t_line,
|
|
|
|
|
current_line_length,
|
|
|
|
|
ui,
|
|
|
|
|
);
|
2025-07-15 00:42:01 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn extract_current_line(&self, content: &str, cursor_pos: usize) -> String {
|
|
|
|
|
let bytes = content.as_bytes();
|
2025-07-16 15:28:55 -04:00
|
|
|
let safe_cursor_pos = cursor_pos.min(bytes.len());
|
2025-07-15 11:07:41 -04:00
|
|
|
|
2025-07-16 15:28:55 -04:00
|
|
|
let mut line_start = safe_cursor_pos;
|
2025-07-15 00:42:01 -04:00
|
|
|
while line_start > 0 && bytes[line_start - 1] != b'\n' {
|
|
|
|
|
line_start -= 1;
|
|
|
|
|
}
|
2025-07-15 11:07:41 -04:00
|
|
|
|
2025-07-16 15:28:55 -04:00
|
|
|
let mut line_end = safe_cursor_pos;
|
2025-07-15 00:42:01 -04:00
|
|
|
while line_end < bytes.len() && bytes[line_end] != b'\n' {
|
|
|
|
|
line_end += 1;
|
|
|
|
|
}
|
2025-07-15 11:07:41 -04:00
|
|
|
|
2025-07-16 15:28:55 -04:00
|
|
|
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()
|
|
|
|
|
}
|
2025-07-15 00:42:01 -04:00
|
|
|
}
|
|
|
|
|
|
2025-07-15 11:07:41 -04:00
|
|
|
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 11:07:41 -04:00
|
|
|
|
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| {
|
2025-07-15 11:07:41 -04:00
|
|
|
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 11:07:41 -04:00
|
|
|
|
2025-07-15 00:42:01 -04:00
|
|
|
self.update_processing_result(result);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-05 14:42:45 -04:00
|
|
|
pub fn get_text_processing_result(&self) -> TextProcessingResult {
|
|
|
|
|
self.text_processing_result
|
|
|
|
|
.lock()
|
2025-07-16 17:20:09 -04:00
|
|
|
.map(|result| result.to_owned())
|
2025-07-05 14:42:45 -04:00
|
|
|
.unwrap_or_default()
|
|
|
|
|
}
|
2025-07-15 00:42:01 -04:00
|
|
|
|
|
|
|
|
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
|
|
|
}
|