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 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 #[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 #[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 #[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) .unwrap()
250 .child(1) .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 assert_eq!(outer(3..4), Some(("identifier", 3..7)));
313 assert_eq!(outer(3..7), Some(("function_item", 0..12)));
314 assert_eq!(outer(0..13), None);
316 assert_eq!(inner(0..12), Some(("identifier", 3..7)));
318 assert_eq!(inner(3..7), None);
320 assert_eq!(inner(3..5), None);
321 assert_eq!(inner(5..8), None);
323
324 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}