use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use syntect::easy::HighlightLines;
use syntect::highlighting::{FontStyle, Style as SyntectStyle, ThemeSet};
use syntect::parsing::SyntaxSet;
pub struct Highlighter {
syntax_set: SyntaxSet,
theme: syntect::highlighting::Theme,
}
impl Highlighter {
pub fn new() -> Self {
let syntax_set = SyntaxSet::load_defaults_newlines();
let theme_set = ThemeSet::load_defaults();
let theme = theme_set.themes["base16-ocean.dark"].clone();
Self { syntax_set, theme }
}
pub fn highlight_code(&self, code: &str, language: &str) -> Vec<Line<'static>> {
let syntax = self
.syntax_set
.find_syntax_by_token(language)
.unwrap_or_else(|| self.syntax_set.find_syntax_plain_text());
let mut h = HighlightLines::new(syntax, &self.theme);
let mut lines = Vec::new();
for line_str in code.lines() {
match h.highlight_line(line_str, &self.syntax_set) {
Ok(ranges) => {
let spans: Vec<Span<'static>> = ranges
.iter()
.map(|(style, text)| {
Span::styled((*text).to_string(), syntect_to_ratatui(style))
})
.collect();
lines.push(Line::from(spans));
}
Err(_) => {
lines.push(Line::raw(line_str.to_string()));
}
}
}
lines
}
}
impl Default for Highlighter {
fn default() -> Self {
Self::new()
}
}
fn syntect_to_ratatui(style: &SyntectStyle) -> Style {
let fg = Color::Rgb(style.foreground.r, style.foreground.g, style.foreground.b);
let mut ratatui_style = Style::default().fg(fg);
if style.font_style.contains(FontStyle::BOLD) {
ratatui_style = ratatui_style.add_modifier(Modifier::BOLD);
}
if style.font_style.contains(FontStyle::ITALIC) {
ratatui_style = ratatui_style.add_modifier(Modifier::ITALIC);
}
if style.font_style.contains(FontStyle::UNDERLINE) {
ratatui_style = ratatui_style.add_modifier(Modifier::UNDERLINED);
}
ratatui_style
}