Skip to content

Commit

Permalink
feat(lsp): interactive inlay hints (#26382)
Browse files Browse the repository at this point in the history
  • Loading branch information
nayeemrmn authored Oct 21, 2024
1 parent afb33b3 commit 9fe2bf4
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 71 deletions.
2 changes: 1 addition & 1 deletion cli/lsp/language_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
65 changes: 63 additions & 2 deletions cli/lsp/tsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
pub enum InlayHintKind {
Type,
Expand All @@ -2203,17 +2247,31 @@ 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>,
pub whitespace_after: Option<bool>,
}

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,
Expand Down Expand Up @@ -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 {
Expand All @@ -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),
Expand Down
182 changes: 142 additions & 40 deletions tests/integration/lsp_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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",
Expand All @@ -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();
}
Expand Down
28 changes: 0 additions & 28 deletions tests/util/server/src/lsp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 9fe2bf4

Please sign in to comment.