aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_hir_ty/src/display.rs29
-rw-r--r--docs/user/features.md6
-rw-r--r--editors/code/src/commands/syntax_tree.ts165
-rw-r--r--editors/code/src/util.ts4
4 files changed, 134 insertions, 70 deletions
diff --git a/crates/ra_hir_ty/src/display.rs b/crates/ra_hir_ty/src/display.rs
index c3d92a268..13ecd537a 100644
--- a/crates/ra_hir_ty/src/display.rs
+++ b/crates/ra_hir_ty/src/display.rs
@@ -190,8 +190,6 @@ impl HirDisplay for ApplicationTy {
190 }; 190 };
191 write!(f, "{}", name)?; 191 write!(f, "{}", name)?;
192 if self.parameters.len() > 0 { 192 if self.parameters.len() > 0 {
193 write!(f, "<")?;
194
195 let mut non_default_parameters = Vec::with_capacity(self.parameters.len()); 193 let mut non_default_parameters = Vec::with_capacity(self.parameters.len());
196 let parameters_to_write = if f.omit_verbose_types() { 194 let parameters_to_write = if f.omit_verbose_types() {
197 match self 195 match self
@@ -200,8 +198,8 @@ impl HirDisplay for ApplicationTy {
200 .map(|generic_def_id| f.db.generic_defaults(generic_def_id)) 198 .map(|generic_def_id| f.db.generic_defaults(generic_def_id))
201 .filter(|defaults| !defaults.is_empty()) 199 .filter(|defaults| !defaults.is_empty())
202 { 200 {
203 Option::None => self.parameters.0.as_ref(), 201 None => self.parameters.0.as_ref(),
204 Option::Some(default_parameters) => { 202 Some(default_parameters) => {
205 for (i, parameter) in self.parameters.iter().enumerate() { 203 for (i, parameter) in self.parameters.iter().enumerate() {
206 match (parameter, default_parameters.get(i)) { 204 match (parameter, default_parameters.get(i)) {
207 (&Ty::Unknown, _) | (_, None) => { 205 (&Ty::Unknown, _) | (_, None) => {
@@ -221,7 +219,7 @@ impl HirDisplay for ApplicationTy {
221 } else { 219 } else {
222 self.parameters.0.as_ref() 220 self.parameters.0.as_ref()
223 }; 221 };
224 222 write!(f, "<")?;
225 f.write_joined(parameters_to_write, ", ")?; 223 f.write_joined(parameters_to_write, ", ")?;
226 write!(f, ">")?; 224 write!(f, ">")?;
227 } 225 }
@@ -231,9 +229,9 @@ impl HirDisplay for ApplicationTy {
231 AssocContainerId::TraitId(it) => it, 229 AssocContainerId::TraitId(it) => it,
232 _ => panic!("not an associated type"), 230 _ => panic!("not an associated type"),
233 }; 231 };
234 let trait_name = f.db.trait_data(trait_).name.clone(); 232 let trait_ = f.db.trait_data(trait_);
235 let name = f.db.type_alias_data(type_alias).name.clone(); 233 let type_alias = f.db.type_alias_data(type_alias);
236 write!(f, "{}::{}", trait_name, name)?; 234 write!(f, "{}::{}", trait_.name, type_alias.name)?;
237 if self.parameters.len() > 0 { 235 if self.parameters.len() > 0 {
238 write!(f, "<")?; 236 write!(f, "<")?;
239 f.write_joined(&*self.parameters.0, ", ")?; 237 f.write_joined(&*self.parameters.0, ", ")?;
@@ -266,8 +264,8 @@ impl HirDisplay for ProjectionTy {
266 return write!(f, "{}", TYPE_HINT_TRUNCATION); 264 return write!(f, "{}", TYPE_HINT_TRUNCATION);
267 } 265 }
268 266
269 let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone(); 267 let trait_ = f.db.trait_data(self.trait_(f.db));
270 write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?; 268 write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_.name)?;
271 if self.parameters.len() > 1 { 269 if self.parameters.len() > 1 {
272 write!(f, "<")?; 270 write!(f, "<")?;
273 f.write_joined(&self.parameters[1..], ", ")?; 271 f.write_joined(&self.parameters[1..], ", ")?;
@@ -312,7 +310,7 @@ impl HirDisplay for Ty {
312 Ty::Opaque(_) => write!(f, "impl ")?, 310 Ty::Opaque(_) => write!(f, "impl ")?,
313 _ => unreachable!(), 311 _ => unreachable!(),
314 }; 312 };
315 write_bounds_like_dyn_trait(&predicates, f)?; 313 write_bounds_like_dyn_trait(predicates, f)?;
316 } 314 }
317 Ty::Unknown => write!(f, "{{unknown}}")?, 315 Ty::Unknown => write!(f, "{{unknown}}")?,
318 Ty::Infer(..) => write!(f, "_")?, 316 Ty::Infer(..) => write!(f, "_")?,
@@ -345,7 +343,7 @@ fn write_bounds_like_dyn_trait(
345 // We assume that the self type is $0 (i.e. the 343 // We assume that the self type is $0 (i.e. the
346 // existential) here, which is the only thing that's 344 // existential) here, which is the only thing that's
347 // possible in actual Rust, and hence don't print it 345 // possible in actual Rust, and hence don't print it
348 write!(f, "{}", f.db.trait_data(trait_ref.trait_).name.clone())?; 346 write!(f, "{}", f.db.trait_data(trait_ref.trait_).name)?;
349 if trait_ref.substs.len() > 1 { 347 if trait_ref.substs.len() > 1 {
350 write!(f, "<")?; 348 write!(f, "<")?;
351 f.write_joined(&trait_ref.substs[1..], ", ")?; 349 f.write_joined(&trait_ref.substs[1..], ", ")?;
@@ -362,9 +360,8 @@ fn write_bounds_like_dyn_trait(
362 write!(f, "<")?; 360 write!(f, "<")?;
363 angle_open = true; 361 angle_open = true;
364 } 362 }
365 let name = 363 let type_alias = f.db.type_alias_data(projection_pred.projection_ty.associated_ty);
366 f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name.clone(); 364 write!(f, "{} = ", type_alias.name)?;
367 write!(f, "{} = ", name)?;
368 projection_pred.ty.hir_fmt(f)?; 365 projection_pred.ty.hir_fmt(f)?;
369 } 366 }
370 GenericPredicate::Error => { 367 GenericPredicate::Error => {
@@ -398,7 +395,7 @@ impl TraitRef {
398 } else { 395 } else {
399 write!(f, ": ")?; 396 write!(f, ": ")?;
400 } 397 }
401 write!(f, "{}", f.db.trait_data(self.trait_).name.clone())?; 398 write!(f, "{}", f.db.trait_data(self.trait_).name)?;
402 if self.substs.len() > 1 { 399 if self.substs.len() > 1 {
403 write!(f, "<")?; 400 write!(f, "<")?;
404 f.write_joined(&self.substs[1..], ", ")?; 401 f.write_joined(&self.substs[1..], ", ")?;
diff --git a/docs/user/features.md b/docs/user/features.md
index 56d2969fd..8aeec2e81 100644
--- a/docs/user/features.md
+++ b/docs/user/features.md
@@ -81,6 +81,12 @@ Join selected lines into one, smartly fixing up whitespace and trailing commas.
81Shows the parse tree of the current file. It exists mostly for debugging 81Shows the parse tree of the current file. It exists mostly for debugging
82rust-analyzer itself. 82rust-analyzer itself.
83 83
84You can hover over syntax nodes in the opened text file to see the appropriate
85rust code that it refers to and the rust editor will also highlight the proper
86text range.
87
88<img src="https://user-images.githubusercontent.com/36276403/78043783-7425e180-737c-11ea-8653-b02b773c5aa1.png" alt="demo" height="200px" >
89
84#### Expand Macro Recursively 90#### Expand Macro Recursively
85 91
86Shows the full macro expansion of the macro at current cursor. 92Shows the full macro expansion of the macro at current cursor.
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts
index 2e08e8f11..996c7a716 100644
--- a/editors/code/src/commands/syntax_tree.ts
+++ b/editors/code/src/commands/syntax_tree.ts
@@ -1,8 +1,10 @@
1import * as vscode from 'vscode'; 1import * as vscode from 'vscode';
2import * as ra from '../rust-analyzer-api'; 2import * as ra from '../rust-analyzer-api';
3 3
4import { Ctx, Cmd } from '../ctx'; 4import { Ctx, Cmd, Disposable } from '../ctx';
5import { isRustDocument } from '../util'; 5import { isRustDocument, RustEditor, isRustEditor, sleep } from '../util';
6
7const AST_FILE_SCHEME = "rust-analyzer";
6 8
7// Opens the virtual file that will show the syntax tree 9// Opens the virtual file that will show the syntax tree
8// 10//
@@ -10,35 +12,13 @@ import { isRustDocument } from '../util';
10export function syntaxTree(ctx: Ctx): Cmd { 12export function syntaxTree(ctx: Ctx): Cmd {
11 const tdcp = new TextDocumentContentProvider(ctx); 13 const tdcp = new TextDocumentContentProvider(ctx);
12 14
13 ctx.pushCleanup( 15 void new AstInspector(ctx);
14 vscode.workspace.registerTextDocumentContentProvider( 16
15 'rust-analyzer', 17 ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp));
16 tdcp,
17 ),
18 );
19
20 vscode.workspace.onDidChangeTextDocument(
21 (event: vscode.TextDocumentChangeEvent) => {
22 const doc = event.document;
23 if (!isRustDocument(doc)) return;
24 afterLs(() => tdcp.eventEmitter.fire(tdcp.uri));
25 },
26 null,
27 ctx.subscriptions,
28 );
29
30 vscode.window.onDidChangeActiveTextEditor(
31 (editor: vscode.TextEditor | undefined) => {
32 if (!editor || !isRustDocument(editor.document)) return;
33 tdcp.eventEmitter.fire(tdcp.uri);
34 },
35 null,
36 ctx.subscriptions,
37 );
38 18
39 return async () => { 19 return async () => {
40 const editor = vscode.window.activeTextEditor; 20 const editor = vscode.window.activeTextEditor;
41 const rangeEnabled = !!(editor && !editor.selection.isEmpty); 21 const rangeEnabled = !!editor && !editor.selection.isEmpty;
42 22
43 const uri = rangeEnabled 23 const uri = rangeEnabled
44 ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`) 24 ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
@@ -48,45 +28,126 @@ export function syntaxTree(ctx: Ctx): Cmd {
48 28
49 tdcp.eventEmitter.fire(uri); 29 tdcp.eventEmitter.fire(uri);
50 30
51 return vscode.window.showTextDocument( 31 void await vscode.window.showTextDocument(document, {
52 document, 32 viewColumn: vscode.ViewColumn.Two,
53 vscode.ViewColumn.Two, 33 preserveFocus: true
54 true, 34 });
55 );
56 }; 35 };
57} 36}
58 37
59// We need to order this after LS updates, but there's no API for that.
60// Hence, good old setTimeout.
61function afterLs(f: () => void) {
62 setTimeout(f, 10);
63}
64
65
66class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { 38class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
67 uri = vscode.Uri.parse('rust-analyzer://syntaxtree'); 39 readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree');
68 eventEmitter = new vscode.EventEmitter<vscode.Uri>(); 40 readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
41
69 42
70 constructor(private readonly ctx: Ctx) { 43 constructor(private readonly ctx: Ctx) {
44 vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
45 vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
71 } 46 }
72 47
73 provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> { 48 private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
74 const editor = vscode.window.activeTextEditor; 49 if (isRustDocument(event.document)) {
75 const client = this.ctx.client; 50 // We need to order this after language server updates, but there's no API for that.
76 if (!editor || !client) return ''; 51 // Hence, good old sleep().
52 void sleep(10).then(() => this.eventEmitter.fire(this.uri));
53 }
54 }
55 private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
56 if (editor && isRustEditor(editor)) {
57 this.eventEmitter.fire(this.uri);
58 }
59 }
60
61 provideTextDocumentContent(uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
62 const rustEditor = this.ctx.activeRustEditor;
63 if (!rustEditor) return '';
77 64
78 // When the range based query is enabled we take the range of the selection 65 // When the range based query is enabled we take the range of the selection
79 const range = uri.query === 'range=true' && !editor.selection.isEmpty 66 const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
80 ? client.code2ProtocolConverter.asRange(editor.selection) 67 ? this.ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
81 : null; 68 : null;
82 69
83 return client.sendRequest(ra.syntaxTree, { 70 const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
84 textDocument: { uri: editor.document.uri.toString() }, 71 return this.ctx.client.sendRequest(ra.syntaxTree, params, ct);
85 range,
86 });
87 } 72 }
88 73
89 get onDidChange(): vscode.Event<vscode.Uri> { 74 get onDidChange(): vscode.Event<vscode.Uri> {
90 return this.eventEmitter.event; 75 return this.eventEmitter.event;
91 } 76 }
92} 77}
78
79
80// FIXME: consider implementing this via the Tree View API?
81// https://code.visualstudio.com/api/extension-guides/tree-view
82class AstInspector implements vscode.HoverProvider, Disposable {
83 private static readonly astDecorationType = vscode.window.createTextEditorDecorationType({
84 fontStyle: "normal",
85 border: "#ffffff 1px solid",
86 });
87 private rustEditor: undefined | RustEditor;
88
89 constructor(ctx: Ctx) {
90 ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this));
91 vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions);
92 vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions);
93
94 ctx.pushCleanup(this);
95 }
96 dispose() {
97 this.setRustEditor(undefined);
98 }
99
100 private onDidCloseTextDocument(doc: vscode.TextDocument) {
101 if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) {
102 this.setRustEditor(undefined);
103 }
104 }
105
106 private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) {
107 if (editors.every(suspect => suspect.document.uri.scheme !== AST_FILE_SCHEME)) {
108 this.setRustEditor(undefined);
109 return;
110 }
111 this.setRustEditor(editors.find(isRustEditor));
112 }
113
114 private setRustEditor(newRustEditor: undefined | RustEditor) {
115 if (newRustEditor !== this.rustEditor) {
116 this.rustEditor?.setDecorations(AstInspector.astDecorationType, []);
117 }
118 this.rustEditor = newRustEditor;
119 }
120
121 provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> {
122 if (!this.rustEditor) return;
123
124 const astTextLine = doc.lineAt(hoverPosition.line);
125
126 const rustTextRange = this.parseRustTextRange(this.rustEditor.document, astTextLine.text);
127 if (!rustTextRange) return;
128
129 this.rustEditor.setDecorations(AstInspector.astDecorationType, [rustTextRange]);
130 this.rustEditor.revealRange(rustTextRange);
131
132 const rustSourceCode = this.rustEditor.document.getText(rustTextRange);
133 const astTextRange = this.findAstRange(astTextLine);
134
135 return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astTextRange);
136 }
137
138 private findAstRange(astLine: vscode.TextLine) {
139 const lineOffset = astLine.range.start;
140 const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex);
141 const end = lineOffset.translate(undefined, astLine.text.trimEnd().length);
142 return new vscode.Range(begin, end);
143 }
144
145 private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range {
146 const parsedRange = /\[(\d+); (\d+)\)/.exec(astLine);
147 if (!parsedRange) return;
148
149 const [begin, end] = parsedRange.slice(1).map(off => doc.positionAt(+off));
150
151 return new vscode.Range(begin, end);
152 }
153}
diff --git a/editors/code/src/util.ts b/editors/code/src/util.ts
index 978a31751..6f91f81d6 100644
--- a/editors/code/src/util.ts
+++ b/editors/code/src/util.ts
@@ -65,12 +65,12 @@ export async function sendRequestWithRetry<TParam, TRet>(
65 throw 'unreachable'; 65 throw 'unreachable';
66} 66}
67 67
68function sleep(ms: number) { 68export function sleep(ms: number) {
69 return new Promise(resolve => setTimeout(resolve, ms)); 69 return new Promise(resolve => setTimeout(resolve, ms));
70} 70}
71 71
72export type RustDocument = vscode.TextDocument & { languageId: "rust" }; 72export type RustDocument = vscode.TextDocument & { languageId: "rust" };
73export type RustEditor = vscode.TextEditor & { document: RustDocument; id: string }; 73export type RustEditor = vscode.TextEditor & { document: RustDocument };
74 74
75export function isRustDocument(document: vscode.TextDocument): document is RustDocument { 75export function isRustDocument(document: vscode.TextDocument): document is RustDocument {
76 return document.languageId === 'rust' 76 return document.languageId === 'rust'