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