use std::collections::HashMap;
use std::path::Path;
use ratatui::style::Style;
use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter};
use crate::theme::SyntaxColors;
use super::languages::{detect_language, language_entries};
use super::theme::{highlight_names_vec, style_for_highlight};
#[derive(Debug, Clone)]
pub struct HighlightSpan {
pub start: usize,
pub end: usize,
pub style: Style,
}
pub struct HighlightEngine {
configs: HashMap<String, HighlightConfiguration>,
}
impl HighlightEngine {
pub fn new() -> Self {
let highlight_names = highlight_names_vec();
let mut configs = HashMap::new();
for entry in language_entries() {
let config = entry.config(&highlight_names);
configs.insert(entry.name.to_string(), config);
}
Self { configs }
}
pub fn highlight_lines(
&self,
path: &Path,
content: &str,
syntax: &SyntaxColors,
) -> Option<Vec<Vec<HighlightSpan>>> {
let lang_name = detect_language(path)?;
let config = self.configs.get(lang_name)?;
let mut highlighter = Highlighter::new();
let events = highlighter
.highlight(config, content.as_bytes(), None, |_| None)
.ok()?;
let lines: Vec<&str> = content.split('\n').collect();
let mut result: Vec<Vec<HighlightSpan>> = vec![Vec::new(); lines.len()];
let mut current_style = Style::default();
for event in events {
match event.ok()? {
HighlightEvent::Source { start, end } => {
add_spans_for_range(&lines, &mut result, start, end, current_style);
}
HighlightEvent::HighlightStart(highlight) => {
current_style = style_for_highlight(highlight.0, syntax);
}
HighlightEvent::HighlightEnd => {
current_style = Style::default();
}
}
}
Some(result)
}
}
fn add_spans_for_range(
lines: &[&str],
result: &mut [Vec<HighlightSpan>],
start: usize,
end: usize,
style: Style,
) {
if start >= end {
return;
}
let mut line_start_byte = 0;
for (line_idx, line) in lines.iter().enumerate() {
let line_end_byte = line_start_byte + line.len();
if start < line_end_byte + 1 && end > line_start_byte {
let span_start = start.saturating_sub(line_start_byte).min(line.len());
let span_end = (end - line_start_byte).min(line.len());
if span_start < span_end && line_idx < result.len() {
result[line_idx].push(HighlightSpan {
start: span_start,
end: span_end,
style,
});
}
}
if line_start_byte > end {
break;
}
line_start_byte = line_end_byte + 1;
}
}