aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide_api/src/inlay_hints.rs98
-rw-r--r--crates/ra_lsp_server/src/main_loop/handlers.rs9
-rw-r--r--crates/ra_lsp_server/src/req.rs7
-rw-r--r--editors/code/src/commands/inlay_hints.ts76
-rw-r--r--editors/code/src/extension.ts20
5 files changed, 98 insertions, 112 deletions
diff --git a/crates/ra_ide_api/src/inlay_hints.rs b/crates/ra_ide_api/src/inlay_hints.rs
index a524e014f..7b9190314 100644
--- a/crates/ra_ide_api/src/inlay_hints.rs
+++ b/crates/ra_ide_api/src/inlay_hints.rs
@@ -9,14 +9,9 @@ use ra_syntax::{
9 SmolStr, SyntaxKind, SyntaxNode, TextRange, 9 SmolStr, SyntaxKind, SyntaxNode, TextRange,
10}; 10};
11 11
12#[derive(Debug, PartialEq, Eq, Clone)] 12#[derive(Debug, PartialEq, Eq)]
13pub enum InlayKind { 13pub enum InlayKind {
14 LetBindingType, 14 TypeHint,
15 ClosureParameterType,
16 ForExpressionBindingType,
17 IfExpressionType,
18 WhileLetExpressionType,
19 MatchArmType,
20} 15}
21 16
22#[derive(Debug)] 17#[derive(Debug)]
@@ -46,7 +41,7 @@ fn get_inlay_hints(
46 } 41 }
47 let pat = let_statement.pat()?; 42 let pat = let_statement.pat()?;
48 let analyzer = SourceAnalyzer::new(db, file_id, let_statement.syntax(), None); 43 let analyzer = SourceAnalyzer::new(db, file_id, let_statement.syntax(), None);
49 Some(get_pat_hints(db, &analyzer, pat, InlayKind::LetBindingType, false)) 44 Some(get_pat_type_hints(db, &analyzer, pat, false))
50 }) 45 })
51 .visit(|closure_parameter: LambdaExpr| { 46 .visit(|closure_parameter: LambdaExpr| {
52 let analyzer = SourceAnalyzer::new(db, file_id, closure_parameter.syntax(), None); 47 let analyzer = SourceAnalyzer::new(db, file_id, closure_parameter.syntax(), None);
@@ -55,15 +50,7 @@ fn get_inlay_hints(
55 .params() 50 .params()
56 .filter(|closure_param| closure_param.ascribed_type().is_none()) 51 .filter(|closure_param| closure_param.ascribed_type().is_none())
57 .filter_map(|closure_param| closure_param.pat()) 52 .filter_map(|closure_param| closure_param.pat())
58 .map(|root_pat| { 53 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, false))
59 get_pat_hints(
60 db,
61 &analyzer,
62 root_pat,
63 InlayKind::ClosureParameterType,
64 false,
65 )
66 })
67 .flatten() 54 .flatten()
68 .collect() 55 .collect()
69 }) 56 })
@@ -71,17 +58,17 @@ fn get_inlay_hints(
71 .visit(|for_expression: ForExpr| { 58 .visit(|for_expression: ForExpr| {
72 let pat = for_expression.pat()?; 59 let pat = for_expression.pat()?;
73 let analyzer = SourceAnalyzer::new(db, file_id, for_expression.syntax(), None); 60 let analyzer = SourceAnalyzer::new(db, file_id, for_expression.syntax(), None);
74 Some(get_pat_hints(db, &analyzer, pat, InlayKind::ForExpressionBindingType, false)) 61 Some(get_pat_type_hints(db, &analyzer, pat, false))
75 }) 62 })
76 .visit(|if_expr: IfExpr| { 63 .visit(|if_expr: IfExpr| {
77 let pat = if_expr.condition()?.pat()?; 64 let pat = if_expr.condition()?.pat()?;
78 let analyzer = SourceAnalyzer::new(db, file_id, if_expr.syntax(), None); 65 let analyzer = SourceAnalyzer::new(db, file_id, if_expr.syntax(), None);
79 Some(get_pat_hints(db, &analyzer, pat, InlayKind::IfExpressionType, true)) 66 Some(get_pat_type_hints(db, &analyzer, pat, true))
80 }) 67 })
81 .visit(|while_expr: WhileExpr| { 68 .visit(|while_expr: WhileExpr| {
82 let pat = while_expr.condition()?.pat()?; 69 let pat = while_expr.condition()?.pat()?;
83 let analyzer = SourceAnalyzer::new(db, file_id, while_expr.syntax(), None); 70 let analyzer = SourceAnalyzer::new(db, file_id, while_expr.syntax(), None);
84 Some(get_pat_hints(db, &analyzer, pat, InlayKind::WhileLetExpressionType, true)) 71 Some(get_pat_type_hints(db, &analyzer, pat, true))
85 }) 72 })
86 .visit(|match_arm_list: MatchArmList| { 73 .visit(|match_arm_list: MatchArmList| {
87 let analyzer = SourceAnalyzer::new(db, file_id, match_arm_list.syntax(), None); 74 let analyzer = SourceAnalyzer::new(db, file_id, match_arm_list.syntax(), None);
@@ -90,9 +77,7 @@ fn get_inlay_hints(
90 .arms() 77 .arms()
91 .map(|match_arm| match_arm.pats()) 78 .map(|match_arm| match_arm.pats())
92 .flatten() 79 .flatten()
93 .map(|root_pat| { 80 .map(|root_pat| get_pat_type_hints(db, &analyzer, root_pat, true))
94 get_pat_hints(db, &analyzer, root_pat, InlayKind::MatchArmType, true)
95 })
96 .flatten() 81 .flatten()
97 .collect(), 82 .collect(),
98 ) 83 )
@@ -100,11 +85,10 @@ fn get_inlay_hints(
100 .accept(&node)? 85 .accept(&node)?
101} 86}
102 87
103fn get_pat_hints( 88fn get_pat_type_hints(
104 db: &RootDatabase, 89 db: &RootDatabase,
105 analyzer: &SourceAnalyzer, 90 analyzer: &SourceAnalyzer,
106 root_pat: Pat, 91 root_pat: Pat,
107 kind: InlayKind,
108 skip_root_pat_hint: bool, 92 skip_root_pat_hint: bool,
109) -> Vec<InlayHint> { 93) -> Vec<InlayHint> {
110 let original_pat = &root_pat.clone(); 94 let original_pat = &root_pat.clone();
@@ -118,7 +102,7 @@ fn get_pat_hints(
118 }) 102 })
119 .map(|(range, pat_type)| InlayHint { 103 .map(|(range, pat_type)| InlayHint {
120 range, 104 range,
121 kind: kind.clone(), 105 kind: InlayKind::TypeHint,
122 label: pat_type.display(db).to_string().into(), 106 label: pat_type.display(db).to_string().into(),
123 }) 107 })
124 .collect() 108 .collect()
@@ -232,52 +216,52 @@ fn main() {
232 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[ 216 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
233 InlayHint { 217 InlayHint {
234 range: [193; 197), 218 range: [193; 197),
235 kind: LetBindingType, 219 kind: TypeHint,
236 label: "i32", 220 label: "i32",
237 }, 221 },
238 InlayHint { 222 InlayHint {
239 range: [236; 244), 223 range: [236; 244),
240 kind: LetBindingType, 224 kind: TypeHint,
241 label: "i32", 225 label: "i32",
242 }, 226 },
243 InlayHint { 227 InlayHint {
244 range: [275; 279), 228 range: [275; 279),
245 kind: LetBindingType, 229 kind: TypeHint,
246 label: "&str", 230 label: "&str",
247 }, 231 },
248 InlayHint { 232 InlayHint {
249 range: [539; 543), 233 range: [539; 543),
250 kind: LetBindingType, 234 kind: TypeHint,
251 label: "(i32, char)", 235 label: "(i32, char)",
252 }, 236 },
253 InlayHint { 237 InlayHint {
254 range: [566; 567), 238 range: [566; 567),
255 kind: LetBindingType, 239 kind: TypeHint,
256 label: "i32", 240 label: "i32",
257 }, 241 },
258 InlayHint { 242 InlayHint {
259 range: [570; 571), 243 range: [570; 571),
260 kind: LetBindingType, 244 kind: TypeHint,
261 label: "i32", 245 label: "i32",
262 }, 246 },
263 InlayHint { 247 InlayHint {
264 range: [573; 574), 248 range: [573; 574),
265 kind: LetBindingType, 249 kind: TypeHint,
266 label: "i32", 250 label: "i32",
267 }, 251 },
268 InlayHint { 252 InlayHint {
269 range: [584; 585), 253 range: [584; 585),
270 kind: LetBindingType, 254 kind: TypeHint,
271 label: "i32", 255 label: "i32",
272 }, 256 },
273 InlayHint { 257 InlayHint {
274 range: [577; 578), 258 range: [577; 578),
275 kind: LetBindingType, 259 kind: TypeHint,
276 label: "f64", 260 label: "f64",
277 }, 261 },
278 InlayHint { 262 InlayHint {
279 range: [580; 581), 263 range: [580; 581),
280 kind: LetBindingType, 264 kind: TypeHint,
281 label: "f64", 265 label: "f64",
282 }, 266 },
283]"# 267]"#
@@ -299,12 +283,12 @@ fn main() {
299 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[ 283 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
300 InlayHint { 284 InlayHint {
301 range: [21; 30), 285 range: [21; 30),
302 kind: LetBindingType, 286 kind: TypeHint,
303 label: "i32", 287 label: "i32",
304 }, 288 },
305 InlayHint { 289 InlayHint {
306 range: [57; 66), 290 range: [57; 66),
307 kind: ClosureParameterType, 291 kind: TypeHint,
308 label: "i32", 292 label: "i32",
309 }, 293 },
310]"# 294]"#
@@ -326,12 +310,12 @@ fn main() {
326 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[ 310 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
327 InlayHint { 311 InlayHint {
328 range: [21; 30), 312 range: [21; 30),
329 kind: LetBindingType, 313 kind: TypeHint,
330 label: "i32", 314 label: "i32",
331 }, 315 },
332 InlayHint { 316 InlayHint {
333 range: [44; 53), 317 range: [44; 53),
334 kind: ForExpressionBindingType, 318 kind: TypeHint,
335 label: "i32", 319 label: "i32",
336 }, 320 },
337]"# 321]"#
@@ -364,7 +348,7 @@ fn main() {
364 if let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {}; 348 if let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {};
365 if let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {}; 349 if let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {};
366 if let CustomOption::Some(Test { b: y, .. }) = &test {}; 350 if let CustomOption::Some(Test { b: y, .. }) = &test {};
367 351
368 if test == CustomOption::None {} 352 if test == CustomOption::None {}
369}"#, 353}"#,
370 ); 354 );
@@ -372,27 +356,27 @@ fn main() {
372 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[ 356 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
373 InlayHint { 357 InlayHint {
374 range: [166; 170), 358 range: [166; 170),
375 kind: LetBindingType, 359 kind: TypeHint,
376 label: "CustomOption<Test>", 360 label: "CustomOption<Test>",
377 }, 361 },
378 InlayHint { 362 InlayHint {
379 range: [334; 338), 363 range: [334; 338),
380 kind: IfExpressionType, 364 kind: TypeHint,
381 label: "&Test", 365 label: "&Test",
382 }, 366 },
383 InlayHint { 367 InlayHint {
384 range: [389; 390), 368 range: [389; 390),
385 kind: IfExpressionType, 369 kind: TypeHint,
386 label: "&CustomOption<u32>", 370 label: "&CustomOption<u32>",
387 }, 371 },
388 InlayHint { 372 InlayHint {
389 range: [392; 393), 373 range: [392; 393),
390 kind: IfExpressionType, 374 kind: TypeHint,
391 label: "&u8", 375 label: "&u8",
392 }, 376 },
393 InlayHint { 377 InlayHint {
394 range: [531; 532), 378 range: [531; 532),
395 kind: IfExpressionType, 379 kind: TypeHint,
396 label: "&u32", 380 label: "&u32",
397 }, 381 },
398]"# 382]"#
@@ -425,7 +409,7 @@ fn main() {
425 while let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {}; 409 while let CustomOption::Some(Test { a: CustomOption::Some(x), b: y }) = &test {};
426 while let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {}; 410 while let CustomOption::Some(Test { a: CustomOption::None, b: y }) = &test {};
427 while let CustomOption::Some(Test { b: y, .. }) = &test {}; 411 while let CustomOption::Some(Test { b: y, .. }) = &test {};
428 412
429 while test == CustomOption::None {} 413 while test == CustomOption::None {}
430}"#, 414}"#,
431 ); 415 );
@@ -433,7 +417,7 @@ fn main() {
433 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[ 417 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
434 InlayHint { 418 InlayHint {
435 range: [166; 170), 419 range: [166; 170),
436 kind: LetBindingType, 420 kind: TypeHint,
437 label: "CustomOption<Test>", 421 label: "CustomOption<Test>",
438 }, 422 },
439]"# 423]"#
@@ -445,7 +429,7 @@ fn main() {
445 let (analysis, file_id) = single_file( 429 let (analysis, file_id) = single_file(
446 r#" 430 r#"
447#[derive(PartialEq)] 431#[derive(PartialEq)]
448enum CustomOption<T> { 432enum CustomOption<T> {
449 None, 433 None,
450 Some(T), 434 Some(T),
451} 435}
@@ -473,23 +457,23 @@ fn main() {
473 457
474 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[ 458 assert_debug_snapshot_matches!(analysis.inlay_hints(file_id).unwrap(), @r#"[
475 InlayHint { 459 InlayHint {
476 range: [312; 316), 460 range: [311; 315),
477 kind: MatchArmType, 461 kind: TypeHint,
478 label: "Test", 462 label: "Test",
479 }, 463 },
480 InlayHint { 464 InlayHint {
481 range: [359; 360), 465 range: [358; 359),
482 kind: MatchArmType, 466 kind: TypeHint,
483 label: "CustomOption<u32>", 467 label: "CustomOption<u32>",
484 }, 468 },
485 InlayHint { 469 InlayHint {
486 range: [362; 363), 470 range: [361; 362),
487 kind: MatchArmType, 471 kind: TypeHint,
488 label: "u8", 472 label: "u8",
489 }, 473 },
490 InlayHint { 474 InlayHint {
491 range: [485; 486), 475 range: [484; 485),
492 kind: MatchArmType, 476 kind: TypeHint,
493 label: "u32", 477 label: "u32",
494 }, 478 },
495]"# 479]"#
diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs
index 005ce08b3..686ee5d12 100644
--- a/crates/ra_lsp_server/src/main_loop/handlers.rs
+++ b/crates/ra_lsp_server/src/main_loop/handlers.rs
@@ -885,14 +885,7 @@ pub fn handle_inlay_hints(
885 label: api_type.label.to_string(), 885 label: api_type.label.to_string(),
886 range: api_type.range.conv_with(&line_index), 886 range: api_type.range.conv_with(&line_index),
887 kind: match api_type.kind { 887 kind: match api_type.kind {
888 ra_ide_api::InlayKind::LetBindingType => InlayKind::LetBindingType, 888 ra_ide_api::InlayKind::TypeHint => InlayKind::TypeHint,
889 ra_ide_api::InlayKind::ClosureParameterType => InlayKind::ClosureParameterType,
890 ra_ide_api::InlayKind::ForExpressionBindingType => {
891 InlayKind::ForExpressionBindingType
892 }
893 ra_ide_api::InlayKind::IfExpressionType => InlayKind::IfExpressionType,
894 ra_ide_api::InlayKind::WhileLetExpressionType => InlayKind::WhileLetExpressionType,
895 ra_ide_api::InlayKind::MatchArmType => InlayKind::MatchArmType,
896 }, 889 },
897 }) 890 })
898 .collect()) 891 .collect())
diff --git a/crates/ra_lsp_server/src/req.rs b/crates/ra_lsp_server/src/req.rs
index 570438643..6b986bcc9 100644
--- a/crates/ra_lsp_server/src/req.rs
+++ b/crates/ra_lsp_server/src/req.rs
@@ -213,12 +213,7 @@ pub struct InlayHintsParams {
213 213
214#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] 214#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)]
215pub enum InlayKind { 215pub enum InlayKind {
216 LetBindingType, 216 TypeHint,
217 ClosureParameterType,
218 ForExpressionBindingType,
219 IfExpressionType,
220 WhileLetExpressionType,
221 MatchArmType,
222} 217}
223 218
224#[derive(Debug, Deserialize, Serialize)] 219#[derive(Debug, Deserialize, Serialize)]
diff --git a/editors/code/src/commands/inlay_hints.ts b/editors/code/src/commands/inlay_hints.ts
index 3ba9da48b..5393a2bc9 100644
--- a/editors/code/src/commands/inlay_hints.ts
+++ b/editors/code/src/commands/inlay_hints.ts
@@ -22,53 +22,56 @@ const typeHintDecorationType = vscode.window.createTextEditorDecorationType({
22export class HintsUpdater { 22export class HintsUpdater {
23 private displayHints = true; 23 private displayHints = true;
24 24
25 public async loadHints(
26 editor: vscode.TextEditor | undefined
27 ): Promise<void> {
28 if (
29 this.displayHints &&
30 editor !== undefined &&
31 this.isRustDocument(editor.document)
32 ) {
33 await this.updateDecorationsFromServer(
34 editor.document.uri.toString(),
35 editor
36 );
37 }
38 }
39
40 public async toggleHintsDisplay(displayHints: boolean): Promise<void> { 25 public async toggleHintsDisplay(displayHints: boolean): Promise<void> {
41 if (this.displayHints !== displayHints) { 26 if (this.displayHints !== displayHints) {
42 this.displayHints = displayHints; 27 this.displayHints = displayHints;
43 28 return this.refreshVisibleEditorsHints(
44 if (displayHints) { 29 displayHints ? undefined : []
45 return this.updateHints(); 30 );
46 } else {
47 const editor = vscode.window.activeTextEditor;
48 if (editor != null) {
49 return editor.setDecorations(typeHintDecorationType, []);
50 }
51 }
52 } 31 }
53 } 32 }
54 33
55 public async updateHints(cause?: TextDocumentChangeEvent): Promise<void> { 34 public async refreshHintsForVisibleEditors(
35 cause?: TextDocumentChangeEvent
36 ): Promise<void> {
56 if (!this.displayHints) { 37 if (!this.displayHints) {
57 return; 38 return;
58 } 39 }
59 const editor = vscode.window.activeTextEditor; 40 if (
60 if (editor == null) { 41 cause !== undefined &&
42 (cause.contentChanges.length === 0 ||
43 !this.isRustDocument(cause.document))
44 ) {
61 return; 45 return;
62 } 46 }
63 const document = cause == null ? editor.document : cause.document; 47 return this.refreshVisibleEditorsHints();
64 if (!this.isRustDocument(document)) { 48 }
65 return; 49
50 private async refreshVisibleEditorsHints(
51 newDecorations?: vscode.DecorationOptions[]
52 ) {
53 const promises: Array<Promise<void>> = [];
54
55 for (const rustEditor of vscode.window.visibleTextEditors.filter(
56 editor => this.isRustDocument(editor.document)
57 )) {
58 if (newDecorations !== undefined) {
59 promises.push(
60 Promise.resolve(
61 rustEditor.setDecorations(
62 typeHintDecorationType,
63 newDecorations
64 )
65 )
66 );
67 } else {
68 promises.push(this.updateDecorationsFromServer(rustEditor));
69 }
66 } 70 }
67 71
68 return await this.updateDecorationsFromServer( 72 for (const promise of promises) {
69 document.uri.toString(), 73 await promise;
70 editor 74 }
71 );
72 } 75 }
73 76
74 private isRustDocument(document: vscode.TextDocument): boolean { 77 private isRustDocument(document: vscode.TextDocument): boolean {
@@ -76,11 +79,10 @@ export class HintsUpdater {
76 } 79 }
77 80
78 private async updateDecorationsFromServer( 81 private async updateDecorationsFromServer(
79 documentUri: string,
80 editor: TextEditor 82 editor: TextEditor
81 ): Promise<void> { 83 ): Promise<void> {
82 const newHints = await this.queryHints(documentUri); 84 const newHints = await this.queryHints(editor.document.uri.toString());
83 if (newHints != null) { 85 if (newHints !== null) {
84 const newDecorations = newHints.map(hint => ({ 86 const newDecorations = newHints.map(hint => ({
85 range: hint.range, 87 range: hint.range,
86 renderOptions: { after: { contentText: `: ${hint.label}` } } 88 renderOptions: { after: { contentText: `: ${hint.label}` } }
diff --git a/editors/code/src/extension.ts b/editors/code/src/extension.ts
index c6efc2e7e..39fe6efd8 100644
--- a/editors/code/src/extension.ts
+++ b/editors/code/src/extension.ts
@@ -151,15 +151,27 @@ export function activate(context: vscode.ExtensionContext) {
151 151
152 if (Server.config.displayInlayHints) { 152 if (Server.config.displayInlayHints) {
153 const hintsUpdater = new HintsUpdater(); 153 const hintsUpdater = new HintsUpdater();
154 hintsUpdater.loadHints(vscode.window.activeTextEditor).then(() => { 154 hintsUpdater.refreshHintsForVisibleEditors().then(() => {
155 // vscode may ignore top level hintsUpdater.refreshHintsForVisibleEditors()
156 // so update the hints once when the focus changes to guarantee their presence
157 let editorChangeDisposable: vscode.Disposable | null = null;
158 editorChangeDisposable = vscode.window.onDidChangeActiveTextEditor(
159 _ => {
160 if (editorChangeDisposable !== null) {
161 editorChangeDisposable.dispose();
162 }
163 return hintsUpdater.refreshHintsForVisibleEditors();
164 }
165 );
166
155 disposeOnDeactivation( 167 disposeOnDeactivation(
156 vscode.window.onDidChangeActiveTextEditor(editor => 168 vscode.window.onDidChangeVisibleTextEditors(_ =>
157 hintsUpdater.loadHints(editor) 169 hintsUpdater.refreshHintsForVisibleEditors()
158 ) 170 )
159 ); 171 );
160 disposeOnDeactivation( 172 disposeOnDeactivation(
161 vscode.workspace.onDidChangeTextDocument(e => 173 vscode.workspace.onDidChangeTextDocument(e =>
162 hintsUpdater.updateHints(e) 174 hintsUpdater.refreshHintsForVisibleEditors(e)
163 ) 175 )
164 ); 176 );
165 disposeOnDeactivation( 177 disposeOnDeactivation(