tui/syntax_highlighting/
syntax_highlighter.rs1use super::syntect_bridge::{find_syntax_for_hint, syntect_to_wisp_style};
2use crate::line::Line;
3use crate::span::Span;
4use crate::style::Style;
5use crate::theme::Theme;
6use std::collections::HashMap;
7use std::sync::{LazyLock, Mutex};
8use syntect::easy::HighlightLines;
9use syntect::parsing::SyntaxSet;
10
11pub struct SyntaxHighlighter {
19 cache: Mutex<HashMap<String, HashMap<String, Vec<Line>>>>,
20}
21
22impl Default for SyntaxHighlighter {
23 fn default() -> Self {
24 Self::new()
25 }
26}
27
28impl SyntaxHighlighter {
29 pub fn new() -> Self {
30 Self { cache: Mutex::new(HashMap::new()) }
31 }
32
33 pub fn highlight(&self, code: &str, lang: &str, theme: &Theme) -> Vec<Line> {
35 if let Some(cached) = self.cache.lock().unwrap().get(lang).and_then(|m| m.get(code)) {
36 return cached.clone();
37 }
38
39 let lines = render_highlighted_lines(code, lang, theme);
40 self.cache.lock().unwrap().entry(lang.to_string()).or_default().insert(code.to_string(), lines.clone());
41
42 lines
43 }
44}
45
46static SYNTAX_SET: LazyLock<SyntaxSet> = LazyLock::new(two_face::syntax::extra_newlines);
47
48fn render_highlighted_lines(code: &str, lang: &str, theme: &Theme) -> Vec<Line> {
49 let Some(syntax) = find_syntax_for_hint(&SYNTAX_SET, lang) else {
50 return plain_code_lines(code, theme);
51 };
52
53 let syntect_theme = theme.syntect_theme();
54 let mut h = HighlightLines::new(syntax, syntect_theme);
55 let mut lines = Vec::new();
56
57 for source_line in code.lines() {
58 let Ok(ranges) = h.highlight_line(source_line, &SYNTAX_SET) else {
59 lines.push(Line::with_style(source_line, Style::fg(theme.code_fg())));
60 continue;
61 };
62
63 let mut line = Line::default();
64 for (syntect_style, text) in ranges {
65 line.push_span(Span::with_style(text, syntect_to_wisp_style(syntect_style)));
66 }
67 lines.push(line);
68 }
69
70 lines
71}
72
73fn plain_code_lines(code: &str, theme: &Theme) -> Vec<Line> {
74 let style = Style::fg(theme.code_fg());
75 code.lines().map(|line| Line::with_style(line, style)).collect()
76}