1#![forbid(unsafe_code)]
29
30use std::collections::HashMap;
31use std::sync::Arc;
32
33use hikari_core::{
34 HighlightSpan as HlSpan, Highlighter, HlClass, Language, LanguagePlugin, Selector, SpanSink,
35};
36use serde::{Deserialize, Serialize};
37use thiserror::Error;
38use tree_sitter::{
39 InputEdit, Language as TsLanguage, Parser, Point, Tree,
40};
41use tree_sitter_highlight::{HighlightConfiguration, HighlightEvent, Highlighter as TsHighlighter};
42
43pub use hikari_token::Semantic;
47
48#[derive(Debug, Error)]
51pub enum TsError {
52 #[error("grammar not registered: {0}")]
53 Unknown(String),
54 #[error("tree-sitter: {0}")]
55 Ts(String),
56}
57
58pub type Result<T> = std::result::Result<T, TsError>;
59
60pub struct Grammar {
64 pub name: String,
65 pub language: TsLanguage,
66 pub config: HighlightConfiguration,
67 pub extensions: Vec<String>,
71}
72
73pub struct GrammarRegistry {
75 grammars: HashMap<String, Grammar>,
76 pub highlight_names: Vec<&'static str>,
79}
80
81impl GrammarRegistry {
82 pub fn builtin() -> Result<Self> {
88 let highlight_names = canonical_highlight_names();
89 let mut reg = Self {
90 grammars: HashMap::new(),
91 highlight_names,
92 };
93 reg.register(
94 "rust",
95 &tree_sitter_rust::language(),
96 tree_sitter_rust::HIGHLIGHTS_QUERY,
97 tree_sitter_rust::INJECTIONS_QUERY,
98 &["rs"],
99 )?;
100 reg.register(
101 "python",
102 &tree_sitter_python::language(),
103 tree_sitter_python::HIGHLIGHTS_QUERY,
104 "",
105 &["py", "pyi"],
106 )?;
107 reg.register(
108 "json",
109 &tree_sitter_json::language(),
110 tree_sitter_json::HIGHLIGHTS_QUERY,
111 "",
112 &["json"],
113 )?;
114 reg.register(
115 "bash",
116 &tree_sitter_bash::language(),
117 tree_sitter_bash::HIGHLIGHT_QUERY,
118 "",
119 &["sh", "bash", "zsh"],
120 )?;
121 Ok(reg)
122 }
123
124 fn register(
131 &mut self,
132 name: &str,
133 language: &TsLanguage,
134 highlights: &str,
135 injections: &str,
136 extensions: &[&str],
137 ) -> Result<()> {
138 let mut cfg =
139 HighlightConfiguration::new(language.clone(), name, highlights, injections, "")
140 .map_err(|e| TsError::Ts(format!("{name}: {e}")))?;
141 cfg.configure(&self.highlight_names);
142 self.grammars.insert(
143 name.to_string(),
144 Grammar {
145 name: name.to_string(),
146 language: language.clone(),
147 config: cfg,
148 extensions: extensions.iter().map(|s| (*s).to_string()).collect(),
149 },
150 );
151 Ok(())
152 }
153
154 #[must_use]
155 pub fn get(&self, language: &str) -> Option<&Grammar> {
156 self.grammars.get(language)
157 }
158
159 #[must_use]
161 pub fn from_extension(&self, ext: &str) -> Option<&Grammar> {
162 self.grammars
163 .values()
164 .find(|g| g.extensions.iter().any(|e| e == ext))
165 }
166
167 pub fn add_extension(&mut self, language: &str, ext: impl Into<String>) -> bool {
170 if let Some(g) = self.grammars.get_mut(language) {
171 let ext = ext.into();
172 if !g.extensions.iter().any(|e| *e == ext) {
173 g.extensions.push(ext);
174 }
175 true
176 } else {
177 false
178 }
179 }
180
181 pub fn languages(&self) -> impl Iterator<Item = &str> {
183 self.grammars.keys().map(String::as_str)
184 }
185}
186
187pub struct BufferParser {
191 language: String,
192 parser: Parser,
193 tree: Option<Tree>,
194}
195
196impl BufferParser {
197 pub fn new(language: &str, registry: &GrammarRegistry) -> Result<Self> {
202 let grammar = registry
203 .get(language)
204 .ok_or_else(|| TsError::Unknown(language.to_string()))?;
205 let mut parser = Parser::new();
206 parser
207 .set_language(&grammar.language)
208 .map_err(|e| TsError::Ts(e.to_string()))?;
209 Ok(Self {
210 language: language.to_string(),
211 parser,
212 tree: None,
213 })
214 }
215
216 #[must_use]
217 pub fn language(&self) -> &str {
218 &self.language
219 }
220
221 pub fn reparse(&mut self, src: &str) -> Result<()> {
233 self.tree = self.parser.parse(src, None);
234 Ok(())
235 }
236
237 pub fn reparse_edit(
247 &mut self,
248 old_src: &str,
249 new_src: &str,
250 start_byte: usize,
251 old_end_byte: usize,
252 ) -> Result<()> {
253 if self.tree.is_some() {
254 let edit = TsEdit::from_splice(old_src, new_src, start_byte, old_end_byte);
255 if let Some(tree) = self.tree.as_mut() {
256 tree.edit(&edit.to_input_edit());
257 }
258 self.tree = self.parser.parse(new_src, self.tree.as_ref());
259 } else {
260 self.tree = self.parser.parse(new_src, None);
261 }
262 Ok(())
263 }
264
265 #[must_use]
266 pub fn tree(&self) -> Option<&Tree> {
267 self.tree.as_ref()
268 }
269}
270
271#[derive(Debug, Clone, Copy, PartialEq, Eq)]
276pub struct TsEdit {
277 pub start_byte: usize,
278 pub old_end_byte: usize,
279 pub new_end_byte: usize,
280 pub start_point: (usize, usize),
282 pub old_end_point: (usize, usize),
283 pub new_end_point: (usize, usize),
284}
285
286impl TsEdit {
287 #[must_use]
291 pub fn from_splice(old: &str, new: &str, start_byte: usize, old_end_byte: usize) -> Self {
292 let new_end_byte = new.len() - (old.len() - old_end_byte);
293 Self {
294 start_byte,
295 old_end_byte,
296 new_end_byte,
297 start_point: byte_to_point(old, start_byte),
298 old_end_point: byte_to_point(old, old_end_byte),
299 new_end_point: byte_to_point(new, new_end_byte),
300 }
301 }
302
303 fn to_input_edit(self) -> InputEdit {
304 let pt = |(row, column): (usize, usize)| Point { row, column };
305 InputEdit {
306 start_byte: self.start_byte,
307 old_end_byte: self.old_end_byte,
308 new_end_byte: self.new_end_byte,
309 start_position: pt(self.start_point),
310 old_end_position: pt(self.old_end_point),
311 new_end_position: pt(self.new_end_point),
312 }
313 }
314}
315
316#[must_use]
319fn byte_to_point(text: &str, byte: usize) -> (usize, usize) {
320 let byte = byte.min(text.len());
321 let prefix = &text[..byte];
322 let row = prefix.bytes().filter(|&b| b == b'\n').count();
323 let col = prefix.len() - prefix.rfind('\n').map_or(0, |i| i + 1);
324 (row, col)
325}
326
327#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
331pub struct HighlightSpan {
332 pub start: usize,
333 pub end: usize,
334 pub semantic: Semantic,
335}
336
337pub fn highlight(
342 src: &str,
343 grammar: &Grammar,
344 registry: &GrammarRegistry,
345) -> Result<Vec<HighlightSpan>> {
346 let mut highlighter = TsHighlighter::new();
347 let events = highlighter
348 .highlight(&grammar.config, src.as_bytes(), None, |_| None)
349 .map_err(|e| TsError::Ts(e.to_string()))?;
350
351 let mut stack: Vec<usize> = Vec::new();
352 let mut spans: Vec<HighlightSpan> = Vec::new();
353 let mut run_start: Option<(usize, usize)> = None;
354
355 for ev in events {
356 let ev = ev.map_err(|e| TsError::Ts(e.to_string()))?;
357 match ev {
358 HighlightEvent::HighlightStart(h) => stack.push(h.0),
359 HighlightEvent::HighlightEnd => {
360 stack.pop();
361 run_start = None;
362 }
363 HighlightEvent::Source { start, end } => {
364 if let Some(&top) = stack.last() {
365 let sem = highlight_index_to_semantic(top, ®istry.highlight_names);
366 match run_start {
367 Some((rs, _)) if rs == start => {}
368 _ => {
369 spans.push(HighlightSpan {
370 start,
371 end,
372 semantic: sem,
373 });
374 run_start = Some((start, end));
375 }
376 }
377 }
378 }
379 }
380 }
381
382 Ok(spans)
383}
384
385fn canonical_highlight_names() -> Vec<&'static str> {
388 vec![
389 "keyword",
390 "function",
391 "function.call",
392 "function.method",
393 "type",
394 "type.builtin",
395 "constant",
396 "constant.builtin",
397 "string",
398 "string.special",
399 "number",
400 "boolean",
401 "comment",
402 "operator",
403 "punctuation",
404 "punctuation.bracket",
405 "punctuation.delimiter",
406 "variable",
407 "variable.parameter",
408 "variable.builtin",
409 "attribute",
410 "label",
411 "tag",
412 ]
413}
414
415fn highlight_index_to_semantic(index: usize, names: &[&'static str]) -> Semantic {
416 let name = names.get(index).copied().unwrap_or("");
417 match name {
418 n if n.starts_with("keyword") => Semantic::Keyword,
419 n if n.starts_with("function") => Semantic::Symbol,
420 n if n.starts_with("type") => Semantic::Accent,
421 n if n.starts_with("constant.builtin") || n == "boolean" => Semantic::Literal,
422 n if n.starts_with("constant") => Semantic::Literal,
423 n if n.starts_with("string") => Semantic::String,
424 n if n == "number" => Semantic::Number,
425 n if n.starts_with("comment") => Semantic::Comment,
426 n if n.starts_with("operator") => Semantic::Accent,
427 n if n.starts_with("punctuation") => Semantic::Muted,
428 n if n.starts_with("variable") => Semantic::Symbol,
429 n if n == "attribute" => Semantic::Hint,
430 n if n == "label" => Semantic::Hint,
431 n if n == "tag" => Semantic::Keyword,
432 _ => Semantic::Symbol,
433 }
434}
435
436#[derive(Clone)]
442pub struct TreeSitterHost {
443 registry: Arc<GrammarRegistry>,
444}
445
446impl TreeSitterHost {
447 pub fn builtin() -> Result<Self> {
452 Ok(Self {
453 registry: Arc::new(GrammarRegistry::builtin()?),
454 })
455 }
456
457 pub fn languages(&self) -> impl Iterator<Item = &str> {
459 self.registry.languages()
460 }
461
462 #[must_use]
465 pub fn plugins(&self) -> Vec<Box<dyn LanguagePlugin>> {
466 let mut out: Vec<Box<dyn LanguagePlugin>> = Vec::new();
467 for name in self.registry.languages() {
468 let lang: &'static str = Box::leak(name.to_string().into_boxed_str());
471 let selectors: Vec<Selector> = self
472 .registry
473 .get(name)
474 .map(|g| {
475 g.extensions
476 .iter()
477 .map(|e| Selector::Extension(Box::leak(e.clone().into_boxed_str())))
478 .collect()
479 })
480 .unwrap_or_default();
481 out.push(Box::new(TreeSitterPlugin {
482 language: Language(lang),
483 selectors: selectors.leak(),
484 registry: self.registry.clone(),
485 grammar: lang,
486 }));
487 }
488 out
489 }
490
491 #[must_use]
493 pub fn highlighter(&self, grammar: &'static str) -> TreeSitterHighlighter {
494 TreeSitterHighlighter {
495 registry: self.registry.clone(),
496 grammar,
497 }
498 }
499}
500
501pub struct TreeSitterPlugin {
503 language: Language,
504 selectors: &'static [Selector],
505 registry: Arc<GrammarRegistry>,
506 grammar: &'static str,
507}
508
509impl LanguagePlugin for TreeSitterPlugin {
510 fn language(&self) -> Language {
511 self.language
512 }
513 fn selectors(&self) -> &'static [Selector] {
514 self.selectors
515 }
516 fn make_highlighter(&self) -> Box<dyn Highlighter> {
517 Box::new(TreeSitterHighlighter {
518 registry: self.registry.clone(),
519 grammar: self.grammar,
520 })
521 }
522}
523
524pub struct TreeSitterHighlighter {
528 registry: Arc<GrammarRegistry>,
529 grammar: &'static str,
530}
531
532impl Highlighter for TreeSitterHighlighter {
533 fn highlight(&self, text: &str) -> Vec<HlSpan> {
534 let len = u32::try_from(text.len()).unwrap_or(u32::MAX);
535 let mut sink = SpanSink::for_document(len);
536 if let Some(grammar) = self.registry.get(self.grammar)
537 && let Ok(spans) = highlight(text, grammar, &self.registry)
538 {
539 for s in spans {
540 let class: HlClass = s.semantic.into();
542 sink.push(
543 u32::try_from(s.start).unwrap_or(u32::MAX),
544 u32::try_from(s.end).unwrap_or(u32::MAX),
545 class,
546 );
547 }
548 }
549 sink.finish()
550 }
551}
552
553#[cfg(test)]
554mod tests {
555 use super::{BufferParser, GrammarRegistry, TreeSitterHost, TsEdit, byte_to_point};
556 use hikari_core::{Highlighter, HlClass};
557
558 #[test]
559 fn builtin_host_highlights_rust_with_coverage() {
560 let host = TreeSitterHost::builtin().expect("builtin grammars");
561 let hl = host.highlighter("rust");
562 let src = "fn main() {\n let x = 42;\n}\n";
563 let spans = hl.highlight(src);
564 let mut cursor = 0u32;
566 for s in &spans {
567 assert_eq!(s.span.start, cursor, "gap/overlap at {cursor}");
568 cursor = s.span.end;
569 }
570 assert_eq!(cursor as usize, src.len(), "partition must cover the text");
571 assert!(
572 spans.iter().any(|s| s.class != HlClass::Plain),
573 "tree-sitter should classify something in real Rust",
574 );
575 }
576
577 #[test]
578 fn rust_grammar_is_registered() {
579 let host = TreeSitterHost::builtin().expect("builtin grammars");
580 assert!(host.languages().any(|l| l == "rust"));
581 assert!(!host.plugins().is_empty());
582 }
583
584 #[test]
585 fn byte_to_point_counts_rows_and_byte_columns() {
586 assert_eq!(byte_to_point("abc", 2), (0, 2));
587 assert_eq!(byte_to_point("ab\ncd", 3), (1, 0));
588 assert_eq!(byte_to_point("x\ny\nz", 4), (2, 0));
589 }
590
591 #[test]
593 fn incremental_reparse_equals_full_parse() {
594 let r = GrammarRegistry::builtin().unwrap();
595 let old = "fn main() { let x = 1; }";
596 let new = "fn main() { let x = 42; }";
597 let start = old.find('1').unwrap();
598
599 let mut inc = BufferParser::new("rust", &r).unwrap();
600 inc.reparse(old).unwrap();
601 inc.reparse_edit(old, new, start, start + 1).unwrap();
602
603 let mut full = BufferParser::new("rust", &r).unwrap();
604 full.reparse(new).unwrap();
605
606 assert_eq!(
607 inc.tree().unwrap().root_node().to_sexp(),
608 full.tree().unwrap().root_node().to_sexp(),
609 );
610 }
611
612 #[test]
613 fn ts_edit_new_end_byte_accounts_for_length_delta() {
614 let old = "x = 1;";
615 let new = "x = 42;";
616 let start = old.find('1').unwrap();
617 let e = TsEdit::from_splice(old, new, start, start + 1);
618 assert_eq!(e.new_end_byte, start + 2);
619 }
620}