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::Mutex;
8use syntect::easy::HighlightLines;
9use syntect::parsing::SyntaxSet;
10
11pub struct SyntaxHighlighter {
19 syntax_set: SyntaxSet,
20 cache: Mutex<HashMap<String, HashMap<String, Vec<Line>>>>,
21}
22
23impl Default for SyntaxHighlighter {
24 fn default() -> Self {
25 Self::new()
26 }
27}
28
29impl SyntaxHighlighter {
30 pub fn new() -> Self {
31 Self {
32 syntax_set: two_face::syntax::extra_newlines(),
33 cache: Mutex::new(HashMap::new()),
34 }
35 }
36
37 pub fn highlight(&self, code: &str, lang: &str, theme: &Theme) -> Vec<Line> {
39 if let Some(cached) = self
40 .cache
41 .lock()
42 .unwrap()
43 .get(lang)
44 .and_then(|m| m.get(code))
45 {
46 return cached.clone();
47 }
48
49 let lines = self.render_highlighted_lines(code, lang, theme);
50 self.cache
51 .lock()
52 .unwrap()
53 .entry(lang.to_string())
54 .or_default()
55 .insert(code.to_string(), lines.clone());
56
57 lines
58 }
59}
60
61impl SyntaxHighlighter {
62 fn render_highlighted_lines(&self, code: &str, lang: &str, theme: &Theme) -> Vec<Line> {
63 let syntax = find_syntax_for_hint(&self.syntax_set, lang);
64
65 let Some(syntax) = syntax else {
66 return plain_code_lines(code, theme);
67 };
68
69 let syntect_theme = theme.syntect_theme();
70 let mut h = HighlightLines::new(syntax, syntect_theme);
71 let mut lines = Vec::new();
72
73 for source_line in code.lines() {
74 let Ok(ranges) = h.highlight_line(source_line, &self.syntax_set) else {
75 lines.push(Line::with_style(source_line, Style::fg(theme.code_fg())));
76 continue;
77 };
78
79 let mut line = Line::default();
80 for (syntect_style, text) in ranges {
81 line.push_span(Span::with_style(text, syntect_to_wisp_style(syntect_style)));
82 }
83 lines.push(line);
84 }
85
86 lines
87 }
88}
89
90fn plain_code_lines(code: &str, theme: &Theme) -> Vec<Line> {
91 let style = Style::fg(theme.code_fg());
92 code.lines()
93 .map(|line| Line::with_style(line, style))
94 .collect()
95}