mirror of
https://github.com/denoland/deno.git
synced 2024-12-22 15:24:46 -05:00
fix(lsp): properly handle snippets on completions (#16274)
Fixes #15367
This commit is contained in:
parent
e6e2898190
commit
afcea6c233
9 changed files with 298 additions and 4 deletions
|
@ -105,7 +105,7 @@ text-size = "=1.1.0"
|
||||||
text_lines = "=0.6.0"
|
text_lines = "=0.6.0"
|
||||||
tokio = { version = "=1.21.1", features = ["full"] }
|
tokio = { version = "=1.21.1", features = ["full"] }
|
||||||
tokio-util = "=0.7.4"
|
tokio-util = "=0.7.4"
|
||||||
tower-lsp = "=0.17.0"
|
tower-lsp = { version = "=0.17.0", features = ["proposed"] }
|
||||||
twox-hash = "=1.6.3"
|
twox-hash = "=1.6.3"
|
||||||
typed-arena = "=2.0.1"
|
typed-arena = "=2.0.1"
|
||||||
uuid = { version = "=1.1.2", features = ["v4", "serde"] }
|
uuid = { version = "=1.1.2", features = ["v4", "serde"] }
|
||||||
|
|
|
@ -58,6 +58,7 @@ pub fn server_capabilities(
|
||||||
";".to_string(),
|
";".to_string(),
|
||||||
"(".to_string(),
|
"(".to_string(),
|
||||||
]),
|
]),
|
||||||
|
completion_item: None,
|
||||||
trigger_characters: Some(vec![
|
trigger_characters: Some(vec![
|
||||||
".".to_string(),
|
".".to_string(),
|
||||||
"\"".to_string(),
|
"\"".to_string(),
|
||||||
|
@ -140,5 +141,6 @@ pub fn server_capabilities(
|
||||||
"denoConfigTasks": true,
|
"denoConfigTasks": true,
|
||||||
"testingApi":true,
|
"testingApi":true,
|
||||||
})),
|
})),
|
||||||
|
inlay_hint_provider: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ pub const SETTINGS_SECTION: &str = "deno";
|
||||||
pub struct ClientCapabilities {
|
pub struct ClientCapabilities {
|
||||||
pub code_action_disabled_support: bool,
|
pub code_action_disabled_support: bool,
|
||||||
pub line_folding_only: bool,
|
pub line_folding_only: bool,
|
||||||
|
pub snippet_support: bool,
|
||||||
pub status_notification: bool,
|
pub status_notification: bool,
|
||||||
/// The client provides the `experimental.testingApi` capability, which is
|
/// The client provides the `experimental.testingApi` capability, which is
|
||||||
/// built around VSCode's testing API. It indicates that the server should
|
/// built around VSCode's testing API. It indicates that the server should
|
||||||
|
@ -393,6 +394,16 @@ impl Config {
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(|it| it.disabled_support)
|
.and_then(|it| it.disabled_support)
|
||||||
.unwrap_or(false);
|
.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
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -786,6 +786,7 @@ impl Inner {
|
||||||
Ok(InitializeResult {
|
Ok(InitializeResult {
|
||||||
capabilities,
|
capabilities,
|
||||||
server_info: Some(server_info),
|
server_info: Some(server_info),
|
||||||
|
offset_encoding: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1777,6 +1778,7 @@ impl Inner {
|
||||||
};
|
};
|
||||||
let position =
|
let position =
|
||||||
line_index.offset_tsc(params.text_document_position.position)?;
|
line_index.offset_tsc(params.text_document_position.position)?;
|
||||||
|
let use_snippets = self.config.client_capabilities.snippet_support;
|
||||||
let req = tsc::RequestMethod::GetCompletions((
|
let req = tsc::RequestMethod::GetCompletions((
|
||||||
specifier.clone(),
|
specifier.clone(),
|
||||||
position,
|
position,
|
||||||
|
@ -1792,10 +1794,12 @@ impl Inner {
|
||||||
self.config.get_workspace_settings().suggest.auto_imports,
|
self.config.get_workspace_settings().suggest.auto_imports,
|
||||||
),
|
),
|
||||||
include_completions_for_module_exports: Some(true),
|
include_completions_for_module_exports: Some(true),
|
||||||
include_completions_with_object_literal_method_snippets: Some(true),
|
include_completions_with_object_literal_method_snippets: Some(
|
||||||
include_completions_with_class_member_snippets: Some(true),
|
use_snippets,
|
||||||
|
),
|
||||||
|
include_completions_with_class_member_snippets: Some(use_snippets),
|
||||||
include_completions_with_insert_text: Some(true),
|
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(
|
jsx_attribute_completion_style: Some(
|
||||||
tsc::JsxAttributeCompletionStyle::Auto,
|
tsc::JsxAttributeCompletionStyle::Auto,
|
||||||
),
|
),
|
||||||
|
|
|
@ -74,6 +74,7 @@ impl ReplLanguageServer {
|
||||||
window: None,
|
window: None,
|
||||||
general: None,
|
general: None,
|
||||||
experimental: None,
|
experimental: None,
|
||||||
|
offset_encoding: None,
|
||||||
},
|
},
|
||||||
trace: None,
|
trace: None,
|
||||||
workspace_folders: None,
|
workspace_folders: None,
|
||||||
|
|
|
@ -2196,6 +2196,10 @@ impl CompletionEntry {
|
||||||
|| kind == Some(lsp::CompletionItemKind::METHOD));
|
|| kind == Some(lsp::CompletionItemKind::METHOD));
|
||||||
let commit_characters = self.get_commit_characters(info, settings);
|
let commit_characters = self.get_commit_characters(info, settings);
|
||||||
let mut insert_text = self.insert_text.clone();
|
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 range = self.replacement_span.clone();
|
||||||
let mut filter_text = self.get_filter_text();
|
let mut filter_text = self.get_filter_text();
|
||||||
let mut tags = None;
|
let mut tags = None;
|
||||||
|
@ -2262,6 +2266,7 @@ impl CompletionEntry {
|
||||||
text_edit,
|
text_edit,
|
||||||
filter_text,
|
filter_text,
|
||||||
insert_text,
|
insert_text,
|
||||||
|
insert_text_format,
|
||||||
detail,
|
detail,
|
||||||
tags,
|
tags,
|
||||||
commit_characters,
|
commit_characters,
|
||||||
|
@ -2910,6 +2915,10 @@ pub struct UserPreferences {
|
||||||
pub include_inlay_function_like_return_type_hints: Option<bool>,
|
pub include_inlay_function_like_return_type_hints: Option<bool>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub include_inlay_enum_member_value_hints: Option<bool>,
|
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)]
|
#[derive(Debug, Serialize)]
|
||||||
|
|
|
@ -3654,6 +3654,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]
|
#[test]
|
||||||
fn lsp_completions_registry() {
|
fn lsp_completions_registry() {
|
||||||
let _g = http_server();
|
let _g = http_server();
|
||||||
|
|
|
@ -56,6 +56,11 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"completion": {
|
||||||
|
"completionItem": {
|
||||||
|
"snippetSupport": true
|
||||||
|
}
|
||||||
|
},
|
||||||
"foldingRange": {
|
"foldingRange": {
|
||||||
"lineFoldingOnly": true
|
"lineFoldingOnly": true
|
||||||
},
|
},
|
||||||
|
|
77
cli/tests/testdata/lsp/initialize_params_no_snippet.json
vendored
Normal file
77
cli/tests/testdata/lsp/initialize_params_no_snippet.json
vendored
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue