Find and Replace #5

Merged
candle merged 9 commits from master into release 2025-07-16 21:36:23 +00:00
3 changed files with 44 additions and 13 deletions
Showing only changes of commit 6fa0aa0b61 - Show all commits

View File

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

View File

@ -2,7 +2,7 @@ use super::editor::{TextEditor, TextProcessingResult};
use eframe::egui;
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 mut boundary_pos = pos;
while boundary_pos > 0 && !content.is_char_boundary(boundary_pos) {

View File

@ -1,5 +1,15 @@
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(
ui: &mut egui::Ui,
content: &str,
@ -44,7 +54,7 @@ fn draw_single_highlight(
font_id: &egui::FontId,
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();
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_char_pos = content[..line_start_byte_pos].chars().count();
let start_char_pos = content[..start_pos].chars().count();
let line_start_char_pos = safe_slice_to_pos(content, line_start_byte_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 lines: Vec<&str> = content.lines().collect();