feat: detect explicit completion from vscode (#1496)

* fix: prevent completion list from showing on bad pos

* feat: detect explicit completion from vscode

* fix: cases

* test: update snapshot
This commit is contained in:
Myriad-Dreamin 2025-03-13 12:05:47 +08:00 committed by GitHub
parent 8424df13f9
commit 7399fccd5a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
21 changed files with 262 additions and 19 deletions

View file

@ -10,7 +10,7 @@ use lsp_types::InsertTextFormat;
use once_cell::sync::Lazy;
use regex::{Captures, Regex};
use serde::{Deserialize, Serialize};
use tinymist_analysis::syntax::bad_completion_cursor;
use tinymist_analysis::syntax::{bad_completion_cursor, BadCompletionCursor};
use tinymist_derive::BindTyCtx;
use tinymist_project::LspWorld;
use tinymist_std::path::unix_slash;
@ -175,10 +175,6 @@ impl<'a> CompletionCursor<'a> {
let syntax_context = classify_context(leaf.clone(), Some(cursor));
let surrounding_syntax = surrounding_syntax(&leaf);
if bad_completion_cursor(syntax.as_ref(), syntax_context.as_ref(), &leaf).is_some() {
return None;
}
crate::log_debug_ct!("CompletionCursor: syntax {leaf:?} -> {syntax:#?}");
crate::log_debug_ct!("CompletionCursor: context {leaf:?} -> {syntax_context:#?}");
crate::log_debug_ct!("CompletionCursor: surrounding {leaf:?} -> {surrounding_syntax:#?}");
@ -637,12 +633,21 @@ impl CompletionPair<'_, '_, '_> {
| None => {}
}
let cursor_pos = bad_completion_cursor(
self.cursor.syntax.as_ref(),
self.cursor.syntax_context.as_ref(),
&self.cursor.leaf,
);
// Triggers a complete type checking.
let ty = self
.worker
.ctx
.post_type_of_node(self.cursor.leaf.clone())
.filter(|ty| !matches!(ty, Ty::Any));
.filter(|ty| !matches!(ty, Ty::Any))
// Forbids argument completion list if the cursor is in a bad position. This will
// prevent the completion list from showing up.
.filter(|_| !matches!(cursor_pos, Some(BadCompletionCursor::ArgListPos)));
crate::log_debug_ct!(
"complete_type: {:?} -> ({surrounding_syntax:?}, {ty:#?})",

View file

@ -55,6 +55,10 @@ impl StatefulRequest for CompletionRequest {
return None;
}
let document = doc.as_ref().map(|doc| &doc.document);
let source = ctx.source_by_path(&self.path).ok()?;
let cursor = ctx.to_typst_pos_offset(&source, self.position, 0)?;
// Please see <https://github.com/nvarner/typst-lsp/commit/2d66f26fb96ceb8e485f492e5b81e9db25c3e8ec>
//
// FIXME: correctly identify a completion which is triggered
@ -68,11 +72,17 @@ impl StatefulRequest for CompletionRequest {
//
// Hence, we cannot distinguish between the two cases. Conservatively, we
// assume that the completion is not explicit.
let explicit = false;
let document = doc.as_ref().map(|doc| &doc.document);
let source = ctx.source_by_path(&self.path).ok()?;
let cursor = ctx.to_typst_pos_offset(&source, self.position, 0)?;
//
// Second try: According to VSCode:
// - <https://github.com/microsoft/vscode/issues/130953>
// - <https://github.com/microsoft/vscode/commit/0984071fe0d8a3c157a1ba810c244752d69e5689>
// Checks the previous text to filter out letter explicit completions.
let explicit = self.explicit
&& (self.trigger_character.is_none() && {
let prev_text = &source.text()[..cursor];
let prev_char = prev_text.chars().next_back();
prev_char.is_none_or(|c| !c.is_alphabetic())
});
let mut cursor = CompletionCursor::new(ctx.shared_(), &source, cursor)?;
let mut worker = CompletionWorker::new(ctx, document, explicit, self.trigger_character)?;
@ -118,6 +128,11 @@ mod tests {
let trigger_character = properties
.get("trigger_character")
.map(|v| v.chars().next().unwrap());
let explicit = match properties.get("explicit").copied().map(str::trim) {
Some("true") => true,
Some("false") | None => false,
Some(v) => panic!("invalid value for 'explicit' property: {v}"),
};
let mut includes = HashSet::new();
let mut excludes = HashSet::new();
@ -171,7 +186,7 @@ mod tests {
let request = CompletionRequest {
path: ctx.path_for_id(id).unwrap().as_path().to_owned(),
position: ctx.to_lsp_pos(s, &source),
explicit: false,
explicit,
trigger_character,
};
let result = request

View file

@ -0,0 +1,3 @@
/// contains: AA
/// explicit: true
$norm(x)/* range 0..1 */$

View file

@ -0,0 +1,2 @@
/// contains: AA
$norm(x)A/* range 0..1 */$

View file

@ -0,0 +1,3 @@
/// contains: AA
/// explicit: false
$norm(x)/* range 0..1 */$

View file

@ -0,0 +1,4 @@
/// contains: label
/// explicit: true
/* range 0..1 */

View file

@ -0,0 +1,4 @@
/// contains: label
/// explicit: true
te/* range 0..1 */

View file

@ -0,0 +1,2 @@
/// contains: tanh
$tan/* range 0..1 */$

View file

@ -0,0 +1,3 @@
/// contains: AA
/// explicit: true
$/* range 0..1 */$

View file

@ -5,5 +5,8 @@ expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/arg_range.typ
---
[
null
{
"isIncomplete": false,
"items": []
}
]

View file

@ -5,5 +5,8 @@ expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/arg_range3.typ
---
[
null
{
"isIncomplete": false,
"items": []
}
]

View file

@ -5,5 +5,8 @@ expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/arg_range4.typ
---
[
null
{
"isIncomplete": false,
"items": []
}
]

View file

@ -5,5 +5,8 @@ expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/arg_range_math.typ
---
[
null
{
"isIncomplete": false,
"items": []
}
]

View file

@ -0,0 +1,34 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (44..45)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/arg_range_math2.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 5,
"label": "AA",
"labelDetails": {
"description": "𝔸"
},
"sortText": "000",
"textEdit": {
"newText": "AA",
"range": {
"end": {
"character": 8,
"line": 2
},
"start": {
"character": 8,
"line": 2
}
}
}
}
]
}
]

View file

@ -0,0 +1,34 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (26..27)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/arg_range_math3.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 5,
"label": "AA",
"labelDetails": {
"description": "𝔸"
},
"sortText": "000",
"textEdit": {
"newText": "AA",
"range": {
"end": {
"character": 9,
"line": 1
},
"start": {
"character": 8,
"line": 1
}
}
}
}
]
}
]

View file

@ -0,0 +1,12 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (45..46)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/arg_range_math4.typ
---
[
{
"isIncomplete": false,
"items": []
}
]

View file

@ -0,0 +1,31 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (40..41)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/mode_markup.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 15,
"label": "label",
"sortText": "007",
"textEdit": {
"newText": "<${1:name}>",
"range": {
"end": {
"character": 0,
"line": 3
},
"start": {
"character": 0,
"line": 3
}
}
}
}
]
}
]

View file

@ -0,0 +1,12 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (42..43)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/mode_markup2.typ
---
[
{
"isIncomplete": false,
"items": []
}
]

View file

@ -0,0 +1,34 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (23..24)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/mode_math.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 6,
"label": "tanh",
"labelDetails": {
"description": "op(text: [tanh], limits: false)"
},
"sortText": "318",
"textEdit": {
"newText": "tanh",
"range": {
"end": {
"character": 4,
"line": 1
},
"start": {
"character": 1,
"line": 1
}
}
}
}
]
}
]

View file

@ -0,0 +1,34 @@
---
source: crates/tinymist-query/src/completion.rs
description: Completion on / (37..38)
expression: "JsonRepr::new_pure(results)"
input_file: crates/tinymist-query/src/fixtures/completion/mode_math2.typ
---
[
{
"isIncomplete": false,
"items": [
{
"kind": 5,
"label": "AA",
"labelDetails": {
"description": "𝔸"
},
"sortText": "000",
"textEdit": {
"newText": "AA",
"range": {
"end": {
"character": 1,
"line": 2
},
"start": {
"character": 1,
"line": 2
}
}
}
}
]
}
]