summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNayeem Rahman <nayeemrmn99@gmail.com>2024-10-21 17:15:52 +0100
committerGitHub <noreply@github.com>2024-10-21 17:15:52 +0100
commit9fe2bf42dc584779cc43f0ec15a5a3d6dddca283 (patch)
treef537a20aac656ef5064a894b5c340f44da5719aa
parentafb33b3c2597c9ec943f71218b236486fbc86e23 (diff)
feat(lsp): interactive inlay hints (#26382)
-rw-r--r--cli/lsp/language_server.rs2
-rw-r--r--cli/lsp/tsc.rs65
-rw-r--r--tests/integration/lsp_tests.rs182
-rw-r--r--tests/util/server/src/lsp.rs28
4 files changed, 206 insertions, 71 deletions
diff --git a/cli/lsp/language_server.rs b/cli/lsp/language_server.rs
index 908afa165..33ae539f8 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 cfab39b20..0bc7d1600 100644
--- a/cli/lsp/tsc.rs
+++ b/cli/lsp/tsc.rs
@@ -2183,6 +2183,50 @@ impl NavigateToItem {
}
#[derive(Debug, Clone, Deserialize)]
+#[serde(rename_all = "camelCase")]
+pub struct InlayHintDisplayPart {
+ pub text: String,
+ pub span: Option<TextSpan>,
+ pub file: Option<String>,
+}
+
+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,
Parameter,
@@ -2203,6 +2247,7 @@ impl InlayHintKind {
#[serde(rename_all = "camelCase")]
pub struct InlayHint {
pub text: String,
+ pub display_parts: Option<Vec<InlayHintDisplayPart>>,
pub position: u32,
pub kind: InlayHintKind,
pub whitespace_before: Option<bool>,
@@ -2210,10 +2255,23 @@ pub struct InlayHint {
}
impl InlayHint {
- pub fn to_lsp(&self, line_index: Arc<LineIndex>) -> lsp::InlayHint {
+ pub fn to_lsp(
+ &self,
+ line_index: Arc<LineIndex>,
+ 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<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub auto_import_file_exclude_patterns: Option<Vec<String>>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub interactive_inlay_hints: Option<bool>,
}
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 85e02041e..0b7c5dd3f 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 ffe72b88a..4e75cfadb 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