syntax/session.rs
1//! Where a parse happens: one parser, and the queries compiled against it.
2//!
3//! Configs live here rather than on [`Lang`] because a wasm `Language` belongs
4//! to the `WasmStore` its parser holds — `parser.c` dispatches every lex and
5//! external-scanner call through that store — so a config compiled for one
6//! parser cannot be shared with another.
7//!
8//! An injected language's config must already be in [`Session::configs`] when
9//! the parse starts: the callback holds `configs` immutably and cannot compile
10//! into it. [`Session::ensure`] walks the `#set! injection.language` names of a
11//! query before parsing and compiles each. An injection that names its language
12//! through a captured node instead — the language is in the source text — is
13//! only painted if that language was already compiled.
14
15use crate::{
16 lang::{Grammar, Lang},
17 registry,
18};
19use std::{cell::RefCell, collections::HashMap, ops::Range};
20use theme::HighlightKind;
21use tree_sitter::Language;
22use tree_sitter_highlight::{HighlightEvent, Highlighter};
23
24/// A parser and its compiled queries.
25#[derive(Default)]
26pub struct Session {
27 highlighter: Highlighter,
28 /// By [`Lang::name`]. `None` is a query that did not compile against its
29 /// grammar, cached so it is not retried on every parse.
30 configs: HashMap<&'static str, Option<crate::lang::Compiled>>,
31}
32
33impl Session {
34 pub fn new() -> Self {
35 Self::default()
36 }
37
38 /// Compile `lang` and everything its injections name, transitively.
39 fn ensure(&mut self, lang: &'static Lang) {
40 if self.configs.contains_key(lang.name) {
41 return;
42 }
43 let compiled = self.compile(lang);
44 let injected = compiled
45 .as_ref()
46 .map(|compiled| compiled.injected.clone())
47 .unwrap_or_default();
48 // Recorded before recursing: two languages injecting each other would
49 // otherwise not terminate.
50 self.configs.insert(lang.name, compiled);
51 for name in injected {
52 if let Some(lang) = registry::of_tag(&name).and_then(|known| known.lang()) {
53 self.ensure(lang);
54 }
55 }
56 }
57
58 /// The grammar for `lang`, then its queries compiled against it.
59 fn compile(&mut self, lang: &'static Lang) -> Option<crate::lang::Compiled> {
60 let grammar = match &lang.grammar {
61 Grammar::Native(grammar) => (*grammar).into(),
62 Grammar::Wasm(bytes) => self.load_wasm(lang.name, &bytes.clone())?,
63 };
64 lang.compile_with(grammar)
65 }
66
67 /// Instantiate a wasm grammar in this session's store, making one from the
68 /// installed engine if there is not one yet.
69 ///
70 /// The store lives on the parser rather than beside it: `set_language` with
71 /// a wasm language reads the store off the parser, and the highlighter calls
72 /// it for every layer. Taking it back out is how a later language is loaded
73 /// into the same store.
74 #[cfg(feature = "wasm")]
75 fn load_wasm(&mut self, name: &str, bytes: &[u8]) -> Option<Language> {
76 use tree_sitter::WasmStore;
77
78 let mut store = match self.highlighter.parser().take_wasm_store() {
79 Some(store) => store,
80 None => WasmStore::new(crate::engine()?).ok()?,
81 };
82 let language = store.load_language(name, bytes).ok();
83 self.highlighter.parser().set_wasm_store(store).ok()?;
84 language
85 }
86
87 /// Without the `wasm` feature there is no engine to instantiate in.
88 #[cfg(not(feature = "wasm"))]
89 fn load_wasm(&mut self, _name: &str, _bytes: &[u8]) -> Option<Language> {
90 None
91 }
92
93 /// Spans over `source`, in bytes, in document order. `None` when the query
94 /// does not compile against the grammar.
95 pub fn highlight(
96 &mut self,
97 lang: &'static Lang,
98 source: &str,
99 ) -> Option<Vec<(Range<usize>, HighlightKind)>> {
100 self.ensure(lang);
101 // Split borrows: the parse holds `highlighter` mutably while the
102 // callback reads `configs`.
103 let Self {
104 highlighter,
105 configs,
106 } = self;
107 let config = &configs.get(lang.name)?.as_ref()?.config;
108 let mut spans = Vec::new();
109 // Nested highlight starts end with `HighlightEnd`; the top of the stack
110 // is the kind painting the `Source` ranges that follow it.
111 let mut kinds: Vec<HighlightKind> = Vec::new();
112 for event in highlighter
113 // `None` encoding: the source is a `&str`, so it is UTF-8 and
114 // tree-sitter's default is the one to take.
115 .highlight(config, source.as_bytes(), None, None, |name| {
116 let key = registry::of_tag(name)?.lang()?.name;
117 configs.get(key)?.as_ref().map(|compiled| &compiled.config)
118 })
119 .ok()?
120 .flatten()
121 {
122 match event {
123 HighlightEvent::HighlightStart(hl) => {
124 kinds.push(crate::lang::kind_of(
125 crate::lang::NAMES.get(hl.0).copied().unwrap_or(""),
126 ));
127 }
128 HighlightEvent::HighlightEnd => {
129 kinds.pop();
130 }
131 HighlightEvent::Source { start, end } => {
132 if let Some(&kind) = kinds.last() {
133 spans.push((start..end, kind));
134 }
135 }
136 }
137 }
138 Some(spans)
139 }
140}
141
142thread_local! {
143 /// One per thread. A `Highlighter` owns a parser and a config costs
144 /// milliseconds to compile, so neither is rebuilt per call.
145 static SESSION: RefCell<Session> = RefCell::new(Session::new());
146}
147
148/// Run `f` against this thread's session.
149pub fn with<T>(f: impl FnOnce(&mut Session) -> T) -> T {
150 SESSION.with(|session| f(&mut session.borrow_mut()))
151}