use std::path::Path;
use hjkl_engine::Query;
use hjkl_tree_sitter::{DotFallbackTheme, Highlighter, LanguageRegistry, Theme};
pub const SYNTAX_WINDOW_MARGIN: usize = 200;
pub struct SyntaxLayer {
registry: LanguageRegistry,
highlighter: Option<Highlighter>,
theme: Box<dyn Theme>,
}
impl SyntaxLayer {
pub fn new(theme: Box<dyn Theme>) -> Self {
Self {
registry: LanguageRegistry::new(),
highlighter: None,
theme,
}
}
pub fn set_language_for_path(&mut self, path: &Path) -> bool {
match self.registry.detect_for_path(path) {
Some(config) => match Highlighter::new(config) {
Ok(h) => {
self.highlighter = Some(h);
true
}
Err(_) => {
self.highlighter = None;
false
}
},
None => {
self.highlighter = None;
false
}
}
}
pub fn set_theme(&mut self, theme: Box<dyn Theme>) {
self.theme = theme;
}
pub fn recompute(
&mut self,
buffer: &impl Query,
viewport_top: usize,
viewport_height: usize,
) -> Option<Vec<Vec<(usize, usize, ratatui::style::Style)>>> {
let highlighter = self.highlighter.as_mut()?;
let row_count = buffer.line_count() as usize;
let window_start = viewport_top.saturating_sub(SYNTAX_WINDOW_MARGIN);
let window_end = (viewport_top + viewport_height + SYNTAX_WINDOW_MARGIN).min(row_count);
if window_start >= window_end {
return Some(vec![Vec::new(); row_count]);
}
let slice_row_count = window_end - window_start;
let source: Vec<u8> = {
let mut s = String::new();
for i in 0..slice_row_count {
if i > 0 {
s.push('\n');
}
s.push_str(buffer.line((window_start + i) as u32));
}
s.push('\n');
s.into_bytes()
};
let flat_spans = highlighter.highlight(&source);
let mut row_starts: Vec<usize> = vec![0];
for (i, &b) in source.iter().enumerate() {
if b == b'\n' {
row_starts.push(i + 1);
}
}
let mut by_row: Vec<Vec<(usize, usize, ratatui::style::Style)>> =
vec![Vec::new(); row_count];
for span in &flat_spans {
let style = match self.theme.style(span.capture()) {
Some(s) => s.to_ratatui(),
None => continue,
};
let span_start = span.byte_range.start;
let span_end = span.byte_range.end;
let start_slice_row = row_starts
.partition_point(|&rs| rs <= span_start)
.saturating_sub(1);
let mut slice_row = start_slice_row;
while slice_row < slice_row_count {
let row_byte_start = row_starts[slice_row];
let row_byte_end = row_starts
.get(slice_row + 1)
.map(|&s| s.saturating_sub(1)) .unwrap_or(source.len());
if row_byte_start >= span_end {
break;
}
let local_start = span_start.saturating_sub(row_byte_start);
let local_end = span_end.min(row_byte_end) - row_byte_start;
if local_end > local_start {
let abs_row = window_start + slice_row;
by_row[abs_row].push((local_start, local_end, style));
}
slice_row += 1;
}
}
Some(by_row)
}
}
pub fn default_layer() -> SyntaxLayer {
SyntaxLayer::new(Box::new(DotFallbackTheme::dark()))
}