same thing for find

This commit is contained in:
candle 2025-07-16 16:04:14 -04:00
parent 48cfcb9997
commit 6fa0aa0b61
3 changed files with 44 additions and 13 deletions

View File

@ -26,11 +26,33 @@ impl TextEditor {
}; };
let mut start = 0; let mut start = 0;
while let Some(pos) = search_content[start..].find(&query) { while start < search_content.len() {
let search_slice = if search_content.is_char_boundary(start) {
&search_content[start..]
} else {
// Find next valid boundary
while start < search_content.len() && !search_content.is_char_boundary(start) {
start += 1;
}
if start >= search_content.len() {
break;
}
&search_content[start..]
};
if let Some(pos) = search_slice.find(&query) {
let absolute_pos = start + pos; let absolute_pos = start + pos;
self.find_matches self.find_matches
.push((absolute_pos, absolute_pos + query.len())); .push((absolute_pos, absolute_pos + query.len()));
// Advance to next valid character boundary instead of just +1
start = absolute_pos + 1; start = absolute_pos + 1;
while start < search_content.len() && !search_content.is_char_boundary(start) {
start += 1;
}
} else {
break;
}
} }
if !self.find_matches.is_empty() { if !self.find_matches.is_empty() {
@ -94,8 +116,8 @@ impl TextEditor {
if let Some(active_tab) = self.get_active_tab() { if let Some(active_tab) = self.get_active_tab() {
let content = &active_tab.content; let content = &active_tab.content;
let start_char = content[..start_byte.min(content.len())].chars().count(); let start_char = Self::safe_slice_to_pos(content, start_byte).chars().count();
let end_char = content[..end_byte.min(content.len())].chars().count(); let end_char = Self::safe_slice_to_pos(content, end_byte).chars().count();
let text_edit_id = egui::Id::new("main_text_editor"); let text_edit_id = egui::Id::new("main_text_editor");
if let Some(mut state) = egui::TextEdit::load_state(ctx, text_edit_id) { if let Some(mut state) = egui::TextEdit::load_state(ctx, text_edit_id) {
@ -132,8 +154,7 @@ impl TextEditor {
self.update_find_matches(); self.update_find_matches();
if let Some(active_tab) = self.get_active_tab() { if let Some(active_tab) = self.get_active_tab() {
let replacement_end_char = active_tab.content let replacement_end_char = Self::safe_slice_to_pos(&active_tab.content, replacement_end)
[..replacement_end.min(active_tab.content.len())]
.chars() .chars()
.count(); .count();

View File

@ -2,7 +2,7 @@ use super::editor::{TextEditor, TextProcessingResult};
use eframe::egui; use eframe::egui;
impl TextEditor { impl TextEditor {
fn safe_slice_to_pos(content: &str, pos: usize) -> &str { pub(crate) fn safe_slice_to_pos(content: &str, pos: usize) -> &str {
let pos = pos.min(content.len()); let pos = pos.min(content.len());
let mut boundary_pos = pos; let mut boundary_pos = pos;
while boundary_pos > 0 && !content.is_char_boundary(boundary_pos) { while boundary_pos > 0 && !content.is_char_boundary(boundary_pos) {

View File

@ -1,5 +1,15 @@
use eframe::egui; use eframe::egui;
/// Safely get a string slice up to a byte position, ensuring UTF-8 boundaries
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(super) fn draw_find_highlights( pub(super) fn draw_find_highlights(
ui: &mut egui::Ui, ui: &mut egui::Ui,
content: &str, content: &str,
@ -44,7 +54,7 @@ fn draw_single_highlight(
font_id: &egui::FontId, font_id: &egui::FontId,
is_current_match: bool, is_current_match: bool,
) { ) {
let text_up_to_start = &content[..start_pos.min(content.len())]; let text_up_to_start = safe_slice_to_pos(content, start_pos);
let start_line = text_up_to_start.chars().filter(|&c| c == '\n').count(); let start_line = text_up_to_start.chars().filter(|&c| c == '\n').count();
if start_line >= galley.rows.len() { if start_line >= galley.rows.len() {
@ -52,8 +62,8 @@ fn draw_single_highlight(
} }
let line_start_byte_pos = text_up_to_start.rfind('\n').map(|pos| pos + 1).unwrap_or(0); let line_start_byte_pos = text_up_to_start.rfind('\n').map(|pos| pos + 1).unwrap_or(0);
let line_start_char_pos = content[..line_start_byte_pos].chars().count(); let line_start_char_pos = safe_slice_to_pos(content, line_start_byte_pos).chars().count();
let start_char_pos = content[..start_pos].chars().count(); let start_char_pos = safe_slice_to_pos(content, start_pos).chars().count();
let start_col = start_char_pos - line_start_char_pos; let start_col = start_char_pos - line_start_char_pos;
let lines: Vec<&str> = content.lines().collect(); let lines: Vec<&str> = content.lines().collect();