taplo_lsp/
query.rs

1//! Cursor queries of a TOML document.
2
3use taplo::{
4    dom::{
5        node::{DomNode, Key},
6        FromSyntax, KeyOrIndex, Keys, Node,
7    },
8    rowan::{Direction, TextRange, TextSize},
9    syntax::{SyntaxKind::*, SyntaxNode, SyntaxToken},
10    util::join_ranges,
11};
12
13#[derive(Debug, Default)]
14pub struct Query {
15    /// The offset the query was made for.
16    pub offset: TextSize,
17    /// Before the cursor.
18    pub before: Option<PositionInfo>,
19    /// After the cursor.
20    pub after: Option<PositionInfo>,
21}
22
23impl Query {
24    /// Query a DOM root with the given cursor offset.
25    /// Returns [`None`] if the position is out of range.
26    ///
27    /// # Panics
28    ///
29    /// Panics if the DOM was not entirely constructed from a syntax tree (e.g. if a node has no associated syntax element).
30    /// Also panics if the given DOM node is not root.
31    ///
32    /// Also the given offset must be within the tree.
33    #[must_use]
34    pub fn at(root: &Node, offset: TextSize) -> Self {
35        let syntax = root.syntax().cloned().unwrap().into_node().unwrap();
36
37        Query {
38            offset,
39            before: offset
40                .checked_sub(TextSize::from(1))
41                .and_then(|offset| Self::position_info_at(root, &syntax, offset)),
42            after: if offset >= syntax.text_range().end() {
43                None
44            } else {
45                Self::position_info_at(root, &syntax, offset)
46            },
47        }
48    }
49
50    fn position_info_at(
51        root: &Node,
52        syntax: &SyntaxNode,
53        offset: TextSize,
54    ) -> Option<PositionInfo> {
55        let syntax = match syntax.token_at_offset(offset) {
56            taplo::rowan::TokenAtOffset::None => return None,
57            taplo::rowan::TokenAtOffset::Single(s) => s,
58            taplo::rowan::TokenAtOffset::Between(_, right) => right,
59        };
60
61        Some(PositionInfo {
62            syntax,
63            dom_node: root
64                .flat_iter()
65                .filter(|(k, n)| full_range(k, n).contains(offset))
66                .max_by_key(|(k, _)| k.len()),
67        })
68    }
69}
70
71impl Query {
72    #[must_use]
73    pub fn in_table_header(&self) -> bool {
74        match (&self.before, &self.after) {
75            (Some(before), Some(after)) => {
76                let Some(header_syntax) = before
77                    .syntax
78                    .parent_ancestors()
79                    .find(|s| s.kind() == TABLE_HEADER)
80                else {
81                    return false;
82                };
83
84                if !after.syntax.parent_ancestors().any(|a| a == header_syntax) {
85                    return false;
86                }
87
88                let Some(bracket_start) = header_syntax.children_with_tokens().find_map(|t| {
89                    if t.kind() == BRACKET_START {
90                        t.into_token()
91                    } else {
92                        None
93                    }
94                }) else {
95                    return false;
96                };
97
98                let Some(bracket_end) = header_syntax.children_with_tokens().find_map(|t| {
99                    if t.kind() == BRACKET_END {
100                        t.into_token()
101                    } else {
102                        None
103                    }
104                }) else {
105                    return false;
106                };
107
108                (before.syntax == bracket_start
109                    || before.syntax.text_range().start() >= bracket_start.text_range().end())
110                    && (after.syntax == bracket_end
111                        || after.syntax.text_range().end() <= bracket_end.text_range().start())
112            }
113            _ => false,
114        }
115    }
116
117    #[must_use]
118    pub fn in_table_array_header(&self) -> bool {
119        match (&self.before, &self.after) {
120            (Some(before), Some(after)) => {
121                let Some(header_syntax) = before
122                    .syntax
123                    .parent_ancestors()
124                    .find(|s| s.kind() == TABLE_ARRAY_HEADER)
125                else {
126                    return false;
127                };
128
129                if !after.syntax.parent_ancestors().any(|a| a == header_syntax) {
130                    return false;
131                }
132
133                let Some(bracket_start) = header_syntax
134                    .children_with_tokens()
135                    .filter_map(|t| {
136                        if t.kind() == BRACKET_START {
137                            t.into_token()
138                        } else {
139                            None
140                        }
141                    })
142                    .nth(1)
143                else {
144                    return false;
145                };
146
147                let Some(bracket_end) = header_syntax.children_with_tokens().find_map(|t| {
148                    if t.kind() == BRACKET_END {
149                        t.into_token()
150                    } else {
151                        None
152                    }
153                }) else {
154                    return false;
155                };
156
157                (before.syntax == bracket_start
158                    || before.syntax.text_range().start() >= bracket_start.text_range().end())
159                    && (after.syntax == bracket_end
160                        || after.syntax.text_range().end() <= bracket_end.text_range().start())
161            }
162            _ => false,
163        }
164    }
165
166    #[must_use]
167    pub fn header_key(&self) -> Option<SyntaxNode> {
168        match (&self.before, &self.after) {
169            (Some(before), _) => {
170                let header_syntax = before
171                    .syntax
172                    .parent_ancestors()
173                    .find(|s| matches!(s.kind(), TABLE_ARRAY_HEADER | TABLE_HEADER))?;
174
175                header_syntax.descendants().find(|n| n.kind() == KEY)
176            }
177            _ => None,
178        }
179    }
180
181    #[must_use]
182    pub fn entry_key(&self) -> Option<SyntaxNode> {
183        let syntax = match self.before.as_ref().or(self.after.as_ref()) {
184            Some(p) => &p.syntax,
185            None => return None,
186        };
187
188        let keys = syntax
189            .parent_ancestors()
190            .find(|n| n.kind() == ENTRY)
191            .and_then(|entry| entry.children().find(|c| c.kind() == KEY))?;
192
193        Some(keys)
194    }
195
196    #[must_use]
197    pub fn entry_value(&self) -> Option<SyntaxNode> {
198        let syntax = match self.before.as_ref().or(self.after.as_ref()) {
199            Some(p) => &p.syntax,
200            None => return None,
201        };
202
203        let value = syntax
204            .parent_ancestors()
205            .find(|n| n.kind() == ENTRY)
206            .and_then(|entry| entry.children().find(|c| c.kind() == VALUE))?;
207
208        Some(value)
209    }
210
211    #[must_use]
212    pub fn parent_table_or_array_table(&self, root: &Node) -> (Keys, Node) {
213        let syntax = match self.before.as_ref().or(self.after.as_ref()) {
214            Some(s) => s.syntax.clone(),
215            None => return (Keys::empty(), root.clone()),
216        };
217
218        let last_header = root
219            .syntax()
220            .unwrap()
221            .as_node()
222            .unwrap()
223            .descendants()
224            .skip(1)
225            .filter(|n| matches!(n.kind(), TABLE_HEADER | TABLE_ARRAY_HEADER))
226            .take_while(|n| n.text_range().end() <= syntax.text_range().end())
227            .last();
228
229        let Some(last_header) = last_header else {
230            return (Keys::empty(), root.clone());
231        };
232
233        let keys = Keys::from_syntax(
234            last_header
235                .descendants()
236                .find(|n| n.kind() == KEY)
237                .unwrap()
238                .into(),
239        );
240        let node = root.path(&keys).unwrap();
241
242        (keys, node)
243    }
244
245    #[must_use]
246    pub fn empty_line(&self) -> bool {
247        let before_syntax = match self.before.as_ref() {
248            Some(s) => &s.syntax,
249            None => return true,
250        };
251
252        match &self.after {
253            Some(after) => {
254                if matches!(after.syntax.kind(), WHITESPACE | NEWLINE) {
255                    let new_line_after = after
256                        .syntax
257                        .siblings_with_tokens(Direction::Next)
258                        .find_map(|s| match s.kind() {
259                            NEWLINE => Some(true),
260                            WHITESPACE | COMMENT => None,
261                            _ => Some(false),
262                        })
263                        .unwrap_or(true);
264
265                    if !new_line_after {
266                        return false;
267                    }
268                } else {
269                    return false;
270                }
271            }
272            None => {}
273        }
274
275        before_syntax
276            .siblings_with_tokens(Direction::Prev)
277            .find_map(|s| match s.kind() {
278                NEWLINE => Some(true),
279                WHITESPACE | COMMENT => None,
280                _ => Some(false),
281            })
282            .unwrap_or(true)
283    }
284
285    #[must_use]
286    pub fn in_entry_keys(&self) -> bool {
287        self.entry_key()
288            .is_some_and(|k| k.text_range().contains(self.offset))
289    }
290
291    #[must_use]
292    pub fn entry_has_eq(&self) -> bool {
293        let Some(key_syntax) = self.entry_key() else {
294            return false;
295        };
296
297        key_syntax
298            .siblings(Direction::Next)
299            .find_map(|s| match s.kind() {
300                EQ => Some(true),
301                WHITESPACE => None,
302                _ => Some(false),
303            })
304            .unwrap_or(false)
305    }
306
307    #[must_use]
308    pub fn in_entry_value(&self) -> bool {
309        let in_value = self
310            .entry_value()
311            // We are inside the value even if the cursor is right after it.
312            .is_some_and(|k| k.text_range().contains_inclusive(self.offset));
313
314        if in_value {
315            return true;
316        }
317
318        let syntax = match self.before.as_ref().or(self.after.as_ref()) {
319            Some(p) => &p.syntax,
320            None => return false,
321        };
322
323        syntax
324            .siblings_with_tokens(Direction::Prev)
325            .find_map(|s| match s.kind() {
326                EQ => Some(true),
327                WHITESPACE | COMMENT | NEWLINE => None,
328                _ => Some(false),
329            })
330            .unwrap_or(false)
331    }
332
333    #[must_use]
334    pub fn is_single_quote_value(&self) -> bool {
335        self.entry_value().is_some_and(|v| {
336            v.descendants_with_tokens()
337                .any(|t| matches!(t.kind(), STRING_LITERAL | MULTI_LINE_STRING_LITERAL))
338        })
339    }
340
341    #[must_use]
342    pub fn is_inline(&self) -> bool {
343        let syntax = match self.before.as_ref().or(self.after.as_ref()) {
344            Some(p) => &p.syntax,
345            None => return false,
346        };
347
348        syntax
349            .parent_ancestors()
350            .any(|a| matches!(a.kind(), INLINE_TABLE | ARRAY))
351    }
352
353    #[must_use]
354    pub fn in_inline_table(&self) -> bool {
355        let syntax = match self.before.as_ref().or(self.after.as_ref()) {
356            Some(p) => &p.syntax,
357            None => return false,
358        };
359
360        match syntax.parent() {
361            Some(parent) => {
362                if parent.kind() != INLINE_TABLE {
363                    return false;
364                }
365
366                parent
367                    .children_with_tokens()
368                    .find_map(|t| {
369                        if t.kind() == BRACE_END {
370                            Some(self.offset <= t.text_range().start())
371                        } else {
372                            None
373                        }
374                    })
375                    .unwrap_or(true)
376            }
377            None => false,
378        }
379    }
380
381    #[must_use]
382    pub fn in_array(&self) -> bool {
383        let syntax = match self.before.as_ref().or(self.after.as_ref()) {
384            Some(p) => &p.syntax,
385            None => return false,
386        };
387
388        match syntax.parent() {
389            Some(parent) => {
390                if parent.kind() != ARRAY {
391                    return false;
392                }
393
394                parent
395                    .children_with_tokens()
396                    .find_map(|t| {
397                        if t.kind() == BRACKET_END {
398                            Some(self.offset <= t.text_range().start())
399                        } else {
400                            None
401                        }
402                    })
403                    .unwrap_or(true)
404            }
405            None => false,
406        }
407    }
408
409    pub fn entry_keys(&self) -> Keys {
410        self.entry_key()
411            .map_or_else(Keys::empty, |keys| Keys::from_syntax(keys.into()))
412    }
413
414    pub fn header_keys(&self) -> Keys {
415        self.header_key()
416            .map_or_else(Keys::empty, |keys| Keys::from_syntax(keys.into()))
417    }
418
419    #[must_use]
420    pub fn dom_node(&self) -> Option<&(Keys, Node)> {
421        self.before
422            .as_ref()
423            .and_then(|p| p.dom_node.as_ref())
424            .or_else(|| self.after.as_ref().and_then(|p| p.dom_node.as_ref()))
425    }
426}
427
428/// Transform the lookup keys to account for arrays of tables and arrays.
429///
430/// It appends an index after each array so that we get the item type
431/// during lookups.
432#[must_use]
433pub fn lookup_keys(root: Node, keys: &Keys) -> Keys {
434    let mut node = root;
435    let mut new_keys = Keys::empty();
436
437    for key in keys.iter().cloned() {
438        node = node.get(&key);
439        new_keys = new_keys.join(key);
440        if let Some(arr) = node.as_array() {
441            new_keys = new_keys.join(arr.items().read().len().saturating_sub(1));
442        }
443    }
444
445    new_keys
446}
447
448#[derive(Debug, Clone)]
449pub struct PositionInfo {
450    /// The narrowest syntax element that contains the position.
451    pub syntax: SyntaxToken,
452    /// The narrowest node that covers the position.
453    pub dom_node: Option<(Keys, Node)>,
454}
455
456fn full_range(keys: &Keys, node: &Node) -> TextRange {
457    let Some(last_key) = keys
458        .iter()
459        .filter_map(KeyOrIndex::as_key)
460        .next_back()
461        .map(Key::text_ranges)
462    else {
463        return join_ranges(node.text_ranges(true));
464    };
465
466    join_ranges(last_key.chain(node.text_ranges(true)))
467}