1use egui::text::LayoutJob;
2use egui::{Color32, Style, TextFormat, TextStyle};
3use re_entity_db::InstancePath;
4use re_log_types::external::re_types_core::{
5 ArchetypeName, ComponentDescriptor, ComponentIdentifier, ComponentType,
6};
7use re_log_types::{ComponentPath, EntityPath, EntityPathPart, Instance};
8
9use crate::HasDesignTokens as _;
10
11pub trait SyntaxHighlighting {
13 fn syntax_highlighted(&self, style: &Style) -> LayoutJob {
14 let mut builder = SyntaxHighlightedBuilder::new();
15 self.syntax_highlight_into(&mut builder);
16 builder.into_job(style)
17 }
18
19 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder);
20}
21
22#[derive(Debug, Default)]
26pub struct SyntaxHighlightedBuilder {
27 text: String,
28 parts: smallvec::SmallVec<[SyntaxHighlightedPart; 1]>,
29}
30
31impl SyntaxHighlightedBuilder {
42 pub const QUOTE_CHAR: char = '"';
43
44 pub fn new() -> Self {
45 Self::default()
46 }
47
48 pub fn from(job: impl Into<LayoutJob>) -> Self {
52 let job = job.into();
53 Self {
54 text: job.text,
55 parts: job
56 .sections
57 .into_iter()
58 .map(|s| SyntaxHighlightedPart {
59 style: SyntaxHighlightedStyle::Custom(Box::new(s.format)),
60 byte_range: s.byte_range,
61 })
62 .collect(),
63 }
64 }
65
66 #[inline]
68 pub fn with(mut self, portion: &dyn SyntaxHighlighting) -> Self {
69 portion.syntax_highlight_into(&mut self);
70 self
71 }
72
73 #[inline]
75 pub fn append(&mut self, portion: &dyn SyntaxHighlighting) -> &mut Self {
76 portion.syntax_highlight_into(self);
77 self
78 }
79
80 fn append_kind(&mut self, style: SyntaxHighlightedStyle, portion: &str) -> &mut Self {
81 let start = self.text.len();
82 self.text.push_str(portion);
83 let end = self.text.len();
84 self.parts.push(SyntaxHighlightedPart {
85 byte_range: start..end,
86 style,
87 });
88 self
89 }
90}
91
92macro_rules! impl_style_fns {
93 ($docs:literal, $pure:ident, $with:ident, $append:ident, $style:ident) => {
94 impl_style_fns!($docs, $pure, $with, $append, (self, portion) {
95 self.append_kind(SyntaxHighlightedStyle::$style, portion);
96 });
97 };
98 ($docs:literal, $pure:ident, $with:ident, $append:ident, ($self:ident, $portion:ident) $content:expr) => {
99 #[doc = $docs]
100 #[inline]
101 pub fn $with(mut self, portion: &str) -> Self {
102 self.$append(portion);
103 self
104 }
105
106 #[doc = $docs]
107 #[inline]
108 pub fn $append(&mut $self, $portion: &str) -> &mut Self {
109 $content
110 $self
111 }
112
113 #[doc = $docs]
114 #[inline]
115 pub fn $pure(portion: &str) -> Self {
116 Self::new().$with(portion)
117 }
118 };
119}
120
121impl SyntaxHighlightedBuilder {
122 impl_style_fns!("null", null, with_null, append_null, Null);
123
124 impl_style_fns!(
125 "Some primitive value, e.g. a number or bool.",
126 primitive,
127 with_primitive,
128 append_primitive,
129 Primitive
130 );
131
132 impl_style_fns!(
133 "A string identifier.\n\nE.g. a variable name, field name, etc. Won't be quoted.",
134 identifier,
135 with_identifier,
136 append_identifier,
137 Identifier
138 );
139
140 impl_style_fns!(
141 "Some string data. Will be quoted.",
142 string_value,
143 with_string_value,
144 append_string_value,
145 (self, portion) {
146 let quote = Self::QUOTE_CHAR.to_string();
147 self.append_kind(SyntaxHighlightedStyle::StringValue, "e);
148 self.append_kind(SyntaxHighlightedStyle::StringValue, portion);
149 self.append_kind(SyntaxHighlightedStyle::StringValue, "e);
150 }
151 );
152
153 impl_style_fns!(
154 "A keyword, e.g. a filter operator, like `and` or `all`",
155 keyword,
156 with_keyword,
157 append_keyword,
158 Keyword
159 );
160
161 impl_style_fns!(
162 "An index number, e.g. an array index.",
163 index,
164 with_index,
165 append_index,
166 Index
167 );
168
169 impl_style_fns!(
170 "Some syntax, e.g. brackets, commas, colons, etc.",
171 syntax,
172 with_syntax,
173 append_syntax,
174 Syntax
175 );
176
177 impl_style_fns!(
178 "Body text, subdued (default label color).",
179 body,
180 with_body,
181 append_body,
182 Body
183 );
184
185 impl_style_fns!(
186 "Body text with default color (color of inactive buttons).",
187 body_default,
188 with_body_default,
189 append_body_default,
190 BodyDefault
191 );
192
193 impl_style_fns!(
194 "Body text in italics, e.g. for emphasis.",
195 body_italics,
196 with_body_italics,
197 append_body_italics,
198 BodyItalics
199 );
200
201 #[inline]
203 pub fn append_with_format(&mut self, text: &str, format: TextFormat) -> &mut Self {
204 self.append_kind(SyntaxHighlightedStyle::Custom(Box::new(format)), text);
205 self
206 }
207
208 #[inline]
210 pub fn append_with_format_closure<F>(&mut self, text: &str, f: F) -> &mut Self
211 where
212 F: 'static + Fn(&Style) -> TextFormat,
213 {
214 self.append_kind(SyntaxHighlightedStyle::CustomClosure(Box::new(f)), text);
215 self
216 }
217
218 #[inline]
220 pub fn with_format(mut self, text: &str, format: TextFormat) -> Self {
221 self.append_with_format(text, format);
222 self
223 }
224
225 #[inline]
227 pub fn with_format_closure<F>(mut self, text: &str, f: F) -> Self
228 where
229 F: 'static + Fn(&Style) -> TextFormat,
230 {
231 self.append_with_format_closure(text, f);
232 self
233 }
234}
235
236impl SyntaxHighlightedBuilder {
239 #[inline]
240 pub fn into_job(self, style: &Style) -> LayoutJob {
241 let mut job = LayoutJob {
242 text: self.text,
243 sections: Vec::with_capacity(self.parts.len()),
244 ..Default::default()
245 };
246
247 for part in self.parts {
248 let format = part.style.into_format(style);
249 job.sections.push(egui::text::LayoutSection {
250 byte_range: part.byte_range,
251 format,
252 leading_space: 0.0,
253 });
254 }
255
256 job
257 }
258
259 #[inline]
260 pub fn into_widget_text(self, style: &Style) -> egui::WidgetText {
261 self.into_job(style).into()
262 }
263
264 pub fn text(&self) -> &str {
265 &self.text
266 }
267}
268
269enum SyntaxHighlightedStyle {
272 StringValue,
273 Identifier,
274 Keyword,
275 Index,
276 Null,
277 Primitive,
278 Syntax,
279 Body,
280 BodyDefault,
281 BodyItalics,
282 Custom(Box<TextFormat>),
283 CustomClosure(Box<dyn Fn(&Style) -> TextFormat>),
284}
285
286impl std::fmt::Debug for SyntaxHighlightedStyle {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 match self {
289 Self::StringValue => write!(f, "StringValue"),
290 Self::Identifier => write!(f, "Identifier"),
291 Self::Keyword => write!(f, "Keyword"),
292 Self::Index => write!(f, "Index"),
293 Self::Null => write!(f, "Null"),
294 Self::Primitive => write!(f, "Primitive"),
295 Self::Syntax => write!(f, "Syntax"),
296 Self::Body => write!(f, "Body"),
297 Self::BodyDefault => write!(f, "BodyDefault"),
298 Self::BodyItalics => write!(f, "BodyItalics"),
299 Self::Custom(_) => write!(f, "Custom(…)"),
300 Self::CustomClosure(_) => write!(f, "CustomClosure(…)"),
301 }
302 }
303}
304
305#[derive(Debug)]
306struct SyntaxHighlightedPart {
307 byte_range: std::ops::Range<usize>,
308 style: SyntaxHighlightedStyle,
309}
310
311impl SyntaxHighlightedStyle {
312 pub fn monospace_with_color(style: &Style, color: Color32) -> TextFormat {
314 TextFormat {
315 font_id: TextStyle::Monospace.resolve(style),
316 color: style.visuals.override_text_color.unwrap_or(color),
317 ..Default::default()
318 }
319 }
320
321 pub fn body_with_color(style: &Style, color: Color32) -> TextFormat {
322 TextFormat {
323 font_id: TextStyle::Body.resolve(style),
324 color: style.visuals.override_text_color.unwrap_or(color),
325 ..Default::default()
326 }
327 }
328
329 pub fn body(style: &Style) -> TextFormat {
330 Self::body_with_color(style, Color32::PLACEHOLDER)
331 }
332
333 pub fn into_format(self, style: &Style) -> TextFormat {
334 match self {
335 Self::StringValue => {
336 Self::monospace_with_color(style, style.tokens().code_string_color)
337 }
338 Self::Identifier => Self::monospace_with_color(style, style.tokens().text_default),
339 Self::Keyword => Self::body_with_color(style, style.tokens().code_keyword_color),
341 Self::Index => Self::monospace_with_color(style, style.tokens().code_index_color),
342 Self::Null => Self::monospace_with_color(style, style.tokens().code_null_color),
343 Self::Primitive => {
344 Self::monospace_with_color(style, style.tokens().code_primitive_color)
345 }
346 Self::Syntax => Self::monospace_with_color(style, style.tokens().text_subdued),
347 Self::Body => Self::body(style),
348 Self::BodyDefault => {
349 let mut format = Self::body(style);
350 format.color = style
351 .visuals
352 .override_text_color
353 .unwrap_or_else(|| style.tokens().text_default);
354 format
355 }
356 Self::BodyItalics => {
357 let mut format = Self::body(style);
358 format.italics = true;
359 format
360 }
361 Self::Custom(format) => *format,
362 Self::CustomClosure(f) => f(style),
363 }
364 }
365}
366
367impl SyntaxHighlighting for EntityPathPart {
370 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
371 builder.append_identifier(&self.ui_string());
372 }
373}
374
375impl SyntaxHighlighting for Instance {
376 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
377 if self.is_all() {
378 builder.append_primitive("all");
379 } else {
380 builder.append_index(&re_format::format_uint(self.get()));
381 }
382 }
383}
384
385impl SyntaxHighlighting for EntityPath {
386 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
387 builder.append_syntax("/");
388
389 for (i, part) in self.iter().enumerate() {
390 if i != 0 {
391 builder.append_syntax("/");
392 }
393 builder.append(part);
394 }
395 }
396}
397
398impl SyntaxHighlighting for InstancePath {
399 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
400 builder.append(&self.entity_path);
401 if self.instance.is_specific() {
402 builder.append(&InstanceInBrackets(self.instance));
403 }
404 }
405}
406
407impl SyntaxHighlighting for ComponentType {
408 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
409 builder.append_identifier(self.short_name());
410 }
411}
412
413impl SyntaxHighlighting for ArchetypeName {
414 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
415 builder.append_identifier(self.short_name());
416 }
417}
418
419impl SyntaxHighlighting for ComponentIdentifier {
420 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
421 builder.append_identifier(self.as_ref());
422 }
423}
424
425impl SyntaxHighlighting for ComponentDescriptor {
426 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
427 builder.append_identifier(self.display_name());
428 }
429}
430
431impl SyntaxHighlighting for ComponentPath {
432 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
433 let Self {
434 entity_path,
435 component,
436 } = self;
437 builder
438 .append(entity_path)
439 .append_syntax(":")
440 .append(component);
441 }
442}
443
444pub struct InstanceInBrackets(pub Instance);
446
447impl SyntaxHighlighting for InstanceInBrackets {
448 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
449 builder
450 .append_syntax("[")
451 .append(&self.0)
452 .append_syntax("]");
453 }
454}
455
456macro_rules! impl_sh_primitive {
457 ($t:ty, $to_string:path) => {
458 impl SyntaxHighlighting for $t {
459 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
460 builder.append_primitive(&$to_string(*self));
461 }
462 }
463 };
464 ($t:ty) => {
465 impl SyntaxHighlighting for $t {
466 fn syntax_highlight_into(&self, builder: &mut SyntaxHighlightedBuilder) {
467 builder.append_primitive(&self.to_string());
468 }
469 }
470 };
471}
472
473impl_sh_primitive!(f32, re_format::format_f32);
474impl_sh_primitive!(f64, re_format::format_f64);
475
476impl_sh_primitive!(i8, re_format::format_int);
477impl_sh_primitive!(i16, re_format::format_int);
478impl_sh_primitive!(i32, re_format::format_int);
479impl_sh_primitive!(i64, re_format::format_int);
480impl_sh_primitive!(isize, re_format::format_int);
481impl_sh_primitive!(u8, re_format::format_uint);
482impl_sh_primitive!(u16, re_format::format_uint);
483impl_sh_primitive!(u32, re_format::format_uint);
484impl_sh_primitive!(u64, re_format::format_uint);
485impl_sh_primitive!(usize, re_format::format_uint);
486
487impl_sh_primitive!(bool);
488
489impl<T: SyntaxHighlighting> From<T> for SyntaxHighlightedBuilder {
490 fn from(portion: T) -> Self {
491 let mut builder = Self::new();
492 portion.syntax_highlight_into(&mut builder);
493 builder
494 }
495}