From 7399fccd5abce142fdfb99e8f8bb0f42404fcf8e Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Thu, 13 Mar 2025 12:05:47 +0800 Subject: [PATCH] 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 --- .../tinymist-query/src/analysis/completion.rs | 17 ++++++---- crates/tinymist-query/src/completion.rs | 27 +++++++++++---- .../fixtures/completion/arg_range_math2.typ | 3 ++ .../fixtures/completion/arg_range_math3.typ | 2 ++ .../fixtures/completion/arg_range_math4.typ | 3 ++ .../src/fixtures/completion/mode_markup.typ | 4 +++ .../src/fixtures/completion/mode_markup2.typ | 4 +++ .../src/fixtures/completion/mode_math.typ | 2 ++ .../src/fixtures/completion/mode_math2.typ | 3 ++ .../completion/snaps/test@arg_range.typ.snap | 5 ++- .../completion/snaps/test@arg_range3.typ.snap | 5 ++- .../completion/snaps/test@arg_range4.typ.snap | 5 ++- .../snaps/test@arg_range_math.typ.snap | 5 ++- .../snaps/test@arg_range_math2.typ.snap | 34 +++++++++++++++++++ .../snaps/test@arg_range_math3.typ.snap | 34 +++++++++++++++++++ .../snaps/test@arg_range_math4.typ.snap | 12 +++++++ .../snaps/test@mode_markup.typ.snap | 31 +++++++++++++++++ .../snaps/test@mode_markup2.typ.snap | 12 +++++++ .../completion/snaps/test@mode_math.typ.snap | 34 +++++++++++++++++++ .../completion/snaps/test@mode_math2.typ.snap | 34 +++++++++++++++++++ crates/tinymist/src/lsp_query.rs | 5 ++- 21 files changed, 262 insertions(+), 19 deletions(-) create mode 100644 crates/tinymist-query/src/fixtures/completion/arg_range_math2.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/arg_range_math3.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/arg_range_math4.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/mode_markup.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/mode_markup2.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/mode_math.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/mode_math2.typ create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math2.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math3.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math4.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@mode_markup.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@mode_markup2.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@mode_math.typ.snap create mode 100644 crates/tinymist-query/src/fixtures/completion/snaps/test@mode_math2.typ.snap diff --git a/crates/tinymist-query/src/analysis/completion.rs b/crates/tinymist-query/src/analysis/completion.rs index a7dca8f7..fca4664c 100644 --- a/crates/tinymist-query/src/analysis/completion.rs +++ b/crates/tinymist-query/src/analysis/completion.rs @@ -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:#?})", diff --git a/crates/tinymist-query/src/completion.rs b/crates/tinymist-query/src/completion.rs index 19f02b20..24951a2c 100644 --- a/crates/tinymist-query/src/completion.rs +++ b/crates/tinymist-query/src/completion.rs @@ -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 // // 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: + // - + // - + // 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 diff --git a/crates/tinymist-query/src/fixtures/completion/arg_range_math2.typ b/crates/tinymist-query/src/fixtures/completion/arg_range_math2.typ new file mode 100644 index 00000000..a6668b91 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/arg_range_math2.typ @@ -0,0 +1,3 @@ +/// contains: AA +/// explicit: true +$norm(x)/* range 0..1 */$ diff --git a/crates/tinymist-query/src/fixtures/completion/arg_range_math3.typ b/crates/tinymist-query/src/fixtures/completion/arg_range_math3.typ new file mode 100644 index 00000000..1722e04b --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/arg_range_math3.typ @@ -0,0 +1,2 @@ +/// contains: AA +$norm(x)A/* range 0..1 */$ diff --git a/crates/tinymist-query/src/fixtures/completion/arg_range_math4.typ b/crates/tinymist-query/src/fixtures/completion/arg_range_math4.typ new file mode 100644 index 00000000..2930d2f8 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/arg_range_math4.typ @@ -0,0 +1,3 @@ +/// contains: AA +/// explicit: false +$norm(x)/* range 0..1 */$ diff --git a/crates/tinymist-query/src/fixtures/completion/mode_markup.typ b/crates/tinymist-query/src/fixtures/completion/mode_markup.typ new file mode 100644 index 00000000..e4b16606 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/mode_markup.typ @@ -0,0 +1,4 @@ +/// contains: label +/// explicit: true + +/* range 0..1 */ diff --git a/crates/tinymist-query/src/fixtures/completion/mode_markup2.typ b/crates/tinymist-query/src/fixtures/completion/mode_markup2.typ new file mode 100644 index 00000000..bac65cd4 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/mode_markup2.typ @@ -0,0 +1,4 @@ +/// contains: label +/// explicit: true + +te/* range 0..1 */ diff --git a/crates/tinymist-query/src/fixtures/completion/mode_math.typ b/crates/tinymist-query/src/fixtures/completion/mode_math.typ new file mode 100644 index 00000000..291092e2 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/mode_math.typ @@ -0,0 +1,2 @@ +/// contains: tanh +$tan/* range 0..1 */$ diff --git a/crates/tinymist-query/src/fixtures/completion/mode_math2.typ b/crates/tinymist-query/src/fixtures/completion/mode_math2.typ new file mode 100644 index 00000000..59d1a237 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/mode_math2.typ @@ -0,0 +1,3 @@ +/// contains: AA +/// explicit: true +$/* range 0..1 */$ diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range.typ.snap index 2573f20f..abeeaece 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range.typ.snap @@ -5,5 +5,8 @@ expression: "JsonRepr::new_pure(results)" input_file: crates/tinymist-query/src/fixtures/completion/arg_range.typ --- [ - null + { + "isIncomplete": false, + "items": [] + } ] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range3.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range3.typ.snap index 888f6e9f..aaf6562c 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range3.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range3.typ.snap @@ -5,5 +5,8 @@ expression: "JsonRepr::new_pure(results)" input_file: crates/tinymist-query/src/fixtures/completion/arg_range3.typ --- [ - null + { + "isIncomplete": false, + "items": [] + } ] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range4.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range4.typ.snap index f00cdf13..980686e7 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range4.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range4.typ.snap @@ -5,5 +5,8 @@ expression: "JsonRepr::new_pure(results)" input_file: crates/tinymist-query/src/fixtures/completion/arg_range4.typ --- [ - null + { + "isIncomplete": false, + "items": [] + } ] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math.typ.snap index be845e2d..31c18408 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math.typ.snap @@ -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": [] + } ] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math2.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math2.typ.snap new file mode 100644 index 00000000..dc6d337b --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math2.typ.snap @@ -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 + } + } + } + } + ] + } +] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math3.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math3.typ.snap new file mode 100644 index 00000000..3ef5efc4 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math3.typ.snap @@ -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 + } + } + } + } + ] + } +] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math4.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math4.typ.snap new file mode 100644 index 00000000..642cef0e --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@arg_range_math4.typ.snap @@ -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": [] + } +] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_markup.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_markup.typ.snap new file mode 100644 index 00000000..6d979319 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_markup.typ.snap @@ -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 + } + } + } + } + ] + } +] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_markup2.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_markup2.typ.snap new file mode 100644 index 00000000..f1554207 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_markup2.typ.snap @@ -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": [] + } +] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_math.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_math.typ.snap new file mode 100644 index 00000000..bf6329d4 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_math.typ.snap @@ -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 + } + } + } + } + ] + } +] diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_math2.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_math2.typ.snap new file mode 100644 index 00000000..ac5b21fa --- /dev/null +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@mode_math2.typ.snap @@ -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 + } + } + } + } + ] + } +] diff --git a/crates/tinymist/src/lsp_query.rs b/crates/tinymist/src/lsp_query.rs index d8216854..1bab8adf 100644 --- a/crates/tinymist/src/lsp_query.rs +++ b/crates/tinymist/src/lsp_query.rs @@ -228,9 +228,8 @@ impl ServerState { let context = params.context.as_ref(); let explicit = context.is_some_and(|context| context.trigger_kind == CompletionTriggerKind::INVOKED); - let trigger_character = params - .context - .and_then(|c| c.trigger_character) + let trigger_character = context + .and_then(|c| c.trigger_character.as_ref()) .and_then(|c| c.chars().next()); run_query!(