Skip to main content

indigo_core/
syntax.rs

1use anyhow::{Context as _, anyhow};
2use camino::Utf8Path;
3use ropey::Rope;
4use std::{
5    ops::{Deref, Range},
6    str::FromStr,
7};
8use tree_sitter::{Node, Parser, Query, TextProvider, Tree};
9
10#[allow(clippy::allow_attributes, unused_imports)]
11use std::sync::OnceLock;
12
13pub struct Syntax {
14    language: Language,
15    parser: Parser,
16    tree: Tree,
17}
18
19impl Syntax {
20    #[must_use]
21    pub fn parse(language: Language, code: &Rope) -> Self {
22        let mut parser = Parser::new();
23        parser.set_language(&language.into()).unwrap();
24        let tree = parse(code, &mut parser, None);
25        Self {
26            language,
27            parser,
28            tree,
29        }
30    }
31
32    pub fn reparse(&mut self, code: &Rope) {
33        // TODO: If I want to do incremental reparsing, I need to call `tree.edit(..)`. For now I'll
34        // just parse from scratch every time.
35        // self.tree = parse(code, &mut self.parser, Some(&self.tree));
36        self.tree = parse(code, &mut self.parser, None);
37    }
38
39    #[must_use]
40    pub fn language(&self) -> Language {
41        self.language
42    }
43
44    #[must_use]
45    pub fn tree(&self) -> &Tree {
46        &self.tree
47    }
48
49    /// Nodes from the root down to the smallest node that covers `byte_range`.
50    #[must_use]
51    pub fn node_path(&self, byte_range: Range<usize>) -> Vec<Node<'_>> {
52        let leaf = self
53            .tree
54            .root_node()
55            .descendant_for_byte_range(byte_range.start, byte_range.end);
56        let mut path = Vec::new();
57        let mut node = leaf;
58        while let Some(current) = node {
59            path.push(current);
60            node = current.parent();
61        }
62        path.reverse();
63        path
64    }
65
66    /// First named node, in document order, whose byte range lies strictly inside `byte_range`.
67    #[must_use]
68    pub fn inner_node(&self, byte_range: Range<usize>) -> Option<Node<'_>> {
69        let mut node = self
70            .tree
71            .root_node()
72            .descendant_for_byte_range(byte_range.start, byte_range.end)?;
73        loop {
74            let mut cursor = node.walk();
75            let child = node.named_children(&mut cursor).find(|child| {
76                !child.byte_range().is_empty()
77                    && byte_range.start <= child.start_byte()
78                    && child.end_byte() <= byte_range.end
79            })?;
80            if child.byte_range() != byte_range {
81                return Some(child);
82            }
83            node = child;
84        }
85    }
86
87    /// Smallest node whose byte range strictly contains `byte_range`.
88    ///
89    /// The root node starts after any leading whitespace, so a range that begins in that
90    /// whitespace has no outer node.
91    #[must_use]
92    pub fn outer_node(&self, byte_range: Range<usize>) -> Option<Node<'_>> {
93        let mut node = self
94            .tree
95            .root_node()
96            .descendant_for_byte_range(byte_range.start, byte_range.end)?;
97        while !strictly_contains(&node.byte_range(), &byte_range) {
98            node = node.parent()?;
99        }
100        Some(node)
101    }
102}
103
104fn strictly_contains(outer: &Range<usize>, inner: &Range<usize>) -> bool {
105    outer.start <= inner.start && inner.end <= outer.end && outer != inner
106}
107
108impl Deref for Syntax {
109    type Target = Tree;
110    fn deref(&self) -> &Self::Target {
111        &self.tree
112    }
113}
114
115impl Clone for Syntax {
116    fn clone(&self) -> Self {
117        let mut parser = Parser::new();
118        parser.set_language(&self.language.into()).unwrap();
119        Self {
120            language: self.language,
121            parser,
122            tree: self.tree.clone(),
123        }
124    }
125}
126
127#[derive(Clone, Copy, Debug)]
128pub enum Language {
129    #[cfg(feature = "language-rust")]
130    Rust,
131}
132
133impl Language {
134    #[must_use]
135    pub fn highlights_query(&self) -> &'static Query {
136        match *self {
137            #[cfg(feature = "language-rust")]
138            Self::Rust => {
139                static QUERY: OnceLock<Query> = OnceLock::new();
140                QUERY.get_or_init(|| {
141                    Query::new(&(*self).into(), tree_sitter_rust::HIGHLIGHTS_QUERY).unwrap()
142                })
143            }
144        }
145    }
146
147    #[must_use]
148    pub fn injections_query(&self) -> &'static Query {
149        match *self {
150            #[cfg(feature = "language-rust")]
151            Self::Rust => {
152                static QUERY: OnceLock<Query> = OnceLock::new();
153                QUERY.get_or_init(|| {
154                    Query::new(&(*self).into(), tree_sitter_rust::INJECTIONS_QUERY).unwrap()
155                })
156            }
157        }
158    }
159
160    #[must_use]
161    pub fn tags_query(&self) -> &'static Query {
162        match *self {
163            #[cfg(feature = "language-rust")]
164            Self::Rust => {
165                static QUERY: OnceLock<Query> = OnceLock::new();
166                QUERY.get_or_init(|| {
167                    Query::new(&(*self).into(), tree_sitter_rust::TAGS_QUERY).unwrap()
168                })
169            }
170        }
171    }
172}
173
174impl TryFrom<&Utf8Path> for Language {
175    type Error = anyhow::Error;
176    fn try_from(path: &Utf8Path) -> Result<Self, Self::Error> {
177        let extension_str = path.extension().context("path is missing extension")?;
178        match extension_str {
179            #[cfg(feature = "language-rust")]
180            "rs" => Ok(Self::Rust),
181            _ => Err(anyhow!("could not infer language from path")),
182        }
183    }
184}
185
186impl FromStr for Language {
187    type Err = anyhow::Error;
188    fn from_str(name: &str) -> Result<Self, Self::Err> {
189        match name {
190            #[cfg(feature = "language-rust")]
191            "rust" => Ok(Self::Rust),
192            _ => Err(anyhow!("unknown language `{name}`")),
193        }
194    }
195}
196
197impl From<Language> for tree_sitter::Language {
198    fn from(language: Language) -> Self {
199        match language {
200            #[cfg(feature = "language-rust")]
201            Language::Rust => tree_sitter_rust::LANGUAGE.into(),
202        }
203    }
204}
205
206fn parse(rope: &Rope, parser: &mut Parser, old_tree: Option<&Tree>) -> Tree {
207    parser
208        .parse_with_options(
209            &mut |offset, _point| -> &[u8] {
210                if offset >= rope.len() {
211                    return &[];
212                }
213                let (chunk, chunk_start) = rope.chunk(offset);
214                &chunk.as_bytes()[offset - chunk_start..]
215            },
216            old_tree,
217            None,
218        )
219        .expect("parser has a language set")
220}
221
222pub struct RopeTextProvider<'a>(pub ropey::RopeSlice<'a>);
223
224impl<'a> TextProvider<&'a [u8]> for RopeTextProvider<'a> {
225    type I = std::iter::Map<ropey::iter::Chunks<'a>, fn(&'a str) -> &'a [u8]>;
226    fn text(&mut self, node: Node) -> Self::I {
227        self.0.slice(node.byte_range()).chunks().map(str::as_bytes)
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    #[allow(clippy::allow_attributes, unused_imports)]
234    use super::*;
235
236    #[test]
237    #[cfg_attr(
238        not(feature = "language-rust"),
239        ignore = "requires 'language-rust' feature"
240    )]
241    fn test_rust() {
242        #[cfg(feature = "language-rust")]
243        {
244            let rope = Rope::from("fn main() {}");
245            let syntax = Syntax::parse(Language::Rust, &rope);
246            let main_range = syntax
247                .root_node()
248                .child(0) // entire file
249                .unwrap()
250                .child(1) // `main`
251                .unwrap()
252                .range();
253            assert_eq!(
254                &rope
255                    .slice(main_range.start_byte..main_range.end_byte)
256                    .to_string(),
257                "main"
258            );
259        }
260        #[cfg(not(feature = "language-rust"))]
261        panic!("requires 'language-rust' feature");
262    }
263
264    #[test]
265    #[cfg_attr(
266        not(feature = "language-rust"),
267        ignore = "requires 'language-rust' feature"
268    )]
269    fn test_node_path() {
270        #[cfg(feature = "language-rust")]
271        {
272            let rope = Rope::from("fn main() {}");
273            let syntax = Syntax::parse(Language::Rust, &rope);
274            let kinds = syntax
275                .node_path(3..4)
276                .iter()
277                .map(Node::kind)
278                .collect::<Vec<_>>();
279            assert_eq!(kinds, ["source_file", "function_item", "identifier"]);
280            let kinds = syntax
281                .node_path(0..2)
282                .iter()
283                .map(Node::kind)
284                .collect::<Vec<_>>();
285            assert_eq!(kinds, ["source_file", "function_item", "fn"]);
286        }
287        #[cfg(not(feature = "language-rust"))]
288        panic!("requires 'language-rust' feature");
289    }
290
291    #[test]
292    #[cfg_attr(
293        not(feature = "language-rust"),
294        ignore = "requires 'language-rust' feature"
295    )]
296    fn test_outer_inner_node() {
297        #[cfg(feature = "language-rust")]
298        {
299            let rope = Rope::from("fn main() {}\n");
300            let syntax = Syntax::parse(Language::Rust, &rope);
301            let outer = |range| {
302                syntax
303                    .outer_node(range)
304                    .map(|node| (node.kind(), node.byte_range()))
305            };
306            let inner = |range| {
307                syntax
308                    .inner_node(range)
309                    .map(|node| (node.kind(), node.byte_range()))
310            };
311            // `m` grows to `main`, which grows to the whole item.
312            assert_eq!(outer(3..4), Some(("identifier", 3..7)));
313            assert_eq!(outer(3..7), Some(("function_item", 0..12)));
314            // The root has nothing above it.
315            assert_eq!(outer(0..13), None);
316            // The item shrinks to its first named child.
317            assert_eq!(inner(0..12), Some(("identifier", 3..7)));
318            // Leaves and partial slices of leaves have nothing inside them.
319            assert_eq!(inner(3..7), None);
320            assert_eq!(inner(3..5), None);
321            // A selection covering no complete named node stays put.
322            assert_eq!(inner(5..8), None);
323
324            // The root node starts after leading whitespace.
325            let rope = Rope::from("\nfn main() {}\n");
326            let syntax = Syntax::parse(Language::Rust, &rope);
327            assert_eq!(syntax.outer_node(0..1), None);
328            assert_eq!(
329                syntax
330                    .outer_node(1..3)
331                    .map(|node| (node.kind(), node.byte_range())),
332                Some(("function_item", 1..13))
333            );
334        }
335        #[cfg(not(feature = "language-rust"))]
336        panic!("requires 'language-rust' feature");
337    }
338}