egui_demo_lib/easy_mark/
easy_mark_highlighter.rs1use crate::easy_mark::easy_mark_parser;
2
3#[derive(Default)]
7pub struct MemoizedEasymarkHighlighter {
8 style: egui::Style,
9 code: String,
10 output: egui::text::LayoutJob,
11}
12
13impl MemoizedEasymarkHighlighter {
14 pub fn highlight(&mut self, egui_style: &egui::Style, code: &str) -> egui::text::LayoutJob {
15 if (&self.style, self.code.as_str()) != (egui_style, code) {
16 self.style = egui_style.clone();
17 code.clone_into(&mut self.code);
18 self.output = highlight_easymark(egui_style, code);
19 }
20 self.output.clone()
21 }
22}
23
24pub fn highlight_easymark(egui_style: &egui::Style, mut text: &str) -> egui::text::LayoutJob {
25 let mut job = egui::text::LayoutJob::default();
26 let mut style = easy_mark_parser::Style::default();
27 let mut start_of_line = true;
28
29 while !text.is_empty() {
30 if start_of_line && text.starts_with("```") {
31 let end = text.find("\n```").map_or_else(|| text.len(), |i| i + 4);
32 job.append(
33 &text[..end],
34 0.0,
35 format_from_style(
36 egui_style,
37 &easy_mark_parser::Style {
38 code: true,
39 ..Default::default()
40 },
41 ),
42 );
43 text = &text[end..];
44 style = Default::default();
45 continue;
46 }
47
48 if text.starts_with('`') {
49 style.code = true;
50 let end = text[1..]
51 .find(&['`', '\n'][..])
52 .map_or_else(|| text.len(), |i| i + 2);
53 job.append(&text[..end], 0.0, format_from_style(egui_style, &style));
54 text = &text[end..];
55 style.code = false;
56 continue;
57 }
58
59 let mut skip;
60
61 if text.starts_with('\\') && text.len() >= 2 {
62 skip = 2;
63 } else if start_of_line && text.starts_with(' ') {
64 skip = 1;
66 } else if start_of_line && text.starts_with("# ") {
67 style.heading = true;
68 skip = 2;
69 } else if start_of_line && text.starts_with("> ") {
70 style.quoted = true;
71 skip = 2;
72 } else if start_of_line && text.starts_with("- ") {
74 skip = 2;
75 } else if text.starts_with('*') {
77 skip = 1;
78 if style.strong {
79 job.append(&text[..skip], 0.0, format_from_style(egui_style, &style));
81 text = &text[skip..];
82 skip = 0;
83 }
84 style.strong ^= true;
85 } else if text.starts_with('$') {
86 skip = 1;
87 if style.small {
88 job.append(&text[..skip], 0.0, format_from_style(egui_style, &style));
90 text = &text[skip..];
91 skip = 0;
92 }
93 style.small ^= true;
94 } else if text.starts_with('^') {
95 skip = 1;
96 if style.raised {
97 job.append(&text[..skip], 0.0, format_from_style(egui_style, &style));
99 text = &text[skip..];
100 skip = 0;
101 }
102 style.raised ^= true;
103 } else {
104 skip = 0;
105 }
106 let line_end = text[skip..]
110 .find('\n')
111 .map_or_else(|| text.len(), |i| skip + i + 1);
112 let end = text[skip..]
113 .find(&['*', '`', '~', '_', '/', '$', '^', '\\', '<', '['][..])
114 .map_or_else(|| text.len(), |i| (skip + i).max(1));
115
116 if line_end <= end {
117 job.append(
118 &text[..line_end],
119 0.0,
120 format_from_style(egui_style, &style),
121 );
122 text = &text[line_end..];
123 start_of_line = true;
124 style = Default::default();
125 } else {
126 job.append(&text[..end], 0.0, format_from_style(egui_style, &style));
127 text = &text[end..];
128 start_of_line = false;
129 }
130 }
131
132 job
133}
134
135fn format_from_style(
136 egui_style: &egui::Style,
137 emark_style: &easy_mark_parser::Style,
138) -> egui::text::TextFormat {
139 use egui::{Align, Color32, Stroke, TextStyle};
140
141 let color = if emark_style.strong || emark_style.heading {
142 egui_style.visuals.strong_text_color()
143 } else if emark_style.quoted {
144 egui_style.visuals.weak_text_color()
145 } else {
146 egui_style.visuals.text_color()
147 };
148
149 let text_style = if emark_style.heading {
150 TextStyle::Heading
151 } else if emark_style.code {
152 TextStyle::Monospace
153 } else if emark_style.small | emark_style.raised {
154 TextStyle::Small
155 } else {
156 TextStyle::Body
157 };
158
159 let background = if emark_style.code {
160 egui_style.visuals.code_bg_color
161 } else {
162 Color32::TRANSPARENT
163 };
164
165 let underline = if emark_style.underline {
166 Stroke::new(1.0, color)
167 } else {
168 Stroke::NONE
169 };
170
171 let strikethrough = if emark_style.strikethrough {
172 Stroke::new(1.0, color)
173 } else {
174 Stroke::NONE
175 };
176
177 let valign = if emark_style.raised {
178 Align::TOP
179 } else {
180 Align::BOTTOM
181 };
182
183 egui::text::TextFormat {
184 font_id: text_style.resolve(egui_style),
185 color,
186 background,
187 italics: emark_style.italics,
188 underline,
189 strikethrough,
190 valign,
191 ..Default::default()
192 }
193}