aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorimtsuki <[email protected]>2020-01-14 17:02:01 +0000
committerimtsuki <[email protected]>2020-01-14 17:18:52 +0000
commitc390e92fdd25ced46c589bfbff94e4b0bc4d9c38 (patch)
treebd33983bd3d77c1442f7814d595cbf0506f421da
parentd8d8c20077702b8537086d49914d02654a46ebc5 (diff)
Add inlay parameter name hints for function calls
Signed-off-by: imtsuki <[email protected]>
-rw-r--r--crates/ra_ide/src/display/function_signature.rs22
-rw-r--r--crates/ra_ide/src/inlay_hints.rs141
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs1
-rw-r--r--crates/ra_lsp_server/src/req.rs1
-rw-r--r--editors/code/package.json2
-rw-r--r--editors/code/src/inlay_hints.ts51
6 files changed, 205 insertions, 13 deletions
diff --git a/crates/ra_ide/src/display/function_signature.rs b/crates/ra_ide/src/display/function_signature.rs
index 324ad9552..b0eb1f194 100644
--- a/crates/ra_ide/src/display/function_signature.rs
+++ b/crates/ra_ide/src/display/function_signature.rs
@@ -34,6 +34,8 @@ pub struct FunctionSignature {
34 pub generic_parameters: Vec<String>, 34 pub generic_parameters: Vec<String>,
35 /// Parameters of the function 35 /// Parameters of the function
36 pub parameters: Vec<String>, 36 pub parameters: Vec<String>,
37 /// Parameter names of the function
38 pub parameter_names: Vec<String>,
37 /// Optional return type 39 /// Optional return type
38 pub ret_type: Option<String>, 40 pub ret_type: Option<String>,
39 /// Where predicates 41 /// Where predicates
@@ -75,6 +77,7 @@ impl FunctionSignature {
75 name: node.name().map(|n| n.text().to_string()), 77 name: node.name().map(|n| n.text().to_string()),
76 ret_type: node.name().map(|n| n.text().to_string()), 78 ret_type: node.name().map(|n| n.text().to_string()),
77 parameters: params, 79 parameters: params,
80 parameter_names: vec![],
78 generic_parameters: generic_parameters(&node), 81 generic_parameters: generic_parameters(&node),
79 where_predicates: where_predicates(&node), 82 where_predicates: where_predicates(&node),
80 doc: None, 83 doc: None,
@@ -114,6 +117,7 @@ impl FunctionSignature {
114 name: Some(name), 117 name: Some(name),
115 ret_type: None, 118 ret_type: None,
116 parameters: params, 119 parameters: params,
120 parameter_names: vec![],
117 generic_parameters: vec![], 121 generic_parameters: vec![],
118 where_predicates: vec![], 122 where_predicates: vec![],
119 doc: None, 123 doc: None,
@@ -134,6 +138,7 @@ impl FunctionSignature {
134 name: node.name().map(|n| n.text().to_string()), 138 name: node.name().map(|n| n.text().to_string()),
135 ret_type: None, 139 ret_type: None,
136 parameters: params, 140 parameters: params,
141 parameter_names: vec![],
137 generic_parameters: vec![], 142 generic_parameters: vec![],
138 where_predicates: vec![], 143 where_predicates: vec![],
139 doc: None, 144 doc: None,
@@ -157,6 +162,22 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
157 res 162 res
158 } 163 }
159 164
165 fn param_name_list(node: &ast::FnDef) -> Vec<String> {
166 let mut res = vec![];
167 if let Some(param_list) = node.param_list() {
168 if let Some(self_param) = param_list.self_param() {
169 res.push(self_param.syntax().text().to_string())
170 }
171
172 res.extend(
173 param_list
174 .params()
175 .map(|param| param.pat().unwrap().syntax().text().to_string()),
176 );
177 }
178 res
179 }
180
160 FunctionSignature { 181 FunctionSignature {
161 kind: CallableKind::Function, 182 kind: CallableKind::Function,
162 visibility: node.visibility().map(|n| n.syntax().text().to_string()), 183 visibility: node.visibility().map(|n| n.syntax().text().to_string()),
@@ -166,6 +187,7 @@ impl From<&'_ ast::FnDef> for FunctionSignature {
166 .and_then(|r| r.type_ref()) 187 .and_then(|r| r.type_ref())
167 .map(|n| n.syntax().text().to_string()), 188 .map(|n| n.syntax().text().to_string()),
168 parameters: param_list(node), 189 parameters: param_list(node),
190 parameter_names: param_name_list(node),
169 generic_parameters: generic_parameters(node), 191 generic_parameters: generic_parameters(node),
170 where_predicates: where_predicates(node), 192 where_predicates: where_predicates(node),
171 // docs are processed separately 193 // docs are processed separately
diff --git a/crates/ra_ide/src/inlay_hints.rs b/crates/ra_ide/src/inlay_hints.rs
index 977aafc51..83e4588c1 100644
--- a/crates/ra_ide/src/inlay_hints.rs
+++ b/crates/ra_ide/src/inlay_hints.rs
@@ -4,15 +4,16 @@ use hir::{HirDisplay, SourceAnalyzer};
4use once_cell::unsync::Lazy; 4use once_cell::unsync::Lazy;
5use ra_prof::profile; 5use ra_prof::profile;
6use ra_syntax::{ 6use ra_syntax::{
7 ast::{self, AstNode, TypeAscriptionOwner}, 7 ast::{self, ArgListOwner, AstNode, TypeAscriptionOwner},
8 match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange, 8 match_ast, SmolStr, SourceFile, SyntaxKind, SyntaxNode, TextRange,
9}; 9};
10 10
11use crate::{db::RootDatabase, FileId}; 11use crate::{db::RootDatabase, FileId, FunctionSignature};
12 12
13#[derive(Debug, PartialEq, Eq)] 13#[derive(Debug, PartialEq, Eq)]
14pub enum InlayKind { 14pub enum InlayKind {
15 TypeHint, 15 TypeHint,
16 ParameterHint,
16} 17}
17 18
18#[derive(Debug)] 19#[derive(Debug)]
@@ -87,10 +88,79 @@ fn get_inlay_hints(
87 .collect(), 88 .collect(),
88 ) 89 )
89 }, 90 },
91 ast::CallExpr(it) => {
92 get_param_name_hints(db, &analyzer, ast::Expr::from(it))
93 },
94 ast::MethodCallExpr(it) => {
95 get_param_name_hints(db, &analyzer, ast::Expr::from(it))
96 },
90 _ => None, 97 _ => None,
91 } 98 }
92 } 99 }
93} 100}
101fn get_param_name_hints(
102 db: &RootDatabase,
103 analyzer: &SourceAnalyzer,
104 expr: ast::Expr,
105) -> Option<Vec<InlayHint>> {
106 let args = match &expr {
107 ast::Expr::CallExpr(expr) => Some(expr.arg_list()?.args()),
108 ast::Expr::MethodCallExpr(expr) => Some(expr.arg_list()?.args()),
109 _ => None,
110 }?;
111
112 let mut parameters = get_fn_signature(db, analyzer, &expr)?.parameter_names.into_iter();
113
114 if let ast::Expr::MethodCallExpr(_) = &expr {
115 parameters.next();
116 };
117
118 let hints = parameters
119 .zip(args)
120 .filter_map(|(param, arg)| {
121 if arg.syntax().kind() == SyntaxKind::LITERAL {
122 Some((arg.syntax().text_range(), param))
123 } else {
124 None
125 }
126 })
127 .map(|(range, param_name)| InlayHint {
128 range,
129 kind: InlayKind::ParameterHint,
130 label: param_name.into(),
131 })
132 .collect();
133
134 Some(hints)
135}
136
137fn get_fn_signature(
138 db: &RootDatabase,
139 analyzer: &SourceAnalyzer,
140 expr: &ast::Expr,
141) -> Option<FunctionSignature> {
142 match expr {
143 ast::Expr::CallExpr(expr) => {
144 // FIXME: Type::as_callable is broken for closures
145 let callable_def = analyzer.type_of(db, &expr.expr()?)?.as_callable()?;
146 match callable_def {
147 hir::CallableDef::FunctionId(it) => {
148 let fn_def = it.into();
149 Some(FunctionSignature::from_hir(db, fn_def))
150 }
151 hir::CallableDef::StructId(it) => FunctionSignature::from_struct(db, it.into()),
152 hir::CallableDef::EnumVariantId(it) => {
153 FunctionSignature::from_enum_variant(db, it.into())
154 }
155 }
156 }
157 ast::Expr::MethodCallExpr(expr) => {
158 let fn_def = analyzer.resolve_method_call(&expr)?;
159 Some(FunctionSignature::from_hir(db, fn_def))
160 }
161 _ => None,
162 }
163}
94 164
95fn get_pat_type_hints( 165fn get_pat_type_hints(
96 db: &RootDatabase, 166 db: &RootDatabase,
@@ -605,4 +675,71 @@ fn main() {
605 "### 675 "###
606 ); 676 );
607 } 677 }
678
679 #[test]
680 fn function_call_parameter_hint() {
681 let (analysis, file_id) = single_file(
682 r#"
683struct Test {}
684
685impl Test {
686 fn method(&self, param: i32) -> i32 {
687 param * 2
688 }
689}
690
691fn test_func(foo: i32, bar: i32, msg: &str, _: i32, last: i32) -> i32 {
692 foo + bar
693}
694
695fn main() {
696 let not_literal = 1;
697 let _: i32 = test_func(1, 2, "hello", 3, not_literal);
698 let t: Test = Test {};
699 t.method(123);
700 Test::method(&t, 3456);
701}"#,
702 );
703
704 assert_debug_snapshot!(analysis.inlay_hints(file_id, None).unwrap(), @r###"
705 [
706 InlayHint {
707 range: [207; 218),
708 kind: TypeHint,
709 label: "i32",
710 },
711 InlayHint {
712 range: [251; 252),
713 kind: ParameterHint,
714 label: "foo",
715 },
716 InlayHint {
717 range: [254; 255),
718 kind: ParameterHint,
719 label: "bar",
720 },
721 InlayHint {
722 range: [257; 264),
723 kind: ParameterHint,
724 label: "msg",
725 },
726 InlayHint {
727 range: [266; 267),
728 kind: ParameterHint,
729 label: "_",
730 },
731 InlayHint {
732 range: [322; 325),
733 kind: ParameterHint,
734 label: "param",
735 },
736 InlayHint {
737 range: [349; 353),
738 kind: ParameterHint,
739 label: "param",
740 },
741 ]
742 "###
743 );
744 }
608} 745}
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index a592f0a12..a9a8538b7 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -952,6 +952,7 @@ pub fn handle_inlay_hints(
952 range: api_type.range.conv_with(&line_index), 952 range: api_type.range.conv_with(&line_index),
953 kind: match api_type.kind { 953 kind: match api_type.kind {
954 ra_ide::InlayKind::TypeHint => InlayKind::TypeHint, 954 ra_ide::InlayKind::TypeHint => InlayKind::TypeHint,
955 ra_ide::InlayKind::ParameterHint => InlayKind::ParameterHint,
955 }, 956 },
956 }) 957 })
957 .collect()) 958 .collect())
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index 8098ff31d..dc327f53d 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -197,6 +197,7 @@ pub struct InlayHintsParams {
197#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] 197#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
198pub enum InlayKind { 198pub enum InlayKind {
199 TypeHint, 199 TypeHint,
200 ParameterHint,
200} 201}
201 202
202#[derive(Debug, Deserialize, Serialize)] 203#[derive(Debug, Deserialize, Serialize)]
diff --git a/editors/code/package.json b/editors/code/package.json
index 7c22d21d3..ed637d114 100644
--- a/editors/code/package.json
+++ b/editors/code/package.json
@@ -228,7 +228,7 @@
228 "rust-analyzer.displayInlayHints": { 228 "rust-analyzer.displayInlayHints": {
229 "type": "boolean", 229 "type": "boolean",
230 "default": true, 230 "default": true,
231 "description": "Display additional type information in the editor" 231 "description": "Display additional type and parameter information in the editor"
232 }, 232 },
233 "rust-analyzer.maxInlayHintLength": { 233 "rust-analyzer.maxInlayHintLength": {
234 "type": "number", 234 "type": "number",
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts
index 078d18f0f..c4206cf5b 100644
--- a/editors/code/src/inlay_hints.ts
+++ b/editors/code/src/inlay_hints.ts
@@ -38,6 +38,12 @@ const typeHintDecorationType = vscode.window.createTextEditorDecorationType({
38 }, 38 },
39}); 39});
40 40
41const parameterHintDecorationType = vscode.window.createTextEditorDecorationType({
42 before: {
43 color: new vscode.ThemeColor('rust_analyzer.inlayHint'),
44 }
45})
46
41class HintsUpdater { 47class HintsUpdater {
42 private pending: Map<string, vscode.CancellationTokenSource> = new Map(); 48 private pending: Map<string, vscode.CancellationTokenSource> = new Map();
43 private ctx: Ctx; 49 private ctx: Ctx;
@@ -55,7 +61,10 @@ class HintsUpdater {
55 if (this.enabled) { 61 if (this.enabled) {
56 await this.refresh(); 62 await this.refresh();
57 } else { 63 } else {
58 this.allEditors.forEach(it => this.setDecorations(it, [])); 64 this.allEditors.forEach(it => {
65 this.setTypeDecorations(it, []);
66 this.setParameterDecorations(it, []);
67 });
59 } 68 }
60 } 69 }
61 70
@@ -68,15 +77,27 @@ class HintsUpdater {
68 private async refreshEditor(editor: vscode.TextEditor): Promise<void> { 77 private async refreshEditor(editor: vscode.TextEditor): Promise<void> {
69 const newHints = await this.queryHints(editor.document.uri.toString()); 78 const newHints = await this.queryHints(editor.document.uri.toString());
70 if (newHints == null) return; 79 if (newHints == null) return;
71 const newDecorations = newHints.map(hint => ({ 80 const newTypeDecorations = newHints.filter(hint => hint.kind === 'TypeHint')
72 range: hint.range, 81 .map(hint => ({
73 renderOptions: { 82 range: hint.range,
74 after: { 83 renderOptions: {
75 contentText: `: ${hint.label}`, 84 after: {
85 contentText: `: ${hint.label}`,
86 },
76 }, 87 },
77 }, 88 }));
78 })); 89 this.setTypeDecorations(editor, newTypeDecorations);
79 this.setDecorations(editor, newDecorations); 90
91 const newParameterDecorations = newHints.filter(hint => hint.kind === 'ParameterHint')
92 .map(hint => ({
93 range: hint.range,
94 renderOptions: {
95 before: {
96 contentText: `${hint.label}: `,
97 },
98 },
99 }));
100 this.setParameterDecorations(editor, newParameterDecorations);
80 } 101 }
81 102
82 private get allEditors(): vscode.TextEditor[] { 103 private get allEditors(): vscode.TextEditor[] {
@@ -85,7 +106,7 @@ class HintsUpdater {
85 ); 106 );
86 } 107 }
87 108
88 private setDecorations( 109 private setTypeDecorations(
89 editor: vscode.TextEditor, 110 editor: vscode.TextEditor,
90 decorations: vscode.DecorationOptions[], 111 decorations: vscode.DecorationOptions[],
91 ) { 112 ) {
@@ -95,6 +116,16 @@ class HintsUpdater {
95 ); 116 );
96 } 117 }
97 118
119 private setParameterDecorations(
120 editor: vscode.TextEditor,
121 decorations: vscode.DecorationOptions[],
122 ) {
123 editor.setDecorations(
124 parameterHintDecorationType,
125 this.enabled ? decorations : [],
126 );
127 }
128
98 private async queryHints(documentUri: string): Promise<InlayHint[] | null> { 129 private async queryHints(documentUri: string): Promise<InlayHint[] | null> {
99 let client = this.ctx.client; 130 let client = this.ctx.client;
100 if (!client) return null; 131 if (!client) return null;