use std::path::Path;
use std::sync::Arc;
use hjkl_bonsai::{DotFallbackTheme, Theme};
use hjkl_buffer_tui::Sign;
use hjkl_engine::Query;
use hjkl_syntax_tui::render_output_ref_to_tui;
use hjkl_lang::LanguageDirectory;
pub use hjkl_syntax::{BufferId, LoadEvent, LoadEventKind, SetLanguageOutcome};
#[derive(Debug, Clone)]
pub struct RenderOutput {
pub spans: Vec<Vec<(usize, usize, ratatui::style::Style)>>,
pub signs: Vec<Sign>,
pub key: (u64, usize, usize),
}
impl PartialEq for RenderOutput {
fn eq(&self, other: &Self) -> bool {
self.spans == other.spans
&& self.signs.len() == other.signs.len()
&& self
.signs
.iter()
.zip(other.signs.iter())
.all(|(a, b)| a.row == b.row && a.ch == b.ch && a.priority == b.priority)
}
}
fn convert_output(raw: &hjkl_syntax::RenderOutputRef<'_>) -> RenderOutput {
let (spans, signs) = render_output_ref_to_tui(raw);
RenderOutput {
spans,
signs,
key: raw.key,
}
}
pub struct SyntaxLayer {
inner: hjkl_syntax::SyntaxLayer,
}
impl SyntaxLayer {
pub fn new(theme: Arc<dyn Theme + Send + Sync>, directory: Arc<LanguageDirectory>) -> Self {
Self {
inner: hjkl_syntax::SyntaxLayer::new(theme, directory),
}
}
pub fn set_language_for_path(&mut self, id: BufferId, path: &Path) -> SetLanguageOutcome {
self.inner.set_language_for_path(id, path)
}
pub fn language_name_for_path(&self, path: &Path) -> Option<String> {
self.inner.directory().name_for_path(path)
}
pub fn poll_pending_loads(&mut self) -> Vec<LoadEvent> {
self.inner.poll_pending_loads()
}
pub fn forget(&mut self, id: BufferId) {
self.inner.forget(id);
}
pub fn set_theme(&mut self, theme: Arc<dyn Theme + Send + Sync>) {
self.inner.set_theme(theme);
}
pub fn set_colorizer(&mut self, enabled: bool, filetypes: Vec<String>) {
self.inner.set_colorizer(enabled, filetypes);
}
pub fn set_rainbow_brackets(&mut self, enabled: bool) {
self.inner.set_rainbow_brackets(enabled);
}
pub fn reset(&mut self, id: BufferId) {
self.inner.reset(id);
}
pub fn apply_edits(&mut self, id: BufferId, edits: &[hjkl_engine::ContentEdit]) {
self.inner.apply_edits(id, edits);
}
pub fn extract_fold_ranges(
&mut self,
id: BufferId,
buffer: &impl hjkl_engine::Query,
) -> Option<Vec<(usize, usize)>> {
self.inner.extract_fold_ranges(id, buffer)
}
pub fn render_viewport(
&mut self,
id: BufferId,
buffer: &impl Query,
viewport_top: usize,
viewport_height: usize,
) -> Option<RenderOutput> {
let raw = self
.inner
.render_viewport_ref(id, buffer, viewport_top, viewport_height)?;
Some(convert_output(&raw))
}
}
pub fn layer_with_theme(
theme: Arc<DotFallbackTheme>,
directory: Arc<LanguageDirectory>,
) -> SyntaxLayer {
SyntaxLayer::new(theme, directory)
}
#[cfg(test)]
pub fn default_layer() -> SyntaxLayer {
let directory = Arc::new(LanguageDirectory::new().expect("language directory"));
SyntaxLayer::new(Arc::new(DotFallbackTheme::dark()), directory)
}
#[cfg(test)]
mod tests {
use super::*;
use hjkl_buffer::View;
use std::path::Path;
const TID: BufferId = 0;
#[test]
fn render_output_eq_ignores_sign_style() {
let mk = |style: ratatui::style::Style| RenderOutput {
spans: vec![vec![(0, 2, ratatui::style::Style::default())]],
signs: vec![Sign {
row: 1,
ch: 'E',
style,
priority: 100,
}],
key: (7, 0, 30),
};
let plain = mk(ratatui::style::Style::default());
let styled = mk(ratatui::style::Style::default().fg(ratatui::style::Color::Red));
assert_eq!(plain, styled, "sign style must not affect equality");
let mut differing = plain.clone();
differing.signs[0].ch = 'W';
assert_ne!(plain, differing, "sign `ch` must affect equality");
}
#[test]
#[ignore = "network + compiler: needs tree-sitter-rust grammar"]
fn render_viewport_converts_spans_to_ratatui() {
let buf = View::from_str("fn main() { let x = 1; }\n");
let mut layer = default_layer();
assert!(
layer
.set_language_for_path(TID, Path::new("a.rs"))
.is_known()
);
let out = layer
.render_viewport(TID, &buf, 0, 10)
.expect("render output");
assert!(
out.spans.iter().any(|r| !r.is_empty()),
"expected at least one styled span"
);
}
#[test]
#[ignore = "network + compiler: needs tree-sitter-rust grammar"]
fn render_viewport_converts_diag_signs_to_tui_signs() {
let buf = View::from_str("fn main() {\nlet x = ;\n}\n");
let mut layer = default_layer();
layer.set_language_for_path(TID, Path::new("a.rs"));
let out = layer.render_viewport(TID, &buf, 0, 10).unwrap();
assert!(
!out.signs.is_empty(),
"expected at least one diagnostic sign for `let x = ;`"
);
assert!(
out.signs.iter().any(|s| s.row == 1 && s.ch == 'E'),
"expected an 'E' sign on row 1; got {:?}",
out.signs
);
}
}