Skip to main content

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}