fix: correct field access dot target (#1489)

* fix: correct field access dot target

* test: add cases about #1267
This commit is contained in:
Myriad-Dreamin 2025-03-11 23:33:46 +08:00 committed by GitHub
parent db1ff20a6d
commit 4bf6cdb9ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 182 additions and 2 deletions

View file

@ -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<LinkedNode> {
node_ancestors(&node).find(|n| n.is::<ast::Expr>()).cloned()
node_ancestors(&node)
.find(|n| n.is::<ast::Expr>())
.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::<ast::FieldAccess>() 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<SyntaxClass<'_
matches!(mode, InterpretMode::Markup)
&& (matches!(
dot_target.kind(),
SyntaxKind::Ident | SyntaxKind::MathIdent | SyntaxKind::FuncCall
SyntaxKind::Ident
| SyntaxKind::MathIdent
| SyntaxKind::FieldAccess
| SyntaxKind::FuncCall
) || (matches!(
dot_target.prev_leaf().as_deref().map(SyntaxNode::kind),
Some(SyntaxKind::Hash)
@ -1534,13 +1567,24 @@ Text
#[test]
fn test_markup_chain_access() {
assert_snapshot!(access_node("#a.b.", 5), @"a.b");
assert_snapshot!(access_field("#a.b.", 5), @"DotSuffix: 5");
assert_snapshot!(access_node("#a.b.c.", 7), @"a.b.c");
assert_snapshot!(access_field("#a.b.c.", 7), @"DotSuffix: 7");
assert_snapshot!(access_node("#context a.", 11), @"a");
assert_snapshot!(access_field("#context a.", 11), @"DotSuffix: 11");
assert_snapshot!(access_node("#context a.b.", 13), @"a.b");
assert_snapshot!(access_field("#context a.b.", 13), @"DotSuffix: 13");
assert_snapshot!(access_node("#a.at(1).", 9), @"a.at(1)");
assert_snapshot!(access_field("#a.at(1).", 9), @"DotSuffix: 9");
assert_snapshot!(access_node("#context a.at(1).", 17), @"a.at(1)");
assert_snapshot!(access_field("#context a.at(1).", 17), @"DotSuffix: 17");
assert_snapshot!(access_node("#a.at(1).c.", 11), @"a.at(1).c");
assert_snapshot!(access_field("#a.at(1).c.", 11), @"DotSuffix: 11");
assert_snapshot!(access_node("#context a.at(1).c.", 19), @"a.at(1).c");
assert_snapshot!(access_field("#context a.at(1).c.", 19), @"DotSuffix: 19");
}
#[test]

View file

@ -0,0 +1,4 @@
/// contains: first
#let dict = (first: (second: "value"))
#dict.fi/* range 0..1 */

View file

@ -0,0 +1,4 @@
/// contains: second
#let dict = (first: (second: "value"))
#dict.first.sec/* range 0..1 */

View file

@ -0,0 +1,3 @@
/// contains: sin
#if std.calc./* range 0..1 */

View file

@ -0,0 +1,5 @@
/// contains: sin
#{
if std.calc./* range 0..1 */
}

View file

@ -0,0 +1,30 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (68..69)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/dict_chain_access.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 6,
"label": "first",
"textEdit": {
"newText": "first",
"range": {
"end": {
"character": 8,
"line": 3
},
"start": {
"character": 6,
"line": 3
}
}
}
}
]
}
]

View file

@ -0,0 +1,30 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (76..77)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/dict_chain_access2.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 6,
"label": "second",
"textEdit": {
"newText": "second",
"range": {
"end": {
"character": 15,
"line": 3
},
"start": {
"character": 12,
"line": 3
}
}
}
}
]
}
]

View file

@ -0,0 +1,30 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (32..33)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/markup_chain.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 3,
"label": "sin",
"textEdit": {
"newText": "sin(${1:})",
"range": {
"end": {
"character": 13,
"line": 2
},
"start": {
"character": 13,
"line": 2
}
}
}
}
]
}
]

View file

@ -0,0 +1,30 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (36..37)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/markup_chain2.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 3,
"label": "sin",
"textEdit": {
"newText": "sin(${1:})",
"range": {
"end": {
"character": 14,
"line": 3
},
"start": {
"character": 14,
"line": 3
}
}
}
}
]
}
]