Skip to content

Commit 782272f

Browse files
fix: add special syntax class for empty ref syntax (#2324)
closes #2319. This pull request refines the language analysis capabilities by introducing a dedicated syntax class for the standalone '@' symbol. Previously, this might have been ambiguously handled; now, it's explicitly recognized as an 'empty' reference. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 32309a7 commit 782272f

File tree

7 files changed

+95
-15
lines changed

7 files changed

+95
-15
lines changed

crates/tinymist-analysis/src/syntax/matcher.rs

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,11 @@ pub enum SyntaxClass<'a> {
652652
/// `@a:b:|`.
653653
suffix_colon: bool,
654654
},
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+
},
655660
/// A callee expression.
656661
Callee(LinkedNode<'a>),
657662
/// An import path expression.
@@ -685,6 +690,7 @@ impl<'a> SyntaxClass<'a> {
685690
SyntaxClass::VarAccess(cls) => cls.node(),
686691
SyntaxClass::Label { node, .. }
687692
| SyntaxClass::Ref { node, .. }
693+
| SyntaxClass::At { node, .. }
688694
| SyntaxClass::Callee(node)
689695
| SyntaxClass::ImportPath(node)
690696
| SyntaxClass::IncludePath(node)
@@ -847,10 +853,7 @@ pub fn classify_syntax(node: LinkedNode<'_>, cursor: usize) -> Option<SyntaxClas
847853
&& node.offset() + 1 == cursor
848854
&& node.text().starts_with('@')
849855
{
850-
return Some(SyntaxClass::Ref {
851-
node,
852-
suffix_colon: false,
853-
});
856+
return Some(SyntaxClass::At { node });
854857
}
855858

856859
// todo: check if we can remove Text here
@@ -1150,6 +1153,11 @@ pub enum SyntaxContext<'a> {
11501153
/// `@a:b:|`.
11511154
suffix_colon: bool,
11521155
},
1156+
/// A cursor on a `@` text.
1157+
At {
1158+
/// The node of the `@` text.
1159+
node: LinkedNode<'a>,
1160+
},
11531161
/// A cursor on a normal [`SyntaxClass`].
11541162
Normal(LinkedNode<'a>),
11551163
}
@@ -1168,6 +1176,7 @@ impl<'a> SyntaxContext<'a> {
11681176
SyntaxContext::Paren { container, .. } => container.clone(),
11691177
SyntaxContext::Label { node, .. }
11701178
| SyntaxContext::Ref { node, .. }
1179+
| SyntaxContext::At { node, .. }
11711180
| SyntaxContext::ImportPath(node)
11721181
| SyntaxContext::IncludePath(node)
11731182
| SyntaxContext::Normal(node) => node.clone(),
@@ -1261,6 +1270,9 @@ pub fn classify_context(node: LinkedNode<'_>, cursor: Option<usize>) -> Option<S
12611270
SyntaxClass::Ref { node, suffix_colon } => {
12621271
return Some(SyntaxContext::Ref { node, suffix_colon });
12631272
}
1273+
SyntaxClass::At { node } => {
1274+
return Some(SyntaxContext::At { node });
1275+
}
12641276
SyntaxClass::ImportPath(node) => {
12651277
return Some(SyntaxContext::ImportPath(node));
12661278
}
@@ -1530,6 +1542,7 @@ mod tests {
15301542
Some(SyntaxClass::Normal(..)) => 'n',
15311543
Some(SyntaxClass::Label { .. }) => 'l',
15321544
Some(SyntaxClass::Ref { .. }) => 'r',
1545+
Some(SyntaxClass::At { .. }) => 'r',
15331546
Some(SyntaxClass::Callee(..)) => 'c',
15341547
Some(SyntaxClass::ImportPath(..)) => 'i',
15351548
Some(SyntaxClass::IncludePath(..)) => 'I',
@@ -1551,6 +1564,7 @@ mod tests {
15511564
Some(SyntaxContext::IncludePath(..)) => 'I',
15521565
Some(SyntaxContext::Label { .. }) => 'l',
15531566
Some(SyntaxContext::Ref { .. }) => 'r',
1567+
Some(SyntaxContext::At { .. }) => 'r',
15541568
Some(SyntaxContext::Normal(..)) => 'n',
15551569
None => ' ',
15561570
}
@@ -1634,15 +1648,11 @@ Text
16341648
}
16351649

16361650
#[test]
1637-
fn ref_syntxax() {
1651+
fn ref_syntax() {
16381652
assert_snapshot!(map_syntax("@ab:"), @r###"
16391653
@ab:
16401654
rrrr
16411655
"###);
1642-
}
1643-
1644-
#[test]
1645-
fn ref_syntax() {
16461656
assert_snapshot!(map_syntax("@"), @r"
16471657
@
16481658
r
@@ -1651,6 +1661,10 @@ Text
16511661
@;
16521662
r
16531663
");
1664+
assert_snapshot!(map_syntax("@ t"), @r"
1665+
@ t
1666+
r
1667+
");
16541668
assert_snapshot!(map_syntax("@ab"), @r###"
16551669
@ab
16561670
rrr

crates/tinymist-query/src/analysis/completion.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,14 @@ impl<'a> CompletionCursor<'a> {
260260
return Some(SelectedNode::Ref(self.leaf.clone()));
261261
}
262262

263+
// @identifier
264+
// ^ from
265+
let is_from_ref = matches!(self.syntax, Some(SyntaxClass::At { .. }))
266+
&& self.leaf.offset() + 1 == self.from;
267+
if is_from_ref {
268+
return Some(SelectedNode::At(self.leaf.clone()));
269+
}
270+
263271
None
264272
})
265273
}
@@ -287,6 +295,7 @@ impl<'a> CompletionCursor<'a> {
287295
| SyntaxContext::Paren { .. }
288296
| SyntaxContext::Label { .. }
289297
| SyntaxContext::Ref { .. }
298+
| SyntaxContext::At { .. }
290299
| SyntaxContext::Normal(..),
291300
)
292301
| None => {}
@@ -342,8 +351,13 @@ impl<'a> CompletionCursor<'a> {
342351

343352
self.lsp_range_of(rng)
344353
}
345-
Some(SelectedNode::Ref(from_ref)) => {
346-
let mut rng = from_ref.range();
354+
Some(node @ (SelectedNode::At(from_ref) | SelectedNode::Ref(from_ref))) => {
355+
let mut rng = if matches!(node, SelectedNode::At(..)) {
356+
let offset = from_ref.offset();
357+
offset..offset + 1
358+
} else {
359+
from_ref.range()
360+
};
347361
if from_ref.text().starts_with('@') && !snippet.starts_with('@') {
348362
rng.start += 1;
349363
}
@@ -382,6 +396,8 @@ enum SelectedNode<'a> {
382396
Label(LinkedNode<'a>),
383397
/// Selects a reference, e.g. `@foobar|` or `@foo|bar`.
384398
Ref(LinkedNode<'a>),
399+
/// Selects a `@` text, e.g. `@|`.
400+
At(LinkedNode<'a>),
385401
}
386402

387403
/// Autocomplete a cursor position in a source file.
@@ -522,6 +538,7 @@ impl<'a> CompletionWorker<'a> {
522538
let _ = pair.complete_cursor();
523539

524540
// Filters
541+
// todo: reference filter
525542
if let Some(SelectedNode::Ident(from_ident)) = cursor.selected_node() {
526543
let ident_prefix = cursor.text[from_ident.offset()..cursor.cursor].to_string();
527544

@@ -641,10 +658,13 @@ impl CompletionPair<'_, '_, '_> {
641658
return Some(());
642659
}
643660
// todo: complete reference by type
644-
Some(SyntaxContext::Ref {
645-
node,
646-
suffix_colon: _,
647-
}) => {
661+
Some(
662+
SyntaxContext::Ref {
663+
node,
664+
suffix_colon: _,
665+
}
666+
| SyntaxContext::At { node },
667+
) => {
648668
self.cursor.from = node.offset() + 1;
649669
self.ref_completions();
650670
return Some(());

crates/tinymist-query/src/analysis/definition.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ pub fn definition(
124124
node: _,
125125
suffix_colon: true,
126126
}
127+
| SyntaxClass::At { node: _ }
127128
| SyntaxClass::Normal(..) => None,
128129
}
129130
}

crates/tinymist-query/src/analysis/post_tyck.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,7 @@ impl<'a> PostTypeChecker<'a> {
321321
| SyntaxContext::VarAccess(VarClass::DotAccess(node))
322322
| SyntaxContext::Label { node, .. }
323323
| SyntaxContext::Ref { node, .. }
324+
| SyntaxContext::At { node, .. }
324325
| SyntaxContext::Normal(node) => {
325326
let label_or_ref_ty = match cursor {
326327
SyntaxContext::Label { is_error: true, .. } => {
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/// contains:test
2+
/// compile: true
3+
4+
#set heading(numbering: "1.1")
5+
6+
#heading(numbering: "1.1", level: 1)[Test] <test>
7+
8+
= H <tes>
9+
10+
There is a reference here @ w /* range -3..-2 */
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
source: crates/tinymist-query/src/completion.rs
3+
description: Completion on (158..159)
4+
expression: "JsonRepr::new_pure(results)"
5+
input_file: crates/tinymist-query/src/fixtures/completion/ref_empty.typ
6+
---
7+
[
8+
{
9+
"isIncomplete": false,
10+
"items": [
11+
{
12+
"kind": 18,
13+
"label": "test",
14+
"labelDetails": {
15+
"description": "Test"
16+
},
17+
"textEdit": {
18+
"newText": "test",
19+
"range": {
20+
"end": {
21+
"character": 27,
22+
"line": 9
23+
},
24+
"start": {
25+
"character": 27,
26+
"line": 9
27+
}
28+
}
29+
}
30+
}
31+
]
32+
}
33+
]

crates/tinymist-query/src/references.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ pub(crate) fn find_references(
5454
| SyntaxClass::Ref {
5555
suffix_colon: true, ..
5656
}
57+
| SyntaxClass::At { node: _ }
5758
| SyntaxClass::Normal(..) => {
5859
return None;
5960
}

0 commit comments

Comments
 (0)