code_mesh_tui/utils/
syntax_highlighter.rs1use ratatui::{style::Style, text::Span};
2use syntect::{
3 easy::HighlightLines,
4 highlighting::{ThemeSet, Style as SyntectStyle},
5 parsing::SyntaxSet,
6};
7
8use crate::theme::Theme;
9
10pub struct SyntaxHighlighter {
12 syntax_set: SyntaxSet,
13 theme_set: ThemeSet,
14}
15
16impl SyntaxHighlighter {
17 pub fn new() -> Self {
19 Self {
20 syntax_set: SyntaxSet::load_defaults_newlines(),
21 theme_set: ThemeSet::load_defaults(),
22 }
23 }
24
25 pub fn highlight<'a>(&self, line: &'a str, language: &str) -> Vec<Span<'a>> {
27 let syntax = match self.syntax_set.find_syntax_by_name(language) {
28 Some(syntax) => syntax,
29 None => {
30 let extension = match language {
32 "rust" => "rs",
33 "javascript" => "js",
34 "typescript" => "ts",
35 "python" => "py",
36 "bash" => "sh",
37 _ => language,
38 };
39
40 match self.syntax_set.find_syntax_by_extension(extension) {
41 Some(syntax) => syntax,
42 None => {
43 return vec![Span::raw(line)];
45 }
46 }
47 }
48 };
49
50 let theme = &self.theme_set.themes["base16-ocean.dark"];
51 let mut highlighter = HighlightLines::new(syntax, theme);
52
53 match highlighter.highlight_line(line, &self.syntax_set) {
54 Ok(ranges) => {
55 ranges
56 .into_iter()
57 .map(|(style, text)| {
58 let ratatui_style = self.syntect_to_ratatui_style(style);
59 Span::styled(text, ratatui_style)
60 })
61 .collect()
62 }
63 Err(_) => vec![Span::raw(line)],
64 }
65 }
66
67 fn syntect_to_ratatui_style(&self, style: SyntectStyle) -> Style {
69 let fg = style.foreground;
70 let color = ratatui::style::Color::Rgb(fg.r, fg.g, fg.b);
71
72 let mut ratatui_style = Style::default().fg(color);
73
74 if style.font_style.contains(syntect::highlighting::FontStyle::BOLD) {
75 ratatui_style = ratatui_style.add_modifier(ratatui::style::Modifier::BOLD);
76 }
77
78 if style.font_style.contains(syntect::highlighting::FontStyle::ITALIC) {
79 ratatui_style = ratatui_style.add_modifier(ratatui::style::Modifier::ITALIC);
80 }
81
82 if style.font_style.contains(syntect::highlighting::FontStyle::UNDERLINE) {
83 ratatui_style = ratatui_style.add_modifier(ratatui::style::Modifier::UNDERLINED);
84 }
85
86 ratatui_style
87 }
88
89 pub fn supported_languages(&self) -> Vec<String> {
91 self.syntax_set
92 .syntaxes()
93 .iter()
94 .map(|syntax| syntax.name.clone())
95 .collect()
96 }
97}
98
99impl Default for SyntaxHighlighter {
100 fn default() -> Self {
101 Self::new()
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn test_syntax_highlighter_creation() {
111 let highlighter = SyntaxHighlighter::new();
112 let languages = highlighter.supported_languages();
113 assert!(!languages.is_empty());
114 assert!(languages.contains(&"Rust".to_string()));
115 }
116
117 #[test]
118 fn test_basic_highlighting() {
119 let highlighter = SyntaxHighlighter::new();
120 let spans = highlighter.highlight("fn main() {}", "rust");
121 assert!(!spans.is_empty());
122 }
123}