use eframe::egui; thread_local! { static VISUAL_LINE_MAPPING_CACHE: std::cell::RefCell>)>> = std::cell::RefCell::new(None); } pub(super) fn get_visual_line_mapping( ui: &egui::Ui, content: &str, available_width: f32, font_size: f32, ) -> Vec> { let should_recalculate = VISUAL_LINE_MAPPING_CACHE.with(|cache| { if let Some((cached_content, cached_width, _)) = cache.borrow().as_ref() { content != cached_content || available_width != *cached_width } else { true } }); if should_recalculate { let visual_lines = calculate_visual_line_mapping(ui, content, available_width, font_size); VISUAL_LINE_MAPPING_CACHE.with(|cache| { *cache.borrow_mut() = Some((content.to_owned(), available_width, visual_lines)); }); } VISUAL_LINE_MAPPING_CACHE.with(|cache| { cache .borrow() .as_ref() .map(|(_, _, mapping)| mapping.clone()) .unwrap_or_default() }) } fn calculate_visual_line_mapping( ui: &egui::Ui, content: &str, available_width: f32, font_size: f32, ) -> Vec> { let mut visual_lines = Vec::new(); let font_id = egui::FontId::monospace(font_size); for (line_num, line) in content.lines().enumerate() { if line.is_empty() { visual_lines.push(Some(line_num + 1)); continue; } let galley = ui.fonts(|fonts| { fonts.layout( line.to_string(), font_id.clone(), egui::Color32::WHITE, available_width, ) }); let wrapped_line_count = galley.rows.len().max(1); visual_lines.push(Some(line_num + 1)); for _ in 1..wrapped_line_count { visual_lines.push(None); } } visual_lines } pub(super) fn render_line_numbers( ui: &mut egui::Ui, line_count: usize, visual_line_mapping: &[Option], line_number_width: f32, word_wrap: bool, font_size: f32, ) { ui.vertical(|ui| { ui.set_width(line_number_width); ui.spacing_mut().item_spacing.y = 0.0; let text_color = ui.visuals().weak_text_color(); let bg_color = ui.visuals().extreme_bg_color; let line_numbers_rect = ui.available_rect_before_wrap(); ui.painter() .rect_filled(line_numbers_rect, 0.0, bg_color); let font_id = egui::FontId::monospace(font_size); let line_count_width = line_count.to_string().len(); if word_wrap { for line_number_opt in visual_line_mapping { let text = if let Some(line_number) = line_number_opt { format!("{:>width$}", line_number, width = line_count_width) } else { " ".repeat(line_count_width) }; ui.label( egui::RichText::new(text) .font(font_id.clone()) .color(text_color), ); } } else { for i in 1..=line_count { let text = format!("{:>width$}", i, width = line_count_width); ui.label( egui::RichText::new(text) .font(font_id.clone()) .color(text_color), ); } } }); }