2025-07-05 14:42:45 -04:00
|
|
|
use eframe::egui;
|
|
|
|
|
|
2025-07-16 16:04:14 -04:00
|
|
|
/// 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]
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
pub(super) fn draw_find_highlights(
|
2025-07-05 14:42:45 -04:00
|
|
|
ui: &mut egui::Ui,
|
|
|
|
|
content: &str,
|
2025-07-16 13:27:31 -04:00
|
|
|
matches: &[(usize, usize)],
|
|
|
|
|
current_match_index: Option<usize>,
|
|
|
|
|
galley: &std::sync::Arc<egui::Galley>,
|
|
|
|
|
text_area_left: f32,
|
|
|
|
|
text_area_top: f32,
|
2025-07-05 14:42:45 -04:00
|
|
|
font_size: f32,
|
|
|
|
|
) {
|
|
|
|
|
let font_id = ui
|
|
|
|
|
.style()
|
|
|
|
|
.text_styles
|
|
|
|
|
.get(&egui::TextStyle::Monospace)
|
|
|
|
|
.unwrap_or(&egui::FontId::monospace(font_size))
|
|
|
|
|
.clone();
|
|
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
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,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-07-05 14:42:45 -04:00
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
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<egui::Galley>,
|
|
|
|
|
font_id: &egui::FontId,
|
|
|
|
|
is_current_match: bool,
|
|
|
|
|
) {
|
2025-07-16 16:04:14 -04:00
|
|
|
let text_up_to_start = safe_slice_to_pos(content, start_pos);
|
2025-07-05 14:42:45 -04:00
|
|
|
let start_line = text_up_to_start.chars().filter(|&c| c == '\n').count();
|
|
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
if start_line >= galley.rows.len() {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2025-07-05 14:42:45 -04:00
|
|
|
let line_start_byte_pos = text_up_to_start.rfind('\n').map(|pos| pos + 1).unwrap_or(0);
|
2025-07-16 16:04:14 -04:00
|
|
|
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();
|
2025-07-05 14:42:45 -04:00
|
|
|
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
|
|
|
|
|
});
|
|
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
let galley_row = &galley.rows[start_line];
|
|
|
|
|
let start_y = text_area_top + galley_row.min_y();
|
|
|
|
|
let line_height = galley_row.height();
|
2025-07-05 14:42:45 -04:00
|
|
|
let start_x = text_area_left + text_before_width;
|
|
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
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
|
|
|
|
|
});
|
2025-07-05 14:42:45 -04:00
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
let highlight_rect = egui::Rect::from_min_size(
|
|
|
|
|
egui::pos2(start_x, start_y),
|
|
|
|
|
egui::vec2(match_width, line_height),
|
|
|
|
|
);
|
2025-07-05 14:42:45 -04:00
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
let highlight_color = if is_current_match {
|
|
|
|
|
ui.visuals().selection.bg_fill
|
|
|
|
|
} else {
|
|
|
|
|
ui.visuals().selection.bg_fill.gamma_multiply(0.6)
|
|
|
|
|
};
|
2025-07-05 14:42:45 -04:00
|
|
|
|
2025-07-16 13:27:31 -04:00
|
|
|
let painter = ui.painter();
|
|
|
|
|
painter.rect_filled(highlight_rect, 0.0, highlight_color);
|
2025-07-05 14:42:45 -04:00
|
|
|
}
|