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
7pub 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 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}