use egui::text::{CCursorRange, LayoutJob}; use crate::{ markdown::Heading, text_styles::{H1, H1_MONO, H2, H2_MONO, H3, H3_MONO, H4, H4_MONO, H5, H5_MONO, H6, H6_MONO}, }; use super::{Item, Style, parse}; /// Highlight markdown, caching previous output to save CPU. #[derive(Default)] pub struct MemoizedHighlighter { style: egui::Style, code: String, output: LayoutJob, } impl MemoizedHighlighter { pub fn highlight( &mut self, egui_style: &egui::Style, code: &str, cursor: Option, ) -> LayoutJob { if (&self.style, self.code.as_str()) != (egui_style, code) { self.style = egui_style.clone(); code.clone_into(&mut self.code); self.output = highlight_markdown(egui_style, code, cursor); } self.output.clone() } } pub fn highlight_markdown( egui_style: &egui::Style, text: &str, // TODO: hide special characters where cursor isn't _cursor: Option, ) -> LayoutJob { let mut job = LayoutJob::default(); let code_style = Style { code: true, ..Default::default() }; for item in parse(text) { match item { Item::Text { span, style } => { job.append(&span, 0.0, format_from_style(egui_style, &style)); } Item::CodeBlock { all, language: _, // TODO code: _, // TODO } => { job.append(&all, 100.0, format_from_style(egui_style, &code_style)); } } } job } fn format_from_style(egui_style: &egui::Style, style: &Style) -> egui::text::TextFormat { use egui::{Align, Color32, Stroke, TextStyle}; let color = if style.code { egui_style.visuals.strong_text_color() * Color32::GREEN } else if style.strong || style.heading.is_some() { egui_style.visuals.strong_text_color() } else if style.quoted { egui_style.visuals.weak_text_color() } else { egui_style.visuals.text_color() }; let text_style = if let Some(heading) = style.heading { let text_style = match (heading, style.code) { (Heading::H1, false) => H1, (Heading::H2, false) => H2, (Heading::H3, false) => H3, (Heading::H4, false) => H4, (Heading::H5, false) => H5, (Heading::H6, false) => H6, (Heading::H1, true) => H1_MONO, (Heading::H2, true) => H2_MONO, (Heading::H3, true) => H3_MONO, (Heading::H4, true) => H4_MONO, (Heading::H5, true) => H5_MONO, (Heading::H6, true) => H6_MONO, }; TextStyle::Name(text_style.into()) } else if style.code { TextStyle::Monospace } else if style.small | style.raised { TextStyle::Small } else { TextStyle::Body }; let background = if style.code { egui_style.visuals.code_bg_color } else { Color32::TRANSPARENT }; let underline = if style.underline { Stroke::new(1.0, color) } else { Stroke::NONE }; let strikethrough = if style.strikethrough { Stroke::new(1.0, color) } else { Stroke::NONE }; let valign = if style.raised { Align::TOP } else { Align::BOTTOM }; egui::text::TextFormat { font_id: text_style.resolve(egui_style), color, background, italics: style.italics, underline, strikethrough, valign, ..Default::default() } }