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, matches: &[(usize, usize)], current_match_index: Option, galley: &std::sync::Arc, text_area_left: f32, text_area_top: f32, font_size: f32, ) { let font_id = ui .style() .text_styles .get(&egui::TextStyle::Monospace) .unwrap_or(&egui::FontId::monospace(font_size)) .clone(); for (match_index, &(start_pos, end_pos)) in matches.iter().enumerate() { let is_current_match = current_match_index == Some(match_index); draw_single_highlight( ui, content, start_pos, end_pos, text_area_left, text_area_top, galley, &font_id, is_current_match, ); } } fn draw_single_highlight( ui: &mut egui::Ui, content: &str, start_pos: usize, end_pos: usize, text_area_left: f32, text_area_top: f32, galley: &std::sync::Arc, font_id: &egui::FontId, is_current_match: bool, ) { 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() { return; } let line_start_byte_pos = text_up_to_start.rfind('\n').map(|pos| pos + 1).unwrap_or(0); 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(); if start_line >= lines.len() { return; } let line_text = lines[start_line]; let text_before_match: String = line_text.chars().take(start_col).collect(); let text_before_width = ui.fonts(|fonts| { fonts .layout( text_before_match, font_id.clone(), egui::Color32::WHITE, f32::INFINITY, ) .size() .x }); let galley_row = &galley.rows[start_line]; let start_y = text_area_top + galley_row.min_y(); let line_height = galley_row.height(); let start_x = text_area_left + text_before_width; let match_text = &content[start_pos..end_pos.min(content.len())]; let match_width = ui.fonts(|fonts| { fonts .layout( match_text.to_string(), font_id.clone(), ui.visuals().text_color(), f32::INFINITY, ) .size() .x }); let highlight_rect = egui::Rect::from_min_size( egui::pos2(start_x, start_y), egui::vec2(match_width, line_height), ); let highlight_color = if is_current_match { ui.visuals().selection.bg_fill } else { ui.visuals().selection.bg_fill.gamma_multiply(0.6) }; let painter = ui.painter(); painter.rect_filled(highlight_rect, 0.0, highlight_color); }