diff options
author | Kitson Kelly <me@kitsonkelly.com> | 2022-10-14 23:04:38 +1100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-10-14 23:04:38 +1100 |
commit | afcea6c233dad9b1c3e8202b950d38bf0c472c40 (patch) | |
tree | f017e443e1ac7f9b40a6a5cbe1b5f7fd949fbfc4 /cli | |
parent | e6e28981909f220ff0b98a13c692c0203eaf6035 (diff) |
fix(lsp): properly handle snippets on completions (#16274)
Fixes #15367
Diffstat (limited to 'cli')
-rw-r--r-- | cli/Cargo.toml | 2 | ||||
-rw-r--r-- | cli/lsp/capabilities.rs | 2 | ||||
-rw-r--r-- | cli/lsp/config.rs | 11 | ||||
-rw-r--r-- | cli/lsp/language_server.rs | 10 | ||||
-rw-r--r-- | cli/lsp/repl.rs | 1 | ||||
-rw-r--r-- | cli/lsp/tsc.rs | 9 | ||||
-rw-r--r-- | cli/tests/integration/lsp_tests.rs | 185 | ||||
-rw-r--r-- | cli/tests/testdata/lsp/initialize_params.json | 5 | ||||
-rw-r--r-- | cli/tests/testdata/lsp/initialize_params_no_snippet.json | 77 |
9 files changed, 298 insertions, 4 deletions
diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 6dfea3222..267048b27 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -105,7 +105,7 @@ text-size = "=1.1.0" text_lines = "=0.6.0" tokio = { version = "=1.21.1", features = ["full"] } tokio-util = "=0.7.4" -tower-lsp = "=0.17.0" +tower-lsp = { version = "=0.17.0", features = ["proposed"] } twox-hash = "=1.6.3" typed-arena = "=2.0.1" uuid = { version = "=1.1.2", features = ["v4", "serde"] } diff --git a/cli/lsp/capabilities.rs b/cli/lsp/capabilities.rs index 79d2eee1b..0e9c93e63 100644 --- a/cli/lsp/capabilities.rs +++ b/cli/lsp/capabilities.rs @@ -58,6 +58,7 @@ pub fn server_capabilities( ";".to_string(), "(".to_string(), ]), + completion_item: None, trigger_characters: Some(vec![ ".".to_string(), "\"".to_string(), @@ -140,5 +141,6 @@ pub fn server_capabilities( "denoConfigTasks": true, "testingApi":true, })), + inlay_hint_provider: None, } } diff --git a/cli/lsp/config.rs b/cli/lsp/config.rs index 27f52653d..98ba5afb5 100644 --- a/cli/lsp/config.rs +++ b/cli/lsp/config.rs @@ -20,6 +20,7 @@ pub const SETTINGS_SECTION: &str = "deno"; pub struct ClientCapabilities { pub code_action_disabled_support: bool, pub line_folding_only: bool, + pub snippet_support: bool, pub status_notification: bool, /// The client provides the `experimental.testingApi` capability, which is /// built around VSCode's testing API. It indicates that the server should @@ -393,6 +394,16 @@ impl Config { .as_ref() .and_then(|it| it.disabled_support) .unwrap_or(false); + self.client_capabilities.snippet_support = + if let Some(completion) = &text_document.completion { + completion + .completion_item + .as_ref() + .and_then(|it| it.snippet_support) + .unwrap_or(false) + } else { + false + }; } } diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index f442d3d13..64c7adeb6 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -786,6 +786,7 @@ impl Inner { Ok(InitializeResult { capabilities, server_info: Some(server_info), + offset_encoding: None, }) } @@ -1777,6 +1778,7 @@ impl Inner { }; let position = line_index.offset_tsc(params.text_document_position.position)?; + let use_snippets = self.config.client_capabilities.snippet_support; let req = tsc::RequestMethod::GetCompletions(( specifier.clone(), position, @@ -1792,10 +1794,12 @@ impl Inner { self.config.get_workspace_settings().suggest.auto_imports, ), include_completions_for_module_exports: Some(true), - include_completions_with_object_literal_method_snippets: Some(true), - include_completions_with_class_member_snippets: Some(true), + include_completions_with_object_literal_method_snippets: Some( + use_snippets, + ), + include_completions_with_class_member_snippets: Some(use_snippets), include_completions_with_insert_text: Some(true), - include_completions_with_snippet_text: Some(true), + include_completions_with_snippet_text: Some(use_snippets), jsx_attribute_completion_style: Some( tsc::JsxAttributeCompletionStyle::Auto, ), diff --git a/cli/lsp/repl.rs b/cli/lsp/repl.rs index b49a284b7..b6329205a 100644 --- a/cli/lsp/repl.rs +++ b/cli/lsp/repl.rs @@ -74,6 +74,7 @@ impl ReplLanguageServer { window: None, general: None, experimental: None, + offset_encoding: None, }, trace: None, workspace_folders: None, diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index e198c4fb8..51dd74240 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -2196,6 +2196,10 @@ impl CompletionEntry { || kind == Some(lsp::CompletionItemKind::METHOD)); let commit_characters = self.get_commit_characters(info, settings); let mut insert_text = self.insert_text.clone(); + let insert_text_format = match self.is_snippet { + Some(true) => Some(lsp::InsertTextFormat::SNIPPET), + _ => None, + }; let range = self.replacement_span.clone(); let mut filter_text = self.get_filter_text(); let mut tags = None; @@ -2262,6 +2266,7 @@ impl CompletionEntry { text_edit, filter_text, insert_text, + insert_text_format, detail, tags, commit_characters, @@ -2910,6 +2915,10 @@ pub struct UserPreferences { pub include_inlay_function_like_return_type_hints: Option<bool>, #[serde(skip_serializing_if = "Option::is_none")] pub include_inlay_enum_member_value_hints: Option<bool>, + #[serde(skip_serializing_if = "Option::is_none")] + pub allow_rename_of_import_path: Option<bool>, + #[serde(skip_serializing_if = "Option::is_none")] + pub auto_import_file_exclude_patterns: Option<Vec<String>>, } #[derive(Debug, Serialize)] diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs index 130ffe742..cc8625476 100644 --- a/cli/tests/integration/lsp_tests.rs +++ b/cli/tests/integration/lsp_tests.rs @@ -3655,6 +3655,191 @@ fn lsp_completions_auto_import() { } #[test] +fn lsp_completions_snippet() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/a.tsx", + "languageId": "typescriptreact", + "version": 1, + "text": "function A({ type }: { type: string }) {\n return type;\n}\n\nfunction B() {\n return <A t\n}", + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/a.tsx" + }, + "position": { + "line": 5, + "character": 13, + }, + "context": { + "triggerKind": 1, + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(lsp::CompletionResponse::List(list)) = maybe_res { + assert!(!list.is_incomplete); + assert_eq!( + json!(list), + json!({ + "isIncomplete": false, + "items": [ + { + "label": "type", + "kind": 5, + "sortText": "11", + "filterText": "type=\"$1\"", + "insertText": "type=\"$1\"", + "insertTextFormat": 2, + "commitCharacters": [ + ".", + ",", + ";", + "(" + ], + "data": { + "tsc": { + "specifier": "file:///a/a.tsx", + "position": 87, + "name": "type", + "useCodeSnippet": false + } + } + } + ] + }) + ); + } else { + panic!("unexpected completion response"); + } + let (maybe_res, maybe_err) = client + .write_request( + "completionItem/resolve", + json!({ + "label": "type", + "kind": 5, + "sortText": "11", + "filterText": "type=\"$1\"", + "insertText": "type=\"$1\"", + "insertTextFormat": 2, + "commitCharacters": [ + ".", + ",", + ";", + "(" + ], + "data": { + "tsc": { + "specifier": "file:///a/a.tsx", + "position": 87, + "name": "type", + "useCodeSnippet": false + } + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "label": "type", + "kind": 5, + "detail": "(property) type: string", + "documentation": { + "kind": "markdown", + "value": "" + }, + "sortText": "11", + "filterText": "type=\"$1\"", + "insertText": "type=\"$1\"", + "insertTextFormat": 2, + "commitCharacters": [ + ".", + ",", + ";", + "(" + ] + })) + ); +} + +#[test] +fn lsp_completions_no_snippet() { + let mut client = init("initialize_params_no_snippet.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/a.tsx", + "languageId": "typescriptreact", + "version": 1, + "text": "function A({ type }: { type: string }) {\n return type;\n}\n\nfunction B() {\n return <A t\n}", + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/a.tsx" + }, + "position": { + "line": 5, + "character": 13, + }, + "context": { + "triggerKind": 1, + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(lsp::CompletionResponse::List(list)) = maybe_res { + assert!(!list.is_incomplete); + assert_eq!( + json!(list), + json!({ + "isIncomplete": false, + "items": [ + { + "label": "type", + "kind": 5, + "sortText": "11", + "commitCharacters": [ + ".", + ",", + ";", + "(" + ], + "data": { + "tsc": { + "specifier": "file:///a/a.tsx", + "position": 87, + "name": "type", + "useCodeSnippet": false + } + } + } + ] + }) + ); + } else { + panic!("unexpected completion response"); + } +} + +#[test] fn lsp_completions_registry() { let _g = http_server(); let mut client = init("initialize_params_registry.json"); diff --git a/cli/tests/testdata/lsp/initialize_params.json b/cli/tests/testdata/lsp/initialize_params.json index b076f3b17..68735b06d 100644 --- a/cli/tests/testdata/lsp/initialize_params.json +++ b/cli/tests/testdata/lsp/initialize_params.json @@ -56,6 +56,11 @@ ] } }, + "completion": { + "completionItem": { + "snippetSupport": true + } + }, "foldingRange": { "lineFoldingOnly": true }, diff --git a/cli/tests/testdata/lsp/initialize_params_no_snippet.json b/cli/tests/testdata/lsp/initialize_params_no_snippet.json new file mode 100644 index 000000000..b076f3b17 --- /dev/null +++ b/cli/tests/testdata/lsp/initialize_params_no_snippet.json @@ -0,0 +1,77 @@ +{ + "processId": 0, + "clientInfo": { + "name": "test-harness", + "version": "1.0.0" + }, + "rootUri": null, + "initializationOptions": { + "enable": true, + "cache": null, + "certificateStores": null, + "codeLens": { + "implementations": true, + "references": true, + "test": true + }, + "config": null, + "importMap": null, + "lint": true, + "suggest": { + "autoImports": true, + "completeFunctionCalls": false, + "names": true, + "paths": true, + "imports": { + "hosts": {} + } + }, + "testing": { + "args": [ + "--allow-all" + ], + "enable": true + }, + "tlsCertificate": null, + "unsafelyIgnoreCertificateErrors": null, + "unstable": false + }, + "capabilities": { + "textDocument": { + "codeAction": { + "codeActionLiteralSupport": { + "codeActionKind": { + "valueSet": [ + "quickfix", + "refactor" + ] + } + }, + "isPreferredSupport": true, + "dataSupport": true, + "disabledSupport": true, + "resolveSupport": { + "properties": [ + "edit" + ] + } + }, + "foldingRange": { + "lineFoldingOnly": true + }, + "synchronization": { + "dynamicRegistration": true, + "willSave": true, + "willSaveWaitUntil": true, + "didSave": true + } + }, + "workspace": { + "configuration": true, + "workspaceFolders": true + }, + "experimental": { + "testingApi": true + } + } +} |