Skip to content

Commit 5a3d241

Browse files
asteritejfecher
andauthored
feat: LSP signature help (#5725)
# Description ## Problem Resolves #1585 ## Summary While working on autocompletion I was always checking what Rust Analyzer did, or how it worked, and I noticed that in Rust Analyzer when it autocompletes a function a little popup with the parameter names and types would show up, which is really useful! But it turned out that's a separate feature: signature help. This PR implements that. I think it makes autocompletion much better. Before: ![lsp-signature-help-before](https://github.com/user-attachments/assets/2480e9bf-ae17-47a6-be0a-bcc58f2b2b92) After: ![lsp-signature-help-after](https://github.com/user-attachments/assets/e50573d0-17cb-4c3a-b50b-a4fed462acdf) Note: in the latest version the popup text starts with "fn ", but I didn't want to re-record all the gifs 😅 . You basically don't have to remember what were the parameter types. It also works with methods: ![lsp-signature-help-method](https://github.com/user-attachments/assets/72fe65ee-2c68-4463-aa75-fd304fcbe50c) And if you have an `fn`: ![lsp-signature-help-fn](https://github.com/user-attachments/assets/d29001e1-5de4-47f1-aede-da01bc1a3c53) And here it's working in an aztec project (I always check that it works outside of small toy programs 😊): ![lsp-signature-help-aztec](https://github.com/user-attachments/assets/3aa1d395-09bc-4578-b56d-c0e90630c4da) ## Additional Context I promise this will be the last big LSP PR I send 🤞 . Now that we have most of the LSP features registered, next PRs should be much smaller, just adding on top of what there is. These PRs are also relatively big because they traverse the AST and that code is kind of boilerplatey. For that reason here I decided to put the boilerplatey code in a `traversal.rs` file, so that it's clear there's no extra logic there other than traversing the AST. I might send a follow-up PR doing the same for autocompletion. One more thing: you can trigger this functionality as a VSCode command (Trigger Parameter Hints). But then when offering autocompletion you can also tell VSCode to execute a command, and in this PR we tell it to execute exactly that command (Rust Analyzer does the same, though they have a custom command for it, not sure why). UPDATE: I managed to implement the visitor pattern to reduce the boilerplate. [Here's a PR](#5727) with that. I think it will greatly simplify things, and make it easier to keep the code updated as we add more nodes. It takes inspiration in [how it's done in the Crystal compiler](https://github.com/crystal-lang/crystal/blob/master/src/compiler/crystal/syntax/visitor.cr), which in turn is inspired by how it was done in the Java editor support for Eclipse 😄 ## Documentation\* Check one: - [x] No documentation needed. - [ ] Documentation included in this PR. - [ ] **[For Experimental Features]** Documentation to be submitted in a separate PR. # PR Checklist\* - [x] I have tested the changes locally. - [x] I have formatted the changes with [Prettier](https://prettier.io/) and/or `cargo fmt` on default settings. --------- Co-authored-by: jfecher <[email protected]>
1 parent 5730e67 commit 5a3d241

File tree

8 files changed

+885
-37
lines changed

8 files changed

+885
-37
lines changed

tooling/lsp/src/lib.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use fxhash::FxHashSet;
2323
use lsp_types::{
2424
request::{
2525
Completion, DocumentSymbolRequest, HoverRequest, InlayHintRequest, PrepareRenameRequest,
26-
References, Rename,
26+
References, Rename, SignatureHelpRequest,
2727
},
2828
CodeLens,
2929
};
@@ -55,7 +55,7 @@ use requests::{
5555
on_goto_declaration_request, on_goto_definition_request, on_goto_type_definition_request,
5656
on_hover_request, on_initialize, on_inlay_hint_request, on_prepare_rename_request,
5757
on_profile_run_request, on_references_request, on_rename_request, on_shutdown,
58-
on_test_run_request, on_tests_request, LspInitializationOptions,
58+
on_signature_help_request, on_test_run_request, on_tests_request, LspInitializationOptions,
5959
};
6060
use serde_json::Value as JsonValue;
6161
use thiserror::Error;
@@ -143,6 +143,7 @@ impl NargoLspService {
143143
.request::<HoverRequest, _>(on_hover_request)
144144
.request::<InlayHintRequest, _>(on_inlay_hint_request)
145145
.request::<Completion, _>(on_completion_request)
146+
.request::<SignatureHelpRequest, _>(on_signature_help_request)
146147
.notification::<notification::Initialized>(on_initialized)
147148
.notification::<notification::DidChangeConfiguration>(on_did_change_configuration)
148149
.notification::<notification::DidOpenTextDocument>(on_did_open_text_document)

tooling/lsp/src/requests/completion/completion_items.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use lsp_types::{CompletionItem, CompletionItemKind, CompletionItemLabelDetails, InsertTextFormat};
1+
use lsp_types::{
2+
Command, CompletionItem, CompletionItemKind, CompletionItemLabelDetails, InsertTextFormat,
3+
};
24
use noirc_frontend::{
35
hir_def::{function::FuncMeta, stmt::HirPattern},
46
macros_api::{ModuleDefId, StructId},
@@ -164,6 +166,13 @@ impl<'a> NodeFinder<'a> {
164166
completion_item
165167
};
166168

169+
let completion_item = match function_completion_kind {
170+
FunctionCompletionKind::Name => completion_item,
171+
FunctionCompletionKind::NameAndParameters => {
172+
completion_item_with_trigger_parameter_hints_command(completion_item)
173+
}
174+
};
175+
167176
Some(completion_item)
168177
}
169178

@@ -342,3 +351,16 @@ pub(super) fn completion_item_with_sort_text(
342351
) -> CompletionItem {
343352
CompletionItem { sort_text: Some(sort_text), ..completion_item }
344353
}
354+
355+
pub(super) fn completion_item_with_trigger_parameter_hints_command(
356+
completion_item: CompletionItem,
357+
) -> CompletionItem {
358+
CompletionItem {
359+
command: Some(Command {
360+
title: "Trigger parameter hints".to_string(),
361+
command: "editor.action.triggerParameterHints".to_string(),
362+
arguments: None,
363+
}),
364+
..completion_item
365+
}
366+
}

tooling/lsp/src/requests/completion/tests.rs

Lines changed: 47 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ mod completion_tests {
55
requests::{
66
completion::{
77
completion_items::{
8-
completion_item_with_sort_text, crate_completion_item, module_completion_item,
9-
simple_completion_item, snippet_completion_item,
8+
completion_item_with_sort_text,
9+
completion_item_with_trigger_parameter_hints_command, crate_completion_item,
10+
module_completion_item, simple_completion_item, snippet_completion_item,
1011
},
1112
sort_text::self_mismatch_sort_text,
1213
},
@@ -90,6 +91,20 @@ mod completion_tests {
9091
assert_eq!(items, expected);
9192
}
9293

94+
pub(super) fn function_completion_item(
95+
label: impl Into<String>,
96+
kind: CompletionItemKind,
97+
insert_text: impl Into<String>,
98+
description: impl Into<String>,
99+
) -> CompletionItem {
100+
completion_item_with_trigger_parameter_hints_command(snippet_completion_item(
101+
label,
102+
kind,
103+
insert_text,
104+
Some(description.into()),
105+
))
106+
}
107+
93108
#[test]
94109
async fn test_use_first_segment() {
95110
let src = r#"
@@ -408,11 +423,11 @@ mod completion_tests {
408423
"#;
409424
assert_completion(
410425
src,
411-
vec![snippet_completion_item(
426+
vec![function_completion_item(
412427
"hello()",
413428
CompletionItemKind::FUNCTION,
414429
"hello()",
415-
Some("fn()".to_string()),
430+
"fn()",
416431
)],
417432
)
418433
.await;
@@ -429,11 +444,11 @@ mod completion_tests {
429444
"#;
430445
assert_completion(
431446
src,
432-
vec![snippet_completion_item(
447+
vec![function_completion_item(
433448
"hello(…)",
434449
CompletionItemKind::FUNCTION,
435450
"hello(${1:x}, ${2:y})",
436-
Some("fn(i32, Field)".to_string()),
451+
"fn(i32, Field)".to_string(),
437452
)],
438453
)
439454
.await;
@@ -455,11 +470,11 @@ mod completion_tests {
455470
"assert(${1:predicate})",
456471
Some("fn(T)".to_string()),
457472
),
458-
snippet_completion_item(
473+
function_completion_item(
459474
"assert_constant(…)",
460475
CompletionItemKind::FUNCTION,
461476
"assert_constant(${1:x})",
462-
Some("fn(T)".to_string()),
477+
"fn(T)",
463478
),
464479
snippet_completion_item(
465480
"assert_eq(…)",
@@ -1063,17 +1078,17 @@ mod completion_tests {
10631078
assert_completion(
10641079
src,
10651080
vec![
1066-
snippet_completion_item(
1081+
function_completion_item(
10671082
"foobar(…)",
10681083
CompletionItemKind::FUNCTION,
10691084
"foobar(${1:x})",
1070-
Some("fn(self, i32)".to_string()),
1085+
"fn(self, i32)".to_string(),
10711086
),
1072-
snippet_completion_item(
1087+
function_completion_item(
10731088
"foobar2(…)",
10741089
CompletionItemKind::FUNCTION,
10751090
"foobar2(${1:x})",
1076-
Some("fn(&mut self, i32)".to_string()),
1091+
"fn(&mut self, i32)".to_string(),
10771092
),
10781093
],
10791094
)
@@ -1102,11 +1117,11 @@ mod completion_tests {
11021117
"#;
11031118
assert_completion(
11041119
src,
1105-
vec![snippet_completion_item(
1120+
vec![function_completion_item(
11061121
"foobar(…)",
11071122
CompletionItemKind::FUNCTION,
11081123
"foobar(${1:x})",
1109-
Some("fn(self, i32)".to_string()),
1124+
"fn(self, i32)",
11101125
)],
11111126
)
11121127
.await;
@@ -1131,11 +1146,11 @@ mod completion_tests {
11311146
"#;
11321147
assert_completion(
11331148
src,
1134-
vec![snippet_completion_item(
1149+
vec![function_completion_item(
11351150
"foobar(…)",
11361151
CompletionItemKind::FUNCTION,
11371152
"foobar(${1:x})",
1138-
Some("fn(self, i32)".to_string()),
1153+
"fn(self, i32)".to_string(),
11391154
)],
11401155
)
11411156
.await;
@@ -1161,28 +1176,28 @@ mod completion_tests {
11611176
src,
11621177
vec![
11631178
completion_item_with_sort_text(
1164-
snippet_completion_item(
1179+
function_completion_item(
11651180
"foobar(…)",
11661181
CompletionItemKind::FUNCTION,
11671182
"foobar(${1:self}, ${2:x})",
1168-
Some("fn(self, i32)".to_string()),
1183+
"fn(self, i32)",
11691184
),
11701185
self_mismatch_sort_text(),
11711186
),
11721187
completion_item_with_sort_text(
1173-
snippet_completion_item(
1188+
function_completion_item(
11741189
"foobar2(…)",
11751190
CompletionItemKind::FUNCTION,
11761191
"foobar2(${1:self}, ${2:x})",
1177-
Some("fn(&mut self, i32)".to_string()),
1192+
"fn(&mut self, i32)",
11781193
),
11791194
self_mismatch_sort_text(),
11801195
),
1181-
snippet_completion_item(
1196+
function_completion_item(
11821197
"foobar3(…)",
11831198
CompletionItemKind::FUNCTION,
11841199
"foobar3(${1:y})",
1185-
Some("fn(i32)".to_string()),
1200+
"fn(i32)",
11861201
),
11871202
],
11881203
)
@@ -1207,11 +1222,11 @@ mod completion_tests {
12071222
"#;
12081223
assert_completion(
12091224
src,
1210-
vec![snippet_completion_item(
1225+
vec![function_completion_item(
12111226
"foobar(…)",
12121227
CompletionItemKind::FUNCTION,
12131228
"foobar(${1:x})",
1214-
Some("fn(self, i32)".to_string()),
1229+
"fn(self, i32)".to_string(),
12151230
)],
12161231
)
12171232
.await;
@@ -1239,28 +1254,28 @@ mod completion_tests {
12391254
src,
12401255
vec![
12411256
completion_item_with_sort_text(
1242-
snippet_completion_item(
1257+
function_completion_item(
12431258
"foobar(…)",
12441259
CompletionItemKind::FUNCTION,
12451260
"foobar(${1:self}, ${2:x})",
1246-
Some("fn(self, i32)".to_string()),
1261+
"fn(self, i32)",
12471262
),
12481263
self_mismatch_sort_text(),
12491264
),
12501265
completion_item_with_sort_text(
1251-
snippet_completion_item(
1266+
function_completion_item(
12521267
"foobar2(…)",
12531268
CompletionItemKind::FUNCTION,
12541269
"foobar2(${1:self}, ${2:x})",
1255-
Some("fn(&mut self, i32)".to_string()),
1270+
"fn(&mut self, i32)",
12561271
),
12571272
self_mismatch_sort_text(),
12581273
),
1259-
snippet_completion_item(
1274+
function_completion_item(
12601275
"foobar3(…)",
12611276
CompletionItemKind::FUNCTION,
12621277
"foobar3(${1:y})",
1263-
Some("fn(i32)".to_string()),
1278+
"fn(i32)",
12641279
),
12651280
],
12661281
)
@@ -1317,11 +1332,11 @@ mod completion_tests {
13171332
"#;
13181333
assert_completion(
13191334
src,
1320-
vec![snippet_completion_item(
1335+
vec![function_completion_item(
13211336
"foo()",
13221337
CompletionItemKind::FUNCTION,
13231338
"foo()",
1324-
Some("fn(self) -> Foo".to_string()),
1339+
"fn(self) -> Foo".to_string(),
13251340
)],
13261341
)
13271342
.await;

tooling/lsp/src/requests/mod.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ mod inlay_hint;
4646
mod profile_run;
4747
mod references;
4848
mod rename;
49+
mod signature_help;
4950
mod test_run;
5051
mod tests;
5152

@@ -56,7 +57,8 @@ pub(crate) use {
5657
goto_definition::on_goto_type_definition_request, hover::on_hover_request,
5758
inlay_hint::on_inlay_hint_request, profile_run::on_profile_run_request,
5859
references::on_references_request, rename::on_prepare_rename_request,
59-
rename::on_rename_request, test_run::on_test_run_request, tests::on_tests_request,
60+
rename::on_rename_request, signature_help::on_signature_help_request,
61+
test_run::on_test_run_request, tests::on_tests_request,
6062
};
6163

6264
/// LSP client will send initialization request after the server has started.
@@ -241,6 +243,15 @@ pub(crate) fn on_initialize(
241243
},
242244
completion_item: None,
243245
})),
246+
signature_help_provider: Some(lsp_types::OneOf::Right(
247+
lsp_types::SignatureHelpOptions {
248+
trigger_characters: Some(vec!["(".to_string(), ",".to_string()]),
249+
retrigger_characters: None,
250+
work_done_progress_options: WorkDoneProgressOptions {
251+
work_done_progress: None,
252+
},
253+
},
254+
)),
244255
},
245256
server_info: None,
246257
})

0 commit comments

Comments
 (0)