diff --git a/crates/tinymist-analysis/src/syntax/matcher.rs b/crates/tinymist-analysis/src/syntax/matcher.rs index 7b4a8929..a99c4119 100644 --- a/crates/tinymist-analysis/src/syntax/matcher.rs +++ b/crates/tinymist-analysis/src/syntax/matcher.rs @@ -71,7 +71,37 @@ pub fn node_ancestors<'a, 'b>( /// Finds the first ancestor node that is an expression. pub fn first_ancestor_expr(node: LinkedNode) -> Option { - node_ancestors(&node).find(|n| n.is::()).cloned() + node_ancestors(&node) + .find(|n| n.is::()) + .map(|mut node| { + while matches!(node.kind(), SyntaxKind::Ident | SyntaxKind::MathIdent) { + let Some(parent) = node.parent() else { + return node; + }; + + let Some(field_access) = parent.cast::() else { + return node; + }; + + let dot = parent + .children() + .find(|n| matches!(n.kind(), SyntaxKind::Dot)); + + // Since typst matches `field()` by `case_last_match`, when the field access + // `x.` (`Ident(x).Error("")`), it will match the `x` as the + // field. We need to check dot position to filter out such cases. + if dot.is_some_and(|dot| { + dot.offset() <= node.offset() && field_access.field().span() == node.span() + }) { + node = parent; + } else { + return node; + } + } + + node + }) + .cloned() } /// A node that is an ancestor of the given node or the previous sibling @@ -715,7 +745,10 @@ pub fn classify_syntax(node: LinkedNode, cursor: usize) -> Option