diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs index 908afa1657..33ae539f85 100644 --- a/cli/lsp/language_server.rs +++ b/cli/lsp/language_server.rs @@ -3812,7 +3812,7 @@ impl Inner { let maybe_inlay_hints = maybe_inlay_hints.map(|hints| { hints .iter() - .map(|hint| hint.to_lsp(line_index.clone())) + .map(|hint| hint.to_lsp(line_index.clone(), self)) .collect() }); self.performance.measure(mark); diff --git a/cli/lsp/tsc.rs b/cli/lsp/tsc.rs index cfab39b20b..0bc7d1600d 100644 --- a/cli/lsp/tsc.rs +++ b/cli/lsp/tsc.rs @@ -2182,6 +2182,50 @@ impl NavigateToItem { } } +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct InlayHintDisplayPart { + pub text: String, + pub span: Option, + pub file: Option, +} + +impl InlayHintDisplayPart { + pub fn to_lsp( + &self, + language_server: &language_server::Inner, + ) -> lsp::InlayHintLabelPart { + let location = self.file.as_ref().map(|f| { + let specifier = + resolve_url(f).unwrap_or_else(|_| INVALID_SPECIFIER.clone()); + let file_referrer = + language_server.documents.get_file_referrer(&specifier); + let uri = language_server + .url_map + .specifier_to_uri(&specifier, file_referrer.as_deref()) + .unwrap_or_else(|_| INVALID_URI.clone()); + let range = self + .span + .as_ref() + .and_then(|s| { + let asset_or_doc = + language_server.get_asset_or_document(&specifier).ok()?; + Some(s.to_range(asset_or_doc.line_index())) + }) + .unwrap_or_else(|| { + lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(0, 0)) + }); + lsp::Location { uri, range } + }); + lsp::InlayHintLabelPart { + value: self.text.clone(), + tooltip: None, + location, + command: None, + } + } +} + #[derive(Debug, Clone, Deserialize)] pub enum InlayHintKind { Type, @@ -2203,6 +2247,7 @@ impl InlayHintKind { #[serde(rename_all = "camelCase")] pub struct InlayHint { pub text: String, + pub display_parts: Option>, pub position: u32, pub kind: InlayHintKind, pub whitespace_before: Option, @@ -2210,10 +2255,23 @@ pub struct InlayHint { } impl InlayHint { - pub fn to_lsp(&self, line_index: Arc) -> lsp::InlayHint { + pub fn to_lsp( + &self, + line_index: Arc, + language_server: &language_server::Inner, + ) -> lsp::InlayHint { lsp::InlayHint { position: line_index.position_tsc(self.position.into()), - label: lsp::InlayHintLabel::String(self.text.clone()), + label: if let Some(display_parts) = &self.display_parts { + lsp::InlayHintLabel::LabelParts( + display_parts + .iter() + .map(|p| p.to_lsp(language_server)) + .collect(), + ) + } else { + lsp::InlayHintLabel::String(self.text.clone()) + }, kind: self.kind.to_lsp(), padding_left: self.whitespace_before, padding_right: self.whitespace_after, @@ -4892,6 +4950,8 @@ pub struct UserPreferences { pub allow_rename_of_import_path: Option, #[serde(skip_serializing_if = "Option::is_none")] pub auto_import_file_exclude_patterns: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + pub interactive_inlay_hints: Option, } impl UserPreferences { @@ -4909,6 +4969,7 @@ impl UserPreferences { include_completions_with_snippet_text: Some( config.snippet_support_capable(), ), + interactive_inlay_hints: Some(true), provide_refactor_not_applicable_reason: Some(true), quote_preference: Some(fmt_config.into()), use_label_details_in_completion_entries: Some(true), diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index 85e02041ed..0b7c5dd3f9 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -1827,15 +1827,41 @@ fn lsp_hover_disabled() { fn lsp_inlay_hints() { let context = TestContextBuilder::new().use_temp_cwd().build(); let mut client = context.new_lsp_command().build(); - client.initialize(|builder| { - builder.enable_inlay_hints(); - }); + client.initialize_default(); + client.change_configuration(json!({ + "deno": { + "enable": true, + }, + "typescript": { + "inlayHints": { + "parameterNames": { + "enabled": "all", + }, + "parameterTypes": { + "enabled": true, + }, + "variableTypes": { + "enabled": true, + }, + "propertyDeclarationTypes": { + "enabled": true, + }, + "functionLikeReturnTypes": { + "enabled": true, + }, + "enumMemberValues": { + "enabled": true, + }, + }, + }, + })); client.did_open(json!({ "textDocument": { "uri": "file:///a/file.ts", "languageId": "typescript", "version": 1, - "text": r#"function a(b: string) { + "text": r#" + function a(b: string) { return b; } @@ -1854,8 +1880,19 @@ fn lsp_inlay_hints() { } ["a"].map((v) => v + v); - "# - } + + interface Bar { + someField: string; + } + function getBar(): Bar { + return { someField: "foo" }; + } + // This shouldn't have a type hint because the variable name makes it + // redundant. + const bar = getBar(); + const someValue = getBar(); + "#, + }, })); let res = client.write_request( "textDocument/inlayHint", @@ -1864,65 +1901,130 @@ fn lsp_inlay_hints() { "uri": "file:///a/file.ts", }, "range": { - "start": { "line": 0, "character": 0 }, - "end": { "line": 19, "character": 0, } - } + "start": { "line": 1, "character": 0 }, + "end": { "line": 31, "character": 0, }, + }, }), ); assert_eq!( res, json!([ { - "position": { "line": 0, "character": 21 }, - "label": ": string", + "position": { "line": 1, "character": 29 }, + "label": [{ "value": ": " }, { "value": "string" }], "kind": 1, - "paddingLeft": true + "paddingLeft": true, }, { - "position": { "line": 4, "character": 10 }, - "label": "b:", + "position": { "line": 5, "character": 10 }, + "label": [ + { + "value": "b", + "location": { + "uri": "file:///a/file.ts", + "range": { + "start": { "line": 1, "character": 19 }, + "end": { "line": 1, "character": 20 }, + }, + }, + }, + { "value": ":" }, + ], "kind": 2, - "paddingRight": true + "paddingRight": true, }, { - "position": { "line": 7, "character": 11 }, + "position": { "line": 8, "character": 11 }, "label": "= 0", - "paddingLeft": true + "paddingLeft": true, }, { - "position": { "line": 10, "character": 17 }, - "label": "string:", + "position": { "line": 11, "character": 17 }, + "label": [ + { + "value": "string", + "location": { + "uri": "deno:/asset/lib.es5.d.ts", + "range": { + "start": { "line": 41, "character": 26 }, + "end": { "line": 41, "character": 32 }, + }, + }, + }, + { "value": ":" }, + ], "kind": 2, - "paddingRight": true + "paddingRight": true, }, { - "position": { "line": 10, "character": 24 }, - "label": "radix:", + "position": { "line": 11, "character": 24 }, + "label": [ + { + "value": "radix", + "location": { + "uri": "deno:/asset/lib.es5.d.ts", + "range": { + "start": { "line": 41, "character": 42 }, + "end": { "line": 41, "character": 47 }, + }, + }, + }, + { "value": ":" }, + ], "kind": 2, - "paddingRight": true + "paddingRight": true, }, { - "position": { "line": 12, "character": 15 }, - "label": ": number", + "position": { "line": 13, "character": 15 }, + "label": [{ "value": ": " }, { "value": "number" }], "kind": 1, - "paddingLeft": true + "paddingLeft": true, }, { - "position": { "line": 15, "character": 11 }, - "label": ": number", + "position": { "line": 16, "character": 11 }, + "label": [{ "value": ": " }, { "value": "number" }], "kind": 1, - "paddingLeft": true + "paddingLeft": true, }, { - "position": { "line": 18, "character": 18 }, - "label": "callbackfn:", + "position": { "line": 19, "character": 18 }, + "label": [ + { + "value": "callbackfn", + "location": { + "uri": "deno:/asset/lib.es5.d.ts", + "range": { + "start": { "line": 1462, "character": 11 }, + "end": { "line": 1462, "character": 21 }, + }, + }, + }, + { "value": ":" }, + ], "kind": 2, - "paddingRight": true + "paddingRight": true, }, { - "position": { "line": 18, "character": 20 }, - "label": ": string", + "position": { "line": 19, "character": 20 }, + "label": [{ "value": ": " }, { "value": "string" }], "kind": 1, - "paddingLeft": true + "paddingLeft": true, }, { - "position": { "line": 18, "character": 21 }, - "label": ": string", + "position": { "line": 19, "character": 21 }, + "label": [{ "value": ": " }, { "value": "string" }], "kind": 1, - "paddingLeft": true - } - ]) + "paddingLeft": true, + }, { + "position": { "line": 30, "character": 23 }, + "label": [ + { "value": ": " }, + { + "value": "Bar", + "location": { + "uri": "file:///a/file.ts", + "range": { + "start": { "line": 21, "character": 18 }, + "end": { "line": 21, "character": 21 }, + }, + }, + }, + ], + "kind": 1, + "paddingLeft": true, + }, + ]), ); client.shutdown(); } diff --git a/tests/util/server/src/lsp.rs b/tests/util/server/src/lsp.rs index ffe72b88af..4e75cfadb9 100644 --- a/tests/util/server/src/lsp.rs +++ b/tests/util/server/src/lsp.rs @@ -308,34 +308,6 @@ impl InitializeParamsBuilder { self } - pub fn enable_inlay_hints(&mut self) -> &mut Self { - let options = self.initialization_options_mut(); - options.insert( - "inlayHints".to_string(), - json!({ - "parameterNames": { - "enabled": "all" - }, - "parameterTypes": { - "enabled": true - }, - "variableTypes": { - "enabled": true - }, - "propertyDeclarationTypes": { - "enabled": true - }, - "functionLikeReturnTypes": { - "enabled": true - }, - "enumMemberValues": { - "enabled": true - } - }), - ); - self - } - pub fn disable_testing_api(&mut self) -> &mut Self { let obj = self .params