Skip to main content

imp_tui/
highlight.rs

1use ratatui::style::{Color, Modifier, Style};
2use ratatui::text::{Line, Span};
3use syntect::easy::HighlightLines;
4use syntect::highlighting::{FontStyle, Style as SyntectStyle, ThemeSet};
5use syntect::parsing::SyntaxSet;
6
7/// Wraps syntect for code block syntax highlighting.
8pub struct Highlighter {
9    syntax_set: SyntaxSet,
10    theme: syntect::highlighting::Theme,
11}
12
13impl Highlighter {
14    pub fn new() -> Self {
15        let syntax_set = SyntaxSet::load_defaults_newlines();
16        let theme_set = ThemeSet::load_defaults();
17        let theme = theme_set.themes["base16-ocean.dark"].clone();
18        Self { syntax_set, theme }
19    }
20
21    /// Highlight a code block, returning styled ratatui Lines.
22    pub fn highlight_code(&self, code: &str, language: &str) -> Vec<Line<'static>> {
23        let syntax = self
24            .syntax_set
25            .find_syntax_by_token(language)
26            .unwrap_or_else(|| self.syntax_set.find_syntax_plain_text());
27
28        let mut h = HighlightLines::new(syntax, &self.theme);
29        let mut lines = Vec::new();
30
31        for line_str in code.lines() {
32            match h.highlight_line(line_str, &self.syntax_set) {
33                Ok(ranges) => {
34                    let spans: Vec<Span<'static>> = ranges
35                        .iter()
36                        .map(|(style, text)| {
37                            Span::styled((*text).to_string(), syntect_to_ratatui(style))
38                        })
39                        .collect();
40                    lines.push(Line::from(spans));
41                }
42                Err(_) => {
43                    lines.push(Line::raw(line_str.to_string()));
44                }
45            }
46        }
47
48        lines
49    }
50}
51
52impl Default for Highlighter {
53    fn default() -> Self {
54        Self::new()
55    }
56}
57
58fn syntect_to_ratatui(style: &SyntectStyle) -> Style {
59    let fg = Color::Rgb(style.foreground.r, style.foreground.g, style.foreground.b);
60    let mut ratatui_style = Style::default().fg(fg);
61    if style.font_style.contains(FontStyle::BOLD) {
62        ratatui_style = ratatui_style.add_modifier(Modifier::BOLD);
63    }
64    if style.font_style.contains(FontStyle::ITALIC) {
65        ratatui_style = ratatui_style.add_modifier(Modifier::ITALIC);
66    }
67    if style.font_style.contains(FontStyle::UNDERLINE) {
68        ratatui_style = ratatui_style.add_modifier(Modifier::UNDERLINED);
69    }
70    ratatui_style
71}