tinymist_analysis/syntax/
matcher.rs

1//! Convenient utilities to match syntax structures of code.
2//! - Iterators/Finders to traverse nodes.
3//! - Predicates to check nodes' properties.
4//! - Classifiers to check nodes' syntaxes.
5//!
6//! ## Classifiers of syntax structures
7//!
8//! A node can have a quadruple to describe its syntax:
9//!
10//! ```text
11//! (InterpretMode, SurroundingSyntax/SyntaxContext, DefClass/SyntaxClass, SyntaxNode)
12//! ```
13//!
14//! Among them, [`InterpretMode`], [`SurroundingSyntax`], and [`SyntaxContext`]
15//! describes outer syntax. [`DefClass`], [`SyntaxClass`] and
16//! [`typst::syntax::SyntaxNode`] describes inner syntax.
17//!
18//! - [`typst::syntax::SyntaxNode`]: Its contextual version is
19//!   [`typst::syntax::LinkedNode`], containing AST information, like inner text
20//!   and [`SyntaxKind`], on the position.
21//! - [`SyntaxClass`]: Provided by [`classify_syntax`], it describes the
22//!   context-free syntax of the node that are more suitable for IDE operations.
23//!   For example, it identifies users' half-typed syntax like half-completed
24//!   labels and dot accesses.
25//! - [`DefClass`]: Provided by [`classify_def`], it describes the definition
26//!   class of the node at the position. The difference between `SyntaxClass`
27//!   and `DefClass` is that the latter matcher will skip the nodes that do not
28//!   define a definition.
29//! - [`SyntaxContext`]: Provided by [`classify_context`], it describes the
30//!   outer syntax of the node that are more suitable for IDE operations. For
31//!   example, it identifies the context of a cursor on the comma in a function
32//!   call.
33//! - [`SurroundingSyntax`]: Provided by [`surrounding_syntax`], it describes
34//!   the surrounding syntax of the node that are more suitable for IDE
35//!   operations. The difference between `SyntaxContext` and `SurroundingSyntax`
36//!   is that the former is more specific and the latter is more general can be
37//!   used for filtering customized snippets.
38//! - [`InterpretMode`]: Provided by [`interpret_mode_at`], it describes the how
39//!   an interpreter should interpret the code at the position.
40//!
41//! Some examples of the quadruple (the cursor is marked by `|`):
42//!
43//! ```text
44//! #(x|);
45//!    ^ SyntaxContext::Paren, SyntaxClass::Normal(SyntaxKind::Ident)
46//! #(x,|);
47//!     ^ SyntaxContext::Element, SyntaxClass::Normal(SyntaxKind::Array)
48//! #f(x,|);
49//!      ^ SyntaxContext::Arg, SyntaxClass::Normal(SyntaxKind::FuncCall)
50//! ```
51//!
52//! ```text
53//! #show raw|: |it => it|
54//!          ^ SurroundingSyntax::Selector
55//!             ^ SurroundingSyntax::ShowTransform
56//!                      ^ SurroundingSyntax::Regular
57//! ```
58
59use serde::{Deserialize, Serialize};
60use tinymist_world::debug_loc::SourceSpanOffset;
61use typst::syntax::Span;
62
63use crate::prelude::*;
64
65/// Returns the ancestor iterator of the given node.
66pub fn node_ancestors<'a, 'b>(
67    node: &'b LinkedNode<'a>,
68) -> impl Iterator<Item = &'b LinkedNode<'a>> {
69    std::iter::successors(Some(node), |node| node.parent())
70}
71
72/// Finds the first ancestor node that is an expression.
73pub fn first_ancestor_expr(node: LinkedNode) -> Option<LinkedNode> {
74    node_ancestors(&node)
75        .find(|n| n.is::<ast::Expr>())
76        .map(|mut node| {
77            while matches!(node.kind(), SyntaxKind::Ident | SyntaxKind::MathIdent) {
78                let Some(parent) = node.parent() else {
79                    return node;
80                };
81
82                let Some(field_access) = parent.cast::<ast::FieldAccess>() else {
83                    return node;
84                };
85
86                let dot = parent
87                    .children()
88                    .find(|n| matches!(n.kind(), SyntaxKind::Dot));
89
90                // Since typst matches `field()` by `case_last_match`, when the field access
91                // `x.` (`Ident(x).Error("")`), it will match the `x` as the
92                // field. We need to check dot position to filter out such cases.
93                if dot.is_some_and(|dot| {
94                    dot.offset() <= node.offset() && field_access.field().span() == node.span()
95                }) {
96                    node = parent;
97                } else {
98                    return node;
99                }
100            }
101
102            node
103        })
104        .cloned()
105}
106
107/// A node that is an ancestor of the given node or the previous sibling
108/// of some ancestor.
109pub enum PreviousItem<'a> {
110    /// When the iterator is crossing an ancesstor node.
111    Parent(&'a LinkedNode<'a>, &'a LinkedNode<'a>),
112    /// When the iterator is on a sibling node of some ancestor.
113    Sibling(&'a LinkedNode<'a>),
114}
115
116impl<'a> PreviousItem<'a> {
117    /// Gets the underlying [`LinkedNode`] of the item.
118    pub fn node(&self) -> &'a LinkedNode<'a> {
119        match self {
120            PreviousItem::Sibling(node) => node,
121            PreviousItem::Parent(node, _) => node,
122        }
123    }
124}
125
126/// Finds the previous items (in the scope) starting from the given position
127/// inclusively. See [`PreviousItem`] for the possible items.
128pub fn previous_items<T>(
129    node: LinkedNode,
130    mut recv: impl FnMut(PreviousItem) -> Option<T>,
131) -> Option<T> {
132    let mut ancestor = Some(node);
133    while let Some(node) = &ancestor {
134        let mut sibling = Some(node.clone());
135        while let Some(node) = &sibling {
136            if let Some(v) = recv(PreviousItem::Sibling(node)) {
137                return Some(v);
138            }
139
140            sibling = node.prev_sibling();
141        }
142
143        if let Some(parent) = node.parent() {
144            if let Some(v) = recv(PreviousItem::Parent(parent, node)) {
145                return Some(v);
146            }
147
148            ancestor = Some(parent.clone());
149            continue;
150        }
151
152        break;
153    }
154
155    None
156}
157
158/// A declaration that is an ancestor of the given node or the previous sibling
159/// of some ancestor.
160pub enum PreviousDecl<'a> {
161    /// An declaration having an identifier.
162    ///
163    /// ## Example
164    ///
165    /// The `x` in the following code:
166    ///
167    /// ```typst
168    /// #let x = 1;
169    /// ```
170    Ident(ast::Ident<'a>),
171    /// An declaration yielding from an import source.
172    ///
173    /// ## Example
174    ///
175    /// The `x` in the following code:
176    ///
177    /// ```typst
178    /// #import "path.typ": x;
179    /// ```
180    ImportSource(ast::Expr<'a>),
181    /// A wildcard import that possibly containing visible declarations.
182    ///
183    /// ## Example
184    ///
185    /// The following import is matched:
186    ///
187    /// ```typst
188    /// #import "path.typ": *;
189    /// ```
190    ImportAll(ast::ModuleImport<'a>),
191}
192
193/// Finds the previous declarations starting from the given position. It checks
194/// [`PreviousItem`] and returns the found declarations.
195pub fn previous_decls<T>(
196    node: LinkedNode,
197    mut recv: impl FnMut(PreviousDecl) -> Option<T>,
198) -> Option<T> {
199    previous_items(node, |item| {
200        match (&item, item.node().cast::<ast::Expr>()?) {
201            (PreviousItem::Sibling(..), ast::Expr::LetBinding(lb)) => {
202                for ident in lb.kind().bindings() {
203                    if let Some(t) = recv(PreviousDecl::Ident(ident)) {
204                        return Some(t);
205                    }
206                }
207            }
208            (PreviousItem::Sibling(..), ast::Expr::ModuleImport(import)) => {
209                // import items
210                match import.imports() {
211                    Some(ast::Imports::Wildcard) => {
212                        if let Some(t) = recv(PreviousDecl::ImportAll(import)) {
213                            return Some(t);
214                        }
215                    }
216                    Some(ast::Imports::Items(items)) => {
217                        for item in items.iter() {
218                            if let Some(t) = recv(PreviousDecl::Ident(item.bound_name())) {
219                                return Some(t);
220                            }
221                        }
222                    }
223                    _ => {}
224                }
225
226                // import itself
227                if let Some(new_name) = import.new_name() {
228                    if let Some(t) = recv(PreviousDecl::Ident(new_name)) {
229                        return Some(t);
230                    }
231                } else if import.imports().is_none()
232                    && let Some(t) = recv(PreviousDecl::ImportSource(import.source()))
233                {
234                    return Some(t);
235                }
236            }
237            (PreviousItem::Parent(parent, child), ast::Expr::ForLoop(for_expr)) => {
238                let body = parent.find(for_expr.body().span());
239                let in_body = body.is_some_and(|n| n.find(child.span()).is_some());
240                if !in_body {
241                    return None;
242                }
243
244                for ident in for_expr.pattern().bindings() {
245                    if let Some(t) = recv(PreviousDecl::Ident(ident)) {
246                        return Some(t);
247                    }
248                }
249            }
250            (PreviousItem::Parent(parent, child), ast::Expr::Closure(closure)) => {
251                let body = parent.find(closure.body().span());
252                let in_body = body.is_some_and(|n| n.find(child.span()).is_some());
253                if !in_body {
254                    return None;
255                }
256
257                for param in closure.params().children() {
258                    match param {
259                        ast::Param::Pos(pos) => {
260                            for ident in pos.bindings() {
261                                if let Some(t) = recv(PreviousDecl::Ident(ident)) {
262                                    return Some(t);
263                                }
264                            }
265                        }
266                        ast::Param::Named(named) => {
267                            if let Some(t) = recv(PreviousDecl::Ident(named.name())) {
268                                return Some(t);
269                            }
270                        }
271                        ast::Param::Spread(spread) => {
272                            if let Some(sink_ident) = spread.sink_ident()
273                                && let Some(t) = recv(PreviousDecl::Ident(sink_ident))
274                            {
275                                return Some(t);
276                            }
277                        }
278                    }
279                }
280            }
281            _ => {}
282        };
283        None
284    })
285}
286
287/// Checks if the node can be recognized as a mark.
288pub fn is_mark(sk: SyntaxKind) -> bool {
289    use SyntaxKind::*;
290    #[allow(clippy::match_like_matches_macro)]
291    match sk {
292        MathAlignPoint | Plus | Minus | Dot | Dots | Arrow | Not | And | Or => true,
293        Eq | EqEq | ExclEq | Lt | LtEq | Gt | GtEq | PlusEq | HyphEq | StarEq | SlashEq => true,
294        LeftBrace | RightBrace | LeftBracket | RightBracket | LeftParen | RightParen => true,
295        Slash | Hat | Comma | Semicolon | Colon | Hash => true,
296        _ => false,
297    }
298}
299
300/// Checks if the node can be recognized as an identifier.
301pub fn is_ident_like(node: &SyntaxNode) -> bool {
302    fn can_be_ident(node: &SyntaxNode) -> bool {
303        typst::syntax::is_ident(node.text())
304    }
305
306    use SyntaxKind::*;
307    let kind = node.kind();
308    matches!(kind, Ident | MathIdent | Underscore)
309        || (matches!(kind, Error) && can_be_ident(node))
310        || kind.is_keyword()
311}
312
313/// A mode in which a text document is interpreted.
314#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
315#[serde(rename_all = "camelCase")]
316pub enum InterpretMode {
317    /// The position is in a comment.
318    Comment,
319    /// The position is in a string.
320    String,
321    /// The position is in a raw.
322    Raw,
323    /// The position is in a markup block.
324    Markup,
325    /// The position is in a code block.
326    Code,
327    /// The position is in a math equation.
328    Math,
329}
330
331/// Determines the interpretation mode at the given position
332/// (context-sensitive).
333pub fn interpret_mode_at(mut leaf: Option<&LinkedNode>) -> InterpretMode {
334    loop {
335        crate::log_debug_ct!("leaf for mode: {leaf:?}");
336        if let Some(t) = leaf {
337            if let Some(mode) = interpret_mode_at_kind(t.kind()) {
338                break mode;
339            }
340
341            if !t.kind().is_trivia() && {
342                // Previous leaf is hash
343                t.prev_leaf().is_some_and(|n| n.kind() == SyntaxKind::Hash)
344            } {
345                return InterpretMode::Code;
346            }
347
348            leaf = t.parent();
349        } else {
350            break InterpretMode::Markup;
351        }
352    }
353}
354
355/// Determines the interpretation mode at the given kind (context-free).
356pub(crate) fn interpret_mode_at_kind(kind: SyntaxKind) -> Option<InterpretMode> {
357    use SyntaxKind::*;
358    Some(match kind {
359        LineComment | BlockComment | Shebang => InterpretMode::Comment,
360        Raw => InterpretMode::Raw,
361        Str => InterpretMode::String,
362        CodeBlock | Code => InterpretMode::Code,
363        ContentBlock | Markup => InterpretMode::Markup,
364        Equation | Math => InterpretMode::Math,
365        Hash => InterpretMode::Code,
366        Label | Text | Ident | Args | FuncCall | FieldAccess | Bool | Int | Float | Numeric
367        | Space | Linebreak | Parbreak | Escape | Shorthand | SmartQuote | RawLang | RawDelim
368        | RawTrimmed | LeftBrace | RightBrace | LeftBracket | RightBracket | LeftParen
369        | RightParen | Comma | Semicolon | Colon | Star | Underscore | Dollar | Plus | Minus
370        | Slash | Hat | Prime | Dot | Eq | EqEq | ExclEq | Lt | LtEq | Gt | GtEq | PlusEq
371        | HyphEq | StarEq | SlashEq | Dots | Arrow | Root | Not | And | Or | None | Auto | As
372        | Named | Keyed | Spread | Error | End => return Option::None,
373        Strong | Emph | Link | Ref | RefMarker | Heading | HeadingMarker | ListItem
374        | ListMarker | EnumItem | EnumMarker | TermItem | TermMarker => InterpretMode::Markup,
375        MathIdent | MathAlignPoint | MathDelimited | MathAttach | MathPrimes | MathFrac
376        | MathRoot | MathShorthand | MathText => InterpretMode::Math,
377        Let | Set | Show | Context | If | Else | For | In | While | Break | Continue | Return
378        | Import | Include | Closure | Params | LetBinding | SetRule | ShowRule | Contextual
379        | Conditional | WhileLoop | ForLoop | LoopBreak | ModuleImport | ImportItems
380        | ImportItemPath | RenamedImportItem | ModuleInclude | LoopContinue | FuncReturn
381        | Unary | Binary | Parenthesized | Dict | Array | Destructuring | DestructAssignment => {
382            InterpretMode::Code
383        }
384    })
385}
386
387/// Classes of def items that can be operated on by IDE functionality.
388#[derive(Debug, Clone)]
389pub enum DefClass<'a> {
390    /// A let binding item.
391    Let(LinkedNode<'a>),
392    /// A module import item.
393    Import(LinkedNode<'a>),
394}
395
396impl DefClass<'_> {
397    /// Gets the node of the def class.
398    pub fn node(&self) -> &LinkedNode<'_> {
399        match self {
400            DefClass::Let(node) => node,
401            DefClass::Import(node) => node,
402        }
403    }
404
405    /// Gets the name node of the def class.
406    pub fn name(&self) -> Option<LinkedNode<'_>> {
407        match self {
408            DefClass::Let(node) => {
409                let lb: ast::LetBinding<'_> = node.cast()?;
410                let names = match lb.kind() {
411                    ast::LetBindingKind::Closure(name) => node.find(name.span())?,
412                    ast::LetBindingKind::Normal(ast::Pattern::Normal(name)) => {
413                        node.find(name.span())?
414                    }
415                    _ => return None,
416                };
417
418                Some(names)
419            }
420            DefClass::Import(_node) => {
421                // let ident = node.cast::<ast::ImportItem>()?;
422                // Some(ident.span().into())
423                // todo: implement this
424                None
425            }
426        }
427    }
428
429    /// Gets the name's range in code of the def class.
430    pub fn name_range(&self) -> Option<Range<usize>> {
431        self.name().map(|node| node.range())
432    }
433}
434
435// todo: whether we should distinguish between strict and loose def classes
436/// Classifies a definition loosely.
437pub fn classify_def_loosely(node: LinkedNode<'_>) -> Option<DefClass<'_>> {
438    classify_def_(node, false)
439}
440
441/// Classifies a definition strictly.
442pub fn classify_def(node: LinkedNode<'_>) -> Option<DefClass<'_>> {
443    classify_def_(node, true)
444}
445
446/// The internal implementation of classifying a definition.
447fn classify_def_(node: LinkedNode<'_>, strict: bool) -> Option<DefClass<'_>> {
448    let mut ancestor = node;
449    if ancestor.kind().is_trivia() || is_mark(ancestor.kind()) {
450        ancestor = ancestor.prev_sibling()?;
451    }
452
453    while !ancestor.is::<ast::Expr>() {
454        ancestor = ancestor.parent()?.clone();
455    }
456    crate::log_debug_ct!("ancestor: {ancestor:?}");
457    let adjusted = adjust_expr(ancestor)?;
458    crate::log_debug_ct!("adjust_expr: {adjusted:?}");
459
460    let may_ident = adjusted.cast::<ast::Expr>()?;
461    if strict && !may_ident.hash() && !matches!(may_ident, ast::Expr::MathIdent(_)) {
462        return None;
463    }
464
465    let expr = may_ident;
466    Some(match expr {
467        // todo: label, reference
468        // todo: include
469        ast::Expr::FuncCall(..) => return None,
470        ast::Expr::SetRule(..) => return None,
471        ast::Expr::LetBinding(..) => DefClass::Let(adjusted),
472        ast::Expr::ModuleImport(..) => DefClass::Import(adjusted),
473        // todo: parameter
474        ast::Expr::Ident(..)
475        | ast::Expr::MathIdent(..)
476        | ast::Expr::FieldAccess(..)
477        | ast::Expr::Closure(..) => {
478            let mut ancestor = adjusted;
479            while !ancestor.is::<ast::LetBinding>() {
480                ancestor = ancestor.parent()?.clone();
481            }
482
483            DefClass::Let(ancestor)
484        }
485        ast::Expr::Str(..) => {
486            let parent = adjusted.parent()?;
487            if parent.kind() != SyntaxKind::ModuleImport {
488                return None;
489            }
490
491            DefClass::Import(parent.clone())
492        }
493        _ if expr.hash() => return None,
494        _ => {
495            crate::log_debug_ct!("unsupported kind {:?}", adjusted.kind());
496            return None;
497        }
498    })
499}
500
501/// Adjusts an expression node to a more suitable one for classification.
502/// It is not formal, but the following cases are forbidden:
503/// - Parenthesized expression.
504/// - Identifier on the right side of a dot operator (field access).
505pub fn adjust_expr(mut node: LinkedNode) -> Option<LinkedNode> {
506    while let Some(paren_expr) = node.cast::<ast::Parenthesized>() {
507        node = node.find(paren_expr.expr().span())?;
508    }
509    if let Some(parent) = node.parent()
510        && let Some(field_access) = parent.cast::<ast::FieldAccess>()
511        && node.span() == field_access.field().span()
512    {
513        return Some(parent.clone());
514    }
515    Some(node)
516}
517
518/// Classes of field syntax that can be operated on by IDE functionality.
519#[derive(Debug, Clone)]
520pub enum FieldClass<'a> {
521    /// A field node.
522    ///
523    /// ## Example
524    ///
525    /// The `x` in the following code:
526    ///
527    /// ```typst
528    /// #a.x
529    /// ```
530    Field(LinkedNode<'a>),
531
532    /// A dot suffix missing a field.
533    ///
534    /// ## Example
535    ///
536    /// The `.` in the following code:
537    ///
538    /// ```typst
539    /// #a.
540    /// ```
541    DotSuffix(SourceSpanOffset),
542}
543
544impl FieldClass<'_> {
545    /// Gets the node of the field class.
546    pub fn offset(&self, source: &Source) -> Option<usize> {
547        Some(match self {
548            Self::Field(node) => node.offset(),
549            Self::DotSuffix(span_offset) => {
550                source.find(span_offset.span)?.offset() + span_offset.offset
551            }
552        })
553    }
554}
555
556/// Classes of variable (access) syntax that can be operated on by IDE
557/// functionality.
558#[derive(Debug, Clone)]
559pub enum VarClass<'a> {
560    /// An identifier expression.
561    Ident(LinkedNode<'a>),
562    /// A field access expression.
563    FieldAccess(LinkedNode<'a>),
564    /// A dot access expression, for example, `#a.|`, `$a.|$`, or `x.|.y`.
565    /// Note the cursor of the last example is on the middle of the spread
566    /// operator.
567    DotAccess(LinkedNode<'a>),
568}
569
570impl<'a> VarClass<'a> {
571    /// Gets the node of the var (access) class.
572    pub fn node(&self) -> &LinkedNode<'a> {
573        match self {
574            Self::Ident(node) | Self::FieldAccess(node) | Self::DotAccess(node) => node,
575        }
576    }
577
578    /// Gets the accessed node of the var (access) class.
579    pub fn accessed_node(&self) -> Option<LinkedNode<'a>> {
580        Some(match self {
581            Self::Ident(node) => node.clone(),
582            Self::FieldAccess(node) => {
583                let field_access = node.cast::<ast::FieldAccess>()?;
584                node.find(field_access.target().span())?
585            }
586            Self::DotAccess(node) => node.clone(),
587        })
588    }
589
590    /// Gets the accessing field of the var (access) class.
591    pub fn accessing_field(&self) -> Option<FieldClass<'a>> {
592        match self {
593            Self::FieldAccess(node) => {
594                let dot = node
595                    .children()
596                    .find(|n| matches!(n.kind(), SyntaxKind::Dot))?;
597                let mut iter_after_dot =
598                    node.children().skip_while(|n| n.kind() != SyntaxKind::Dot);
599                let ident = iter_after_dot.find(|n| {
600                    matches!(
601                        n.kind(),
602                        SyntaxKind::Ident | SyntaxKind::MathIdent | SyntaxKind::Error
603                    )
604                });
605
606                let ident_case = ident.map(|ident| {
607                    if ident.text().is_empty() {
608                        FieldClass::DotSuffix(SourceSpanOffset {
609                            span: ident.span(),
610                            offset: 0,
611                        })
612                    } else {
613                        FieldClass::Field(ident)
614                    }
615                });
616
617                ident_case.or_else(|| {
618                    Some(FieldClass::DotSuffix(SourceSpanOffset {
619                        span: dot.span(),
620                        offset: 1,
621                    }))
622                })
623            }
624            Self::DotAccess(node) => Some(FieldClass::DotSuffix(SourceSpanOffset {
625                span: node.span(),
626                offset: node.range().len() + 1,
627            })),
628            Self::Ident(..) => None,
629        }
630    }
631}
632
633/// Classes of syntax that can be operated on by IDE functionality.
634#[derive(Debug, Clone)]
635pub enum SyntaxClass<'a> {
636    /// A variable access expression.
637    ///
638    /// It can be either an identifier or a field access.
639    VarAccess(VarClass<'a>),
640    /// A (content) label expression.
641    Label {
642        /// The node of the label.
643        node: LinkedNode<'a>,
644        /// Whether the label is converted from an error node.
645        is_error: bool,
646    },
647    /// A (content) reference expression.
648    Ref {
649        /// The node of the reference.
650        node: LinkedNode<'a>,
651        /// A colon after a reference expression, for example, `@a:|` or
652        /// `@a:b:|`.
653        suffix_colon: bool,
654    },
655    /// A `@` text, which can be viewed as references with "empty content"
656    At {
657        /// The node containing the `@` text.
658        node: LinkedNode<'a>,
659    },
660    /// A callee expression.
661    Callee(LinkedNode<'a>),
662    /// An import path expression.
663    ImportPath(LinkedNode<'a>),
664    /// An include path expression.
665    IncludePath(LinkedNode<'a>),
666    /// Rest kind of **expressions**.
667    Normal(SyntaxKind, LinkedNode<'a>),
668}
669
670impl<'a> SyntaxClass<'a> {
671    /// Creates a label syntax class.
672    pub fn label(node: LinkedNode<'a>) -> Self {
673        Self::Label {
674            node,
675            is_error: false,
676        }
677    }
678
679    /// Creates an error label syntax class.
680    pub fn error_as_label(node: LinkedNode<'a>) -> Self {
681        Self::Label {
682            node,
683            is_error: true,
684        }
685    }
686
687    /// Gets the node of the syntax class.
688    pub fn node(&self) -> &LinkedNode<'a> {
689        match self {
690            SyntaxClass::VarAccess(cls) => cls.node(),
691            SyntaxClass::Label { node, .. }
692            | SyntaxClass::Ref { node, .. }
693            | SyntaxClass::At { node, .. }
694            | SyntaxClass::Callee(node)
695            | SyntaxClass::ImportPath(node)
696            | SyntaxClass::IncludePath(node)
697            | SyntaxClass::Normal(_, node) => node,
698        }
699    }
700
701    /// Gets the content offset at which the completion should be triggered.
702    pub fn complete_offset(&self) -> Option<usize> {
703        match self {
704            // `<label`
705            //   ^ node.offset() + 1
706            SyntaxClass::Label { node, .. } => Some(node.offset() + 1),
707            _ => None,
708        }
709    }
710
711    /// Whether the syntax class or its children contain an error.
712    pub fn erroneous(&self) -> bool {
713        use SyntaxClass::*;
714        match self {
715            Label { .. } => false,
716            VarAccess(cls) => cls.node().erroneous(),
717            Normal(_, node)
718            | Callee(node)
719            | At { node }
720            | Ref { node, .. }
721            | ImportPath(node)
722            | IncludePath(node) => node.erroneous(),
723        }
724    }
725}
726
727/// Classifies node's syntax (inner syntax) that can be operated on by IDE
728/// functionality.
729pub fn classify_syntax(node: LinkedNode<'_>, cursor: usize) -> Option<SyntaxClass<'_>> {
730    if matches!(node.kind(), SyntaxKind::Error) && node.text().starts_with('<') {
731        return Some(SyntaxClass::error_as_label(node));
732    }
733
734    /// Skips trivia nodes that are on the same line as the cursor.
735    fn can_skip_trivia(node: &LinkedNode, cursor: usize) -> bool {
736        // A non-trivia node is our target so we stop at it.
737        if !node.kind().is_trivia() || !node.parent_kind().is_some_and(possible_in_code_trivia) {
738            return false;
739        }
740
741        // Gets the trivia text before the cursor.
742        let previous_text = node.text().as_bytes();
743        let previous_text = if node.range().contains(&cursor) {
744            &previous_text[..cursor - node.offset()]
745        } else {
746            previous_text
747        };
748
749        // The deref target should be on the same line as the cursor.
750        // Assuming the underlying text is utf-8 encoded, we can check for newlines by
751        // looking for b'\n'.
752        // todo: if we are in markup mode, we should check if we are at start of node
753        !previous_text.contains(&b'\n')
754    }
755
756    // Moves to the first non-trivia node before the cursor.
757    let mut node = node;
758    if can_skip_trivia(&node, cursor) {
759        node = node.prev_sibling()?;
760    }
761
762    /// Matches complete or incomplete dot accesses in code, math, and markup
763    /// mode.
764    ///
765    /// When in markup mode, the dot access is valid if the dot is after a hash
766    /// expression.
767    fn classify_dot_access<'a>(node: &LinkedNode<'a>) -> Option<SyntaxClass<'a>> {
768        let prev_leaf = node.prev_leaf();
769        let mode = interpret_mode_at(Some(node));
770
771        // Don't match `$ .| $`
772        if matches!(mode, InterpretMode::Markup | InterpretMode::Math)
773            && prev_leaf
774                .as_ref()
775                .is_some_and(|leaf| leaf.range().end < node.offset())
776        {
777            return None;
778        }
779
780        if matches!(mode, InterpretMode::Math)
781            && prev_leaf.as_ref().is_some_and(|leaf| {
782                // Don't match `$ a.| $` or `$.| $`
783                node_ancestors(leaf)
784                    .find(|t| matches!(t.kind(), SyntaxKind::Equation))
785                    .is_some_and(|parent| parent.offset() == leaf.offset())
786            })
787        {
788            return None;
789        }
790
791        let dot_target = prev_leaf.and_then(first_ancestor_expr)?;
792
793        if matches!(mode, InterpretMode::Math | InterpretMode::Code) || {
794            matches!(mode, InterpretMode::Markup)
795                && (matches!(
796                    dot_target.kind(),
797                    SyntaxKind::Ident
798                        | SyntaxKind::MathIdent
799                        | SyntaxKind::FieldAccess
800                        | SyntaxKind::FuncCall
801                ) || (matches!(
802                    dot_target.prev_leaf().as_deref().map(SyntaxNode::kind),
803                    Some(SyntaxKind::Hash)
804                )))
805        } {
806            return Some(SyntaxClass::VarAccess(VarClass::DotAccess(dot_target)));
807        }
808
809        None
810    }
811
812    if node.offset() + 1 == cursor
813        && {
814            // Check if the cursor is exactly after single dot.
815            matches!(node.kind(), SyntaxKind::Dot)
816                || (matches!(
817                    node.kind(),
818                    SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::Error
819                ) && node.text().starts_with("."))
820        }
821        && let Some(dot_access) = classify_dot_access(&node)
822    {
823        return Some(dot_access);
824    }
825
826    if node.offset() + 1 == cursor
827        && matches!(node.kind(), SyntaxKind::Dots)
828        && matches!(node.parent_kind(), Some(SyntaxKind::Spread))
829        && let Some(dot_access) = classify_dot_access(&node)
830    {
831        return Some(dot_access);
832    }
833
834    /// Matches ref parsing broken by a colon.
835    ///
836    /// When in markup mode, the ref is valid if the colon is after a ref
837    /// expression.
838    fn classify_ref<'a>(node: &LinkedNode<'a>) -> Option<SyntaxClass<'a>> {
839        let prev_leaf = node.prev_leaf()?;
840
841        if matches!(prev_leaf.kind(), SyntaxKind::RefMarker)
842            && prev_leaf.range().end == node.offset()
843        {
844            return Some(SyntaxClass::Ref {
845                node: prev_leaf,
846                suffix_colon: true,
847            });
848        }
849
850        None
851    }
852
853    if node.offset() + 1 == cursor
854        && {
855            // Check if the cursor is exactly after single dot.
856            matches!(node.kind(), SyntaxKind::Colon)
857                || (matches!(
858                    node.kind(),
859                    SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::Error
860                ) && node.text().starts_with(":"))
861        }
862        && let Some(ref_syntax) = classify_ref(&node)
863    {
864        return Some(ref_syntax);
865    }
866
867    if node.kind() == SyntaxKind::Text
868        && node.offset() + 1 == cursor
869        && node.text().starts_with('@')
870    {
871        return Some(SyntaxClass::At { node });
872    }
873
874    // todo: check if we can remove Text here
875    if matches!(node.kind(), SyntaxKind::Text | SyntaxKind::MathText) {
876        let mode = interpret_mode_at(Some(&node));
877        if matches!(mode, InterpretMode::Math) && is_ident_like(&node) {
878            return Some(SyntaxClass::VarAccess(VarClass::Ident(node)));
879        }
880    }
881
882    // Moves to the first ancestor that is an expression.
883    let ancestor = first_ancestor_expr(node)?;
884    crate::log_debug_ct!("first_ancestor_expr: {ancestor:?}");
885
886    // Unwraps all parentheses to get the actual expression.
887    let adjusted = adjust_expr(ancestor)?;
888    crate::log_debug_ct!("adjust_expr: {adjusted:?}");
889
890    // Identifies convenient expression kinds.
891    let expr = adjusted.cast::<ast::Expr>()?;
892    Some(match expr {
893        ast::Expr::Label(..) => SyntaxClass::label(adjusted),
894        ast::Expr::Ref(..) => SyntaxClass::Ref {
895            node: adjusted,
896            suffix_colon: false,
897        },
898        ast::Expr::FuncCall(call) => SyntaxClass::Callee(adjusted.find(call.callee().span())?),
899        ast::Expr::SetRule(set) => SyntaxClass::Callee(adjusted.find(set.target().span())?),
900        ast::Expr::Ident(..) | ast::Expr::MathIdent(..) => {
901            SyntaxClass::VarAccess(VarClass::Ident(adjusted))
902        }
903        ast::Expr::FieldAccess(..) => SyntaxClass::VarAccess(VarClass::FieldAccess(adjusted)),
904        ast::Expr::Str(..) => {
905            let parent = adjusted.parent()?;
906            if parent.kind() == SyntaxKind::ModuleImport {
907                SyntaxClass::ImportPath(adjusted)
908            } else if parent.kind() == SyntaxKind::ModuleInclude {
909                SyntaxClass::IncludePath(adjusted)
910            } else {
911                SyntaxClass::Normal(adjusted.kind(), adjusted)
912            }
913        }
914        _ if expr.hash()
915            || matches!(adjusted.kind(), SyntaxKind::MathIdent | SyntaxKind::Error) =>
916        {
917            SyntaxClass::Normal(adjusted.kind(), adjusted)
918        }
919        _ => return None,
920    })
921}
922
923/// Checks if the node might be in code trivia. This is a bit internal so please
924/// check the caller to understand it.
925fn possible_in_code_trivia(kind: SyntaxKind) -> bool {
926    !matches!(
927        interpret_mode_at_kind(kind),
928        Some(InterpretMode::Markup | InterpretMode::Math | InterpretMode::Comment)
929    )
930}
931
932/// Classes of arguments that can be operated on by IDE functionality.
933#[derive(Debug, Clone)]
934pub enum ArgClass<'a> {
935    /// A positional argument.
936    Positional {
937        /// The spread arguments met before the positional argument.
938        spreads: EcoVec<LinkedNode<'a>>,
939        /// The index of the positional argument.
940        positional: usize,
941        /// Whether the positional argument is a spread argument.
942        is_spread: bool,
943    },
944    /// A named argument.
945    Named(LinkedNode<'a>),
946}
947
948impl ArgClass<'_> {
949    /// Creates the class refer to the first positional argument.
950    pub fn first_positional() -> Self {
951        ArgClass::Positional {
952            spreads: EcoVec::new(),
953            positional: 0,
954            is_spread: false,
955        }
956    }
957}
958
959// todo: check if we can merge `SurroundingSyntax` and `SyntaxContext`?
960/// Classes of syntax context (outer syntax) that can be operated on by IDE
961#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash, strum::EnumIter)]
962pub enum SurroundingSyntax {
963    /// Regular syntax.
964    Regular,
965    /// Content in a string.
966    StringContent,
967    /// The cursor is directly on the selector of a show rule.
968    Selector,
969    /// The cursor is directly on the transformation of a show rule.
970    ShowTransform,
971    /// The cursor is directly on the import list.
972    ImportList,
973    /// The cursor is directly on the set rule.
974    SetRule,
975    /// The cursor is directly on the parameter list.
976    ParamList,
977}
978
979/// Determines the surrounding syntax of the node at the given position.
980pub fn surrounding_syntax(node: &LinkedNode) -> SurroundingSyntax {
981    check_previous_syntax(node)
982        .or_else(|| check_surrounding_syntax(node))
983        .unwrap_or(SurroundingSyntax::Regular)
984}
985
986fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option<SurroundingSyntax> {
987    use SurroundingSyntax::*;
988    let mut met_args = false;
989
990    if matches!(leaf.kind(), SyntaxKind::Str) {
991        return Some(StringContent);
992    }
993
994    while let Some(parent) = leaf.parent() {
995        crate::log_debug_ct!(
996            "check_surrounding_syntax: {:?}::{:?}",
997            parent.kind(),
998            leaf.kind()
999        );
1000        match parent.kind() {
1001            SyntaxKind::CodeBlock
1002            | SyntaxKind::ContentBlock
1003            | SyntaxKind::Equation
1004            | SyntaxKind::Closure => {
1005                return Some(Regular);
1006            }
1007            SyntaxKind::ImportItemPath
1008            | SyntaxKind::ImportItems
1009            | SyntaxKind::RenamedImportItem => {
1010                return Some(ImportList);
1011            }
1012            SyntaxKind::ModuleImport => {
1013                let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
1014                let Some(colon) = colon else {
1015                    return Some(Regular);
1016                };
1017
1018                if leaf.offset() >= colon.offset() {
1019                    return Some(ImportList);
1020                } else {
1021                    return Some(Regular);
1022                }
1023            }
1024            SyntaxKind::Named => {
1025                let colon = parent.children().find(|s| s.kind() == SyntaxKind::Colon);
1026                let Some(colon) = colon else {
1027                    return Some(Regular);
1028                };
1029
1030                return if leaf.offset() >= colon.offset() {
1031                    Some(Regular)
1032                } else if node_ancestors(leaf).any(|n| n.kind() == SyntaxKind::Params) {
1033                    Some(ParamList)
1034                } else {
1035                    Some(Regular)
1036                };
1037            }
1038            SyntaxKind::Params => {
1039                return Some(ParamList);
1040            }
1041            SyntaxKind::Args => {
1042                met_args = true;
1043            }
1044            SyntaxKind::SetRule => {
1045                let rule = parent.get().cast::<ast::SetRule>()?;
1046                if met_args || enclosed_by(parent, rule.condition().map(|s| s.span()), leaf) {
1047                    return Some(Regular);
1048                } else {
1049                    return Some(SetRule);
1050                }
1051            }
1052            SyntaxKind::ShowRule => {
1053                if met_args {
1054                    return Some(Regular);
1055                }
1056
1057                let rule = parent.get().cast::<ast::ShowRule>()?;
1058                let colon = rule
1059                    .to_untyped()
1060                    .children()
1061                    .find(|s| s.kind() == SyntaxKind::Colon);
1062                let Some(colon) = colon.and_then(|colon| parent.find(colon.span())) else {
1063                    // incomplete show rule
1064                    return Some(Selector);
1065                };
1066
1067                if leaf.offset() >= colon.offset() {
1068                    return Some(ShowTransform);
1069                } else {
1070                    return Some(Selector); // query's first argument
1071                }
1072            }
1073            _ => {}
1074        }
1075
1076        leaf = parent;
1077    }
1078
1079    None
1080}
1081
1082/// Checks the previous syntax of the node.
1083fn check_previous_syntax(leaf: &LinkedNode) -> Option<SurroundingSyntax> {
1084    let mut leaf = leaf.clone();
1085    if leaf.kind().is_trivia() {
1086        leaf = leaf.prev_sibling()?;
1087    }
1088    if matches!(
1089        leaf.kind(),
1090        SyntaxKind::ShowRule
1091            | SyntaxKind::SetRule
1092            | SyntaxKind::ModuleImport
1093            | SyntaxKind::ModuleInclude
1094    ) {
1095        return check_surrounding_syntax(&leaf.rightmost_leaf()?);
1096    }
1097
1098    if matches!(leaf.kind(), SyntaxKind::Show) {
1099        return Some(SurroundingSyntax::Selector);
1100    }
1101    if matches!(leaf.kind(), SyntaxKind::Set) {
1102        return Some(SurroundingSyntax::SetRule);
1103    }
1104
1105    None
1106}
1107
1108/// Checks if the node is enclosed by the given span.
1109fn enclosed_by(parent: &LinkedNode, s: Option<Span>, leaf: &LinkedNode) -> bool {
1110    s.and_then(|s| parent.find(s)?.find(leaf.span())).is_some()
1111}
1112
1113/// Classes of syntax context (outer syntax) that can be operated on by IDE
1114/// functionality.
1115///
1116/// A syntax context is either a [`SyntaxClass`] or other things.
1117/// One thing is not necessary to refer to some exact node. For example, a
1118/// cursor moving after some comma in a function call is identified as a
1119/// [`SyntaxContext::Arg`].
1120#[derive(Debug, Clone)]
1121pub enum SyntaxContext<'a> {
1122    /// A cursor on an argument.
1123    Arg {
1124        /// The callee node.
1125        callee: LinkedNode<'a>,
1126        /// The arguments node.
1127        args: LinkedNode<'a>,
1128        /// The argument target pointed by the cursor.
1129        target: ArgClass<'a>,
1130        /// Whether the callee is a set rule.
1131        is_set: bool,
1132    },
1133    /// A cursor on an element in an array or dictionary literal.
1134    Element {
1135        /// The container node.
1136        container: LinkedNode<'a>,
1137        /// The element target pointed by the cursor.
1138        target: ArgClass<'a>,
1139    },
1140    /// A cursor on a parenthesized expression.
1141    Paren {
1142        /// The parenthesized expression node.
1143        container: LinkedNode<'a>,
1144        /// Whether the cursor is on the left side of the parenthesized
1145        /// expression.
1146        is_before: bool,
1147    },
1148    /// A variable access expression.
1149    ///
1150    /// It can be either an identifier or a field access.
1151    VarAccess(VarClass<'a>),
1152    /// A cursor on an import path.
1153    ImportPath(LinkedNode<'a>),
1154    /// A cursor on an include path.
1155    IncludePath(LinkedNode<'a>),
1156    /// A cursor on a label.
1157    Label {
1158        /// The label node.
1159        node: LinkedNode<'a>,
1160        /// Whether the label is converted from an error node.
1161        is_error: bool,
1162    },
1163    /// A (content) reference expression.
1164    Ref {
1165        /// The node of the reference.
1166        node: LinkedNode<'a>,
1167        /// A colon after a reference expression, for example, `@a:|` or
1168        /// `@a:b:|`.
1169        suffix_colon: bool,
1170    },
1171    /// A cursor on a `@` text.
1172    At {
1173        /// The node of the `@` text.
1174        node: LinkedNode<'a>,
1175    },
1176    /// A cursor on a normal [`SyntaxClass`].
1177    Normal(LinkedNode<'a>),
1178}
1179
1180impl<'a> SyntaxContext<'a> {
1181    /// Gets the node of the cursor class.
1182    pub fn node(&self) -> Option<LinkedNode<'a>> {
1183        Some(match self {
1184            SyntaxContext::Arg { target, .. } | SyntaxContext::Element { target, .. } => {
1185                match target {
1186                    ArgClass::Positional { .. } => return None,
1187                    ArgClass::Named(node) => node.clone(),
1188                }
1189            }
1190            SyntaxContext::VarAccess(cls) => cls.node().clone(),
1191            SyntaxContext::Paren { container, .. } => container.clone(),
1192            SyntaxContext::Label { node, .. }
1193            | SyntaxContext::Ref { node, .. }
1194            | SyntaxContext::At { node, .. }
1195            | SyntaxContext::ImportPath(node)
1196            | SyntaxContext::IncludePath(node)
1197            | SyntaxContext::Normal(node) => node.clone(),
1198        })
1199    }
1200
1201    /// Gets the argument container node.
1202    pub fn arg_container(&self) -> Option<&LinkedNode<'a>> {
1203        match self {
1204            Self::Arg { args, .. }
1205            | Self::Element {
1206                container: args, ..
1207            } => Some(args),
1208            Self::Paren { container, .. } => Some(container),
1209            _ => None,
1210        }
1211    }
1212}
1213
1214/// Kind of argument source.
1215#[derive(Debug)]
1216enum ArgSourceKind {
1217    /// An argument in a function call.
1218    Call,
1219    /// An argument (element) in an array literal.
1220    Array,
1221    /// An argument (element) in a dictionary literal.
1222    Dict,
1223}
1224
1225/// Classifies the context (outer syntax) of the node by the outer node that
1226/// can be operated on by IDE functionality.
1227pub fn classify_context_outer<'a>(
1228    outer: LinkedNode<'a>,
1229    node: LinkedNode<'a>,
1230) -> Option<SyntaxContext<'a>> {
1231    use SyntaxClass::*;
1232    let context_syntax = classify_syntax(outer.clone(), node.offset())?;
1233    let node_syntax = classify_syntax(node.clone(), node.offset())?;
1234
1235    match context_syntax {
1236        Callee(callee)
1237            if matches!(node_syntax, Normal(..) | Label { .. } | Ref { .. })
1238                && !matches!(node_syntax, Callee(..)) =>
1239        {
1240            let parent = callee.parent()?;
1241            let args = match parent.cast::<ast::Expr>() {
1242                Some(ast::Expr::FuncCall(call)) => call.args(),
1243                Some(ast::Expr::SetRule(set)) => set.args(),
1244                _ => return None,
1245            };
1246            let args = parent.find(args.span())?;
1247
1248            let is_set = parent.kind() == SyntaxKind::SetRule;
1249            let arg_target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1250            Some(SyntaxContext::Arg {
1251                callee,
1252                args,
1253                target: arg_target,
1254                is_set,
1255            })
1256        }
1257        _ => None,
1258    }
1259}
1260
1261/// Classifies the context (outer syntax) of the node that can be operated on
1262/// by IDE functionality.
1263pub fn classify_context(node: LinkedNode<'_>, cursor: Option<usize>) -> Option<SyntaxContext<'_>> {
1264    let mut node = node;
1265    if node.kind().is_trivia() && node.parent_kind().is_some_and(possible_in_code_trivia) {
1266        loop {
1267            node = node.prev_sibling()?;
1268
1269            if !node.kind().is_trivia() {
1270                break;
1271            }
1272        }
1273    }
1274
1275    let cursor = cursor.unwrap_or_else(|| node.offset());
1276    let syntax = classify_syntax(node.clone(), cursor)?;
1277
1278    let normal_syntax = match syntax {
1279        SyntaxClass::Callee(callee) => {
1280            return callee_context(callee, node);
1281        }
1282        SyntaxClass::Label { node, is_error } => {
1283            return Some(SyntaxContext::Label { node, is_error });
1284        }
1285        SyntaxClass::Ref { node, suffix_colon } => {
1286            return Some(SyntaxContext::Ref { node, suffix_colon });
1287        }
1288        SyntaxClass::At { node } => {
1289            return Some(SyntaxContext::At { node });
1290        }
1291        SyntaxClass::ImportPath(node) => {
1292            return Some(SyntaxContext::ImportPath(node));
1293        }
1294        SyntaxClass::IncludePath(node) => {
1295            return Some(SyntaxContext::IncludePath(node));
1296        }
1297        syntax => syntax,
1298    };
1299
1300    let Some(mut node_parent) = node.parent().cloned() else {
1301        return Some(SyntaxContext::Normal(node));
1302    };
1303
1304    while let SyntaxKind::Named | SyntaxKind::Colon = node_parent.kind() {
1305        let Some(parent) = node_parent.parent() else {
1306            return Some(SyntaxContext::Normal(node));
1307        };
1308        node_parent = parent.clone();
1309    }
1310
1311    match node_parent.kind() {
1312        SyntaxKind::Args => {
1313            let callee = node_ancestors(&node_parent).find_map(|ancestor| {
1314                let span = match ancestor.cast::<ast::Expr>()? {
1315                    ast::Expr::FuncCall(call) => call.callee().span(),
1316                    ast::Expr::SetRule(set) => set.target().span(),
1317                    _ => return None,
1318                };
1319                ancestor.find(span)
1320            })?;
1321
1322            let param_node = match node.kind() {
1323                SyntaxKind::Ident
1324                    if matches!(
1325                        node.parent_kind().zip(node.next_sibling_kind()),
1326                        Some((SyntaxKind::Named, SyntaxKind::Colon))
1327                    ) =>
1328                {
1329                    node
1330                }
1331                _ if matches!(node.parent_kind(), Some(SyntaxKind::Named)) => {
1332                    node.parent().cloned()?
1333                }
1334                _ => node,
1335            };
1336
1337            callee_context(callee, param_node)
1338        }
1339        SyntaxKind::Array | SyntaxKind::Dict => {
1340            let element_target = arg_context(
1341                node_parent.clone(),
1342                node.clone(),
1343                match node_parent.kind() {
1344                    SyntaxKind::Array => ArgSourceKind::Array,
1345                    SyntaxKind::Dict => ArgSourceKind::Dict,
1346                    _ => unreachable!(),
1347                },
1348            )?;
1349            Some(SyntaxContext::Element {
1350                container: node_parent.clone(),
1351                target: element_target,
1352            })
1353        }
1354        SyntaxKind::Parenthesized => {
1355            let is_before = node.offset() <= node_parent.offset() + 1;
1356            Some(SyntaxContext::Paren {
1357                container: node_parent.clone(),
1358                is_before,
1359            })
1360        }
1361        _ => Some(match normal_syntax {
1362            SyntaxClass::VarAccess(v) => SyntaxContext::VarAccess(v),
1363            normal_syntax => SyntaxContext::Normal(normal_syntax.node().clone()),
1364        }),
1365    }
1366}
1367
1368/// Classifies the context of the callee node.
1369fn callee_context<'a>(callee: LinkedNode<'a>, node: LinkedNode<'a>) -> Option<SyntaxContext<'a>> {
1370    let parent = callee.parent()?;
1371    let args = match parent.cast::<ast::Expr>() {
1372        Some(ast::Expr::FuncCall(call)) => call.args(),
1373        Some(ast::Expr::SetRule(set)) => set.args(),
1374        _ => return None,
1375    };
1376    let args = parent.find(args.span())?;
1377
1378    let mut parent = &node;
1379    loop {
1380        use SyntaxKind::*;
1381        match parent.kind() {
1382            ContentBlock | CodeBlock | Str | Raw | LineComment | BlockComment => {
1383                return Option::None;
1384            }
1385            Args if parent.range() == args.range() => {
1386                break;
1387            }
1388            _ => {}
1389        }
1390
1391        parent = parent.parent()?;
1392    }
1393
1394    let is_set = parent.kind() == SyntaxKind::SetRule;
1395    let target = arg_context(args.clone(), node, ArgSourceKind::Call)?;
1396    Some(SyntaxContext::Arg {
1397        callee,
1398        args,
1399        target,
1400        is_set,
1401    })
1402}
1403
1404/// Classifies the context of the argument node.
1405fn arg_context<'a>(
1406    args_node: LinkedNode<'a>,
1407    mut node: LinkedNode<'a>,
1408    param_kind: ArgSourceKind,
1409) -> Option<ArgClass<'a>> {
1410    if node.kind() == SyntaxKind::RightParen {
1411        node = node.prev_sibling()?;
1412    }
1413    match node.kind() {
1414        SyntaxKind::Named => {
1415            let param_ident = node.cast::<ast::Named>()?.name();
1416            Some(ArgClass::Named(args_node.find(param_ident.span())?))
1417        }
1418        SyntaxKind::Colon => {
1419            let prev = node.prev_leaf()?;
1420            let param_ident = prev.cast::<ast::Ident>()?;
1421            Some(ArgClass::Named(args_node.find(param_ident.span())?))
1422        }
1423        _ => {
1424            let parent = node.parent();
1425            if let Some(parent) = parent
1426                && parent.kind() == SyntaxKind::Named
1427            {
1428                let param_ident = parent.cast::<ast::Named>()?;
1429                let name = param_ident.name();
1430                let init = param_ident.expr();
1431                let init = parent.find(init.span())?;
1432                if init.range().contains(&node.offset()) {
1433                    let name = args_node.find(name.span())?;
1434                    return Some(ArgClass::Named(name));
1435                }
1436            }
1437
1438            let mut spreads = EcoVec::new();
1439            let mut positional = 0;
1440            let is_spread = node.kind() == SyntaxKind::Spread;
1441
1442            let args_before = args_node
1443                .children()
1444                .take_while(|arg| arg.range().end <= node.offset());
1445            match param_kind {
1446                ArgSourceKind::Call => {
1447                    for ch in args_before {
1448                        match ch.cast::<ast::Arg>() {
1449                            Some(ast::Arg::Pos(..)) => {
1450                                positional += 1;
1451                            }
1452                            Some(ast::Arg::Spread(..)) => {
1453                                spreads.push(ch);
1454                            }
1455                            Some(ast::Arg::Named(..)) | None => {}
1456                        }
1457                    }
1458                }
1459                ArgSourceKind::Array => {
1460                    for ch in args_before {
1461                        match ch.cast::<ast::ArrayItem>() {
1462                            Some(ast::ArrayItem::Pos(..)) => {
1463                                positional += 1;
1464                            }
1465                            Some(ast::ArrayItem::Spread(..)) => {
1466                                spreads.push(ch);
1467                            }
1468                            _ => {}
1469                        }
1470                    }
1471                }
1472                ArgSourceKind::Dict => {
1473                    for ch in args_before {
1474                        if let Some(ast::DictItem::Spread(..)) = ch.cast::<ast::DictItem>() {
1475                            spreads.push(ch);
1476                        }
1477                    }
1478                }
1479            }
1480
1481            Some(ArgClass::Positional {
1482                spreads,
1483                positional,
1484                is_spread,
1485            })
1486        }
1487    }
1488}
1489
1490/// The cursor is on an invalid position for completion.
1491pub enum BadCompletionCursor {
1492    /// The cursor is outside of the argument list.
1493    ArgListPos,
1494}
1495
1496/// Checks if the cursor is on an invalid position for completion.
1497pub fn bad_completion_cursor(
1498    syntax: Option<&SyntaxClass>,
1499    syntax_context: Option<&SyntaxContext>,
1500    leaf: &LinkedNode,
1501) -> Option<BadCompletionCursor> {
1502    // The cursor is on `f()|`
1503    if (matches!(syntax, Some(SyntaxClass::Callee(..))) && {
1504        syntax_context
1505            .and_then(SyntaxContext::arg_container)
1506            .is_some_and(|container| {
1507                container.rightmost_leaf().map(|s| s.offset()) == Some(leaf.offset())
1508            })
1509        // The cursor is on `f[]|`
1510    }) || (matches!(
1511        syntax,
1512        Some(SyntaxClass::Normal(SyntaxKind::ContentBlock, _))
1513    ) && matches!(leaf.kind(), SyntaxKind::RightBracket))
1514    {
1515        return Some(BadCompletionCursor::ArgListPos);
1516    }
1517
1518    None
1519}
1520
1521#[cfg(test)]
1522mod tests {
1523    use super::*;
1524    use insta::assert_snapshot;
1525    use typst::syntax::{Side, Source, is_newline};
1526
1527    fn map_node(source: &str, mapper: impl Fn(&LinkedNode, usize) -> char) -> String {
1528        let source = Source::detached(source.to_owned());
1529        let root = LinkedNode::new(source.root());
1530        let mut output_mapping = String::new();
1531
1532        let mut cursor = 0;
1533        for ch in source.text().chars() {
1534            cursor += ch.len_utf8();
1535            if is_newline(ch) {
1536                output_mapping.push(ch);
1537                continue;
1538            }
1539
1540            output_mapping.push(mapper(&root, cursor));
1541        }
1542
1543        source
1544            .text()
1545            .lines()
1546            .zip(output_mapping.lines())
1547            .flat_map(|(a, b)| [a, "\n", b, "\n"])
1548            .collect::<String>()
1549    }
1550
1551    fn map_syntax(source: &str) -> String {
1552        map_node(source, |root, cursor| {
1553            let node = root.leaf_at(cursor, Side::Before);
1554            let kind = node.and_then(|node| classify_syntax(node, cursor));
1555            match kind {
1556                Some(SyntaxClass::VarAccess(..)) => 'v',
1557                Some(SyntaxClass::Normal(..)) => 'n',
1558                Some(SyntaxClass::Label { .. }) => 'l',
1559                Some(SyntaxClass::Ref { .. }) => 'r',
1560                Some(SyntaxClass::At { .. }) => 'r',
1561                Some(SyntaxClass::Callee(..)) => 'c',
1562                Some(SyntaxClass::ImportPath(..)) => 'i',
1563                Some(SyntaxClass::IncludePath(..)) => 'I',
1564                None => ' ',
1565            }
1566        })
1567    }
1568
1569    fn map_context(source: &str) -> String {
1570        map_node(source, |root, cursor| {
1571            let node = root.leaf_at(cursor, Side::Before);
1572            let kind = node.and_then(|node| classify_context(node, Some(cursor)));
1573            match kind {
1574                Some(SyntaxContext::Arg { .. }) => 'p',
1575                Some(SyntaxContext::Element { .. }) => 'e',
1576                Some(SyntaxContext::Paren { .. }) => 'P',
1577                Some(SyntaxContext::VarAccess { .. }) => 'v',
1578                Some(SyntaxContext::ImportPath(..)) => 'i',
1579                Some(SyntaxContext::IncludePath(..)) => 'I',
1580                Some(SyntaxContext::Label { .. }) => 'l',
1581                Some(SyntaxContext::Ref { .. }) => 'r',
1582                Some(SyntaxContext::At { .. }) => 'r',
1583                Some(SyntaxContext::Normal(..)) => 'n',
1584                None => ' ',
1585            }
1586        })
1587    }
1588
1589    #[test]
1590    fn test_get_syntax() {
1591        assert_snapshot!(map_syntax(r#"#let x = 1  
1592Text
1593= Heading #let y = 2;  
1594== Heading"#).trim(), @r"
1595        #let x = 1  
1596         nnnnvvnnn  
1597        Text
1598            
1599        = Heading #let y = 2;  
1600                   nnnnvvnnn   
1601        == Heading
1602        ");
1603        assert_snapshot!(map_syntax(r#"#let f(x);"#).trim(), @r"
1604        #let f(x);
1605         nnnnv v
1606        ");
1607        assert_snapshot!(map_syntax(r#"#{
1608  calc.  
1609}"#).trim(), @r"
1610        #{
1611         n
1612          calc.  
1613        nnvvvvvnn
1614        }
1615        n
1616        ");
1617    }
1618
1619    #[test]
1620    fn test_get_context() {
1621        assert_snapshot!(map_context(r#"#let x = 1  
1622Text
1623= Heading #let y = 2;  
1624== Heading"#).trim(), @r"
1625        #let x = 1  
1626         nnnnvvnnn  
1627        Text
1628            
1629        = Heading #let y = 2;  
1630                   nnnnvvnnn   
1631        == Heading
1632        ");
1633        assert_snapshot!(map_context(r#"#let f(x);"#).trim(), @r"
1634        #let f(x);
1635         nnnnv v
1636        ");
1637        assert_snapshot!(map_context(r#"#f(1, 2)   Test"#).trim(), @r"
1638        #f(1, 2)   Test
1639         vpppppp
1640        ");
1641        assert_snapshot!(map_context(r#"#()   Test"#).trim(), @r"
1642        #()   Test
1643         ee
1644        ");
1645        assert_snapshot!(map_context(r#"#(1)   Test"#).trim(), @r"
1646        #(1)   Test
1647         PPP
1648        ");
1649        assert_snapshot!(map_context(r#"#(a: 1)   Test"#).trim(), @r"
1650        #(a: 1)   Test
1651         eeeeee
1652        ");
1653        assert_snapshot!(map_context(r#"#(1, 2)   Test"#).trim(), @r"
1654        #(1, 2)   Test
1655         eeeeee
1656        ");
1657        assert_snapshot!(map_context(r#"#(1, 2)  
1658  Test"#).trim(), @r"
1659        #(1, 2)  
1660         eeeeee  
1661          Test
1662        ");
1663    }
1664
1665    #[test]
1666    fn ref_syntax() {
1667        assert_snapshot!(map_syntax("@ab:"), @r###"
1668        @ab:
1669        rrrr
1670        "###);
1671        assert_snapshot!(map_syntax("@"), @r"
1672        @
1673        r
1674        ");
1675        assert_snapshot!(map_syntax("@;"), @r"
1676        @;
1677        r
1678        ");
1679        assert_snapshot!(map_syntax("@ t"), @r"
1680        @ t
1681        r
1682        ");
1683        assert_snapshot!(map_syntax("@ab"), @r###"
1684        @ab
1685        rrr
1686        "###);
1687        assert_snapshot!(map_syntax("@ab:"), @r###"
1688        @ab:
1689        rrrr
1690        "###);
1691        assert_snapshot!(map_syntax("@ab:ab"), @r###"
1692        @ab:ab
1693        rrrrrr
1694        "###);
1695        assert_snapshot!(map_syntax("@ab:ab:"), @r###"
1696        @ab:ab:
1697        rrrrrrr
1698        "###);
1699        assert_snapshot!(map_syntax("@ab:ab:ab"), @r###"
1700        @ab:ab:ab
1701        rrrrrrrrr
1702        "###);
1703        assert_snapshot!(map_syntax("@ab[]:"), @r###"
1704        @ab[]:
1705        rrrnn
1706        "###);
1707        assert_snapshot!(map_syntax("@ab[ab]:"), @r###"
1708        @ab[ab]:
1709        rrrn  n
1710        "###);
1711        assert_snapshot!(map_syntax("@ab :ab: ab"), @r###"
1712        @ab :ab: ab
1713        rrr
1714        "###);
1715        assert_snapshot!(map_syntax("@ab :ab:ab"), @r###"
1716        @ab :ab:ab
1717        rrr
1718        "###);
1719    }
1720
1721    fn access_node(s: &str, cursor: i32) -> String {
1722        access_node_(s, cursor).unwrap_or_default()
1723    }
1724
1725    fn access_node_(s: &str, cursor: i32) -> Option<String> {
1726        access_var(s, cursor, |_source, var| {
1727            Some(var.accessed_node()?.get().clone().into_text().into())
1728        })
1729    }
1730
1731    fn access_field(s: &str, cursor: i32) -> String {
1732        access_field_(s, cursor).unwrap_or_default()
1733    }
1734
1735    fn access_field_(s: &str, cursor: i32) -> Option<String> {
1736        access_var(s, cursor, |source, var| {
1737            let field = var.accessing_field()?;
1738            Some(match field {
1739                FieldClass::Field(ident) => format!("Field: {}", ident.text()),
1740                FieldClass::DotSuffix(span_offset) => {
1741                    let offset = source.find(span_offset.span)?.offset() + span_offset.offset;
1742                    format!("DotSuffix: {offset:?}")
1743                }
1744            })
1745        })
1746    }
1747
1748    fn access_var(
1749        s: &str,
1750        cursor: i32,
1751        f: impl FnOnce(&Source, VarClass) -> Option<String>,
1752    ) -> Option<String> {
1753        let cursor = if cursor < 0 {
1754            s.len() as i32 + cursor
1755        } else {
1756            cursor
1757        };
1758        let source = Source::detached(s.to_owned());
1759        let root = LinkedNode::new(source.root());
1760        let node = root.leaf_at(cursor as usize, Side::Before)?;
1761        let syntax = classify_syntax(node, cursor as usize)?;
1762        let SyntaxClass::VarAccess(var) = syntax else {
1763            return None;
1764        };
1765        f(&source, var)
1766    }
1767
1768    #[test]
1769    fn test_access_field() {
1770        assert_snapshot!(access_field("#(a.b)", 5), @r"Field: b");
1771        assert_snapshot!(access_field("#a.", 3), @"DotSuffix: 3");
1772        assert_snapshot!(access_field("$a.$", 3), @"DotSuffix: 3");
1773        assert_snapshot!(access_field("#(a.)", 4), @"DotSuffix: 4");
1774        assert_snapshot!(access_node("#(a..b)", 4), @"a");
1775        assert_snapshot!(access_field("#(a..b)", 4), @"DotSuffix: 4");
1776        assert_snapshot!(access_node("#(a..b())", 4), @"a");
1777        assert_snapshot!(access_field("#(a..b())", 4), @"DotSuffix: 4");
1778    }
1779
1780    #[test]
1781    fn test_code_access() {
1782        assert_snapshot!(access_node("#{`a`.}", 6), @"`a`");
1783        assert_snapshot!(access_field("#{`a`.}", 6), @"DotSuffix: 6");
1784        assert_snapshot!(access_node("#{$a$.}", 6), @"$a$");
1785        assert_snapshot!(access_field("#{$a$.}", 6), @"DotSuffix: 6");
1786        assert_snapshot!(access_node("#{\"a\".}", 6), @"\"a\"");
1787        assert_snapshot!(access_field("#{\"a\".}", 6), @"DotSuffix: 6");
1788        assert_snapshot!(access_node("#{<a>.}", 6), @"<a>");
1789        assert_snapshot!(access_field("#{<a>.}", 6), @"DotSuffix: 6");
1790    }
1791
1792    #[test]
1793    fn test_markup_access() {
1794        assert_snapshot!(access_field("_a_.", 4), @"");
1795        assert_snapshot!(access_field("*a*.", 4), @"");
1796        assert_snapshot!(access_field("`a`.", 4), @"");
1797        assert_snapshot!(access_field("$a$.", 4), @"");
1798        assert_snapshot!(access_field("\"a\".", 4), @"");
1799        assert_snapshot!(access_field("@a.", 3), @"");
1800        assert_snapshot!(access_field("<a>.", 4), @"");
1801    }
1802
1803    #[test]
1804    fn test_markup_chain_access() {
1805        assert_snapshot!(access_node("#a.b.", 5), @"a.b");
1806        assert_snapshot!(access_field("#a.b.", 5), @"DotSuffix: 5");
1807        assert_snapshot!(access_node("#a.b.c.", 7), @"a.b.c");
1808        assert_snapshot!(access_field("#a.b.c.", 7), @"DotSuffix: 7");
1809        assert_snapshot!(access_node("#context a.", 11), @"a");
1810        assert_snapshot!(access_field("#context a.", 11), @"DotSuffix: 11");
1811        assert_snapshot!(access_node("#context a.b.", 13), @"a.b");
1812        assert_snapshot!(access_field("#context a.b.", 13), @"DotSuffix: 13");
1813
1814        assert_snapshot!(access_node("#a.at(1).", 9), @"a.at(1)");
1815        assert_snapshot!(access_field("#a.at(1).", 9), @"DotSuffix: 9");
1816        assert_snapshot!(access_node("#context a.at(1).", 17), @"a.at(1)");
1817        assert_snapshot!(access_field("#context a.at(1).", 17), @"DotSuffix: 17");
1818
1819        assert_snapshot!(access_node("#a.at(1).c.", 11), @"a.at(1).c");
1820        assert_snapshot!(access_field("#a.at(1).c.", 11), @"DotSuffix: 11");
1821        assert_snapshot!(access_node("#context a.at(1).c.", 19), @"a.at(1).c");
1822        assert_snapshot!(access_field("#context a.at(1).c.", 19), @"DotSuffix: 19");
1823    }
1824
1825    #[test]
1826    fn test_hash_access() {
1827        assert_snapshot!(access_node("#a.", 3), @"a");
1828        assert_snapshot!(access_field("#a.", 3), @"DotSuffix: 3");
1829        assert_snapshot!(access_node("#(a).", 5), @"(a)");
1830        assert_snapshot!(access_field("#(a).", 5), @"DotSuffix: 5");
1831        assert_snapshot!(access_node("#`a`.", 5), @"`a`");
1832        assert_snapshot!(access_field("#`a`.", 5), @"DotSuffix: 5");
1833        assert_snapshot!(access_node("#$a$.", 5), @"$a$");
1834        assert_snapshot!(access_field("#$a$.", 5), @"DotSuffix: 5");
1835        assert_snapshot!(access_node("#(a,).", 6), @"(a,)");
1836        assert_snapshot!(access_field("#(a,).", 6), @"DotSuffix: 6");
1837    }
1838}