use ratatui::style::Color;
use syntect::easy::HighlightLines;
use syntect::highlighting::{Theme, ThemeSet};
use syntect::parsing::{SyntaxReference, SyntaxSet};
const DEFAULT_THEME_TM: &str = include_str!("../../themes/github-dark-high-contrast.tmTheme");
pub fn default_theme() -> Theme {
ThemeSet::load_from_reader(&mut std::io::Cursor::new(DEFAULT_THEME_TM))
.expect("vendored default tmTheme must parse")
}
pub struct Highlighter {
ps: SyntaxSet,
theme: Theme,
}
impl Highlighter {
pub fn new() -> Self {
let ps = two_face::syntax::extra_no_newlines();
let theme = default_theme();
Highlighter { ps, theme }
}
pub fn syntax_for(&self, path: &str) -> &SyntaxReference {
let p = std::path::Path::new(path);
let ext = p.extension().and_then(|e| e.to_str()).unwrap_or("");
self.ps
.find_syntax_by_extension(ext)
.or_else(|| {
let name = p.file_name().and_then(|n| n.to_str()).unwrap_or("");
self.ps.find_syntax_by_token(name)
})
.unwrap_or_else(|| self.ps.find_syntax_plain_text())
}
pub fn line(&self, syntax: &SyntaxReference, text: &str) -> Vec<(Color, String)> {
let mut h = HighlightLines::new(syntax, &self.theme);
match h.highlight_line(text, &self.ps) {
Ok(ranges) => ranges
.into_iter()
.map(|(st, s)| {
let c = st.foreground;
let fg = crate::ui::theme::adapt_color(Color::Rgb(c.r, c.g, c.b));
(fg, s.to_string())
})
.collect(),
Err(_) => vec![(Color::Gray, text.to_string())],
}
}
}
impl Default for Highlighter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_theme_loads_and_highlights() {
let hl = Highlighter::new();
let syntax = hl.syntax_for("main.rs");
let runs = hl.line(syntax, "let x = 1;");
assert!(!runs.is_empty(), "highlighting produced no runs");
assert!(
runs.iter().any(|(c, _)| matches!(c, Color::Rgb(..))),
"expected an RGB foreground from the embedded theme"
);
}
}