1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

feat(lsp): interactive inlay hints (#26382)

This commit is contained in:
Nayeem Rahman 2024-10-21 17:15:52 +01:00 committed by GitHub
parent afb33b3c25
commit 9fe2bf42dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 206 additions and 71 deletions

View file

@ -3812,7 +3812,7 @@ impl Inner {
let maybe_inlay_hints = maybe_inlay_hints.map(|hints| { let maybe_inlay_hints = maybe_inlay_hints.map(|hints| {
hints hints
.iter() .iter()
.map(|hint| hint.to_lsp(line_index.clone())) .map(|hint| hint.to_lsp(line_index.clone(), self))
.collect() .collect()
}); });
self.performance.measure(mark); self.performance.measure(mark);

View file

@ -2182,6 +2182,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)] #[derive(Debug, Clone, Deserialize)]
pub enum InlayHintKind { pub enum InlayHintKind {
Type, Type,
@ -2203,6 +2247,7 @@ impl InlayHintKind {
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct InlayHint { pub struct InlayHint {
pub text: String, pub text: String,
pub display_parts: Option<Vec<InlayHintDisplayPart>>,
pub position: u32, pub position: u32,
pub kind: InlayHintKind, pub kind: InlayHintKind,
pub whitespace_before: Option<bool>, pub whitespace_before: Option<bool>,
@ -2210,10 +2255,23 @@ pub struct InlayHint {
} }
impl 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 { lsp::InlayHint {
position: line_index.position_tsc(self.position.into()), 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(), kind: self.kind.to_lsp(),
padding_left: self.whitespace_before, padding_left: self.whitespace_before,
padding_right: self.whitespace_after, padding_right: self.whitespace_after,
@ -4892,6 +4950,8 @@ pub struct UserPreferences {
pub allow_rename_of_import_path: Option<bool>, pub allow_rename_of_import_path: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")] #[serde(skip_serializing_if = "Option::is_none")]
pub auto_import_file_exclude_patterns: Option<Vec<String>>, pub auto_import_file_exclude_patterns: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub interactive_inlay_hints: Option<bool>,
} }
impl UserPreferences { impl UserPreferences {
@ -4909,6 +4969,7 @@ impl UserPreferences {
include_completions_with_snippet_text: Some( include_completions_with_snippet_text: Some(
config.snippet_support_capable(), config.snippet_support_capable(),
), ),
interactive_inlay_hints: Some(true),
provide_refactor_not_applicable_reason: Some(true), provide_refactor_not_applicable_reason: Some(true),
quote_preference: Some(fmt_config.into()), quote_preference: Some(fmt_config.into()),
use_label_details_in_completion_entries: Some(true), use_label_details_in_completion_entries: Some(true),

View file

@ -1827,15 +1827,41 @@ fn lsp_hover_disabled() {
fn lsp_inlay_hints() { fn lsp_inlay_hints() {
let context = TestContextBuilder::new().use_temp_cwd().build(); let context = TestContextBuilder::new().use_temp_cwd().build();
let mut client = context.new_lsp_command().build(); let mut client = context.new_lsp_command().build();
client.initialize(|builder| { client.initialize_default();
builder.enable_inlay_hints(); 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!({ client.did_open(json!({
"textDocument": { "textDocument": {
"uri": "file:///a/file.ts", "uri": "file:///a/file.ts",
"languageId": "typescript", "languageId": "typescript",
"version": 1, "version": 1,
"text": r#"function a(b: string) { "text": r#"
function a(b: string) {
return b; return b;
} }
@ -1854,8 +1880,19 @@ fn lsp_inlay_hints() {
} }
["a"].map((v) => v + v); ["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( let res = client.write_request(
"textDocument/inlayHint", "textDocument/inlayHint",
@ -1864,65 +1901,130 @@ fn lsp_inlay_hints() {
"uri": "file:///a/file.ts", "uri": "file:///a/file.ts",
}, },
"range": { "range": {
"start": { "line": 0, "character": 0 }, "start": { "line": 1, "character": 0 },
"end": { "line": 19, "character": 0, } "end": { "line": 31, "character": 0, },
} },
}), }),
); );
assert_eq!( assert_eq!(
res, res,
json!([ json!([
{ {
"position": { "line": 0, "character": 21 }, "position": { "line": 1, "character": 29 },
"label": ": string", "label": [{ "value": ": " }, { "value": "string" }],
"kind": 1, "kind": 1,
"paddingLeft": true "paddingLeft": true,
}, { }, {
"position": { "line": 4, "character": 10 }, "position": { "line": 5, "character": 10 },
"label": "b:", "label": [
{
"value": "b",
"location": {
"uri": "file:///a/file.ts",
"range": {
"start": { "line": 1, "character": 19 },
"end": { "line": 1, "character": 20 },
},
},
},
{ "value": ":" },
],
"kind": 2, "kind": 2,
"paddingRight": true "paddingRight": true,
}, { }, {
"position": { "line": 7, "character": 11 }, "position": { "line": 8, "character": 11 },
"label": "= 0", "label": "= 0",
"paddingLeft": true "paddingLeft": true,
}, { }, {
"position": { "line": 10, "character": 17 }, "position": { "line": 11, "character": 17 },
"label": "string:", "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, "kind": 2,
"paddingRight": true "paddingRight": true,
}, { }, {
"position": { "line": 10, "character": 24 }, "position": { "line": 11, "character": 24 },
"label": "radix:", "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, "kind": 2,
"paddingRight": true "paddingRight": true,
}, { }, {
"position": { "line": 12, "character": 15 }, "position": { "line": 13, "character": 15 },
"label": ": number", "label": [{ "value": ": " }, { "value": "number" }],
"kind": 1, "kind": 1,
"paddingLeft": true "paddingLeft": true,
}, { }, {
"position": { "line": 15, "character": 11 }, "position": { "line": 16, "character": 11 },
"label": ": number", "label": [{ "value": ": " }, { "value": "number" }],
"kind": 1, "kind": 1,
"paddingLeft": true "paddingLeft": true,
}, { }, {
"position": { "line": 18, "character": 18 }, "position": { "line": 19, "character": 18 },
"label": "callbackfn:", "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, "kind": 2,
"paddingRight": true "paddingRight": true,
}, { }, {
"position": { "line": 18, "character": 20 }, "position": { "line": 19, "character": 20 },
"label": ": string", "label": [{ "value": ": " }, { "value": "string" }],
"kind": 1, "kind": 1,
"paddingLeft": true "paddingLeft": true,
}, { }, {
"position": { "line": 18, "character": 21 }, "position": { "line": 19, "character": 21 },
"label": ": string", "label": [{ "value": ": " }, { "value": "string" }],
"kind": 1, "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(); client.shutdown();
} }

View file

@ -308,34 +308,6 @@ impl InitializeParamsBuilder {
self 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 { pub fn disable_testing_api(&mut self) -> &mut Self {
let obj = self let obj = self
.params .params