Skip to content

Commit f77fc15

Browse files
bors[bot]Veetaha
andauthored
Merge #3780 #3787
3780: Simplify r=matklad a=Veetaha I absolutely love tha fact that removing `.clone()` simplifies the code comparing to other languages where it's actually the contrary (ahem ~~`std::move()`~~) 3787: vscode: add syntax tree inspection hovers and highlights r=matklad a=Veetaha ![inspect-tree](https://user-images.githubusercontent.com/36276403/78029767-c7426900-7369-11ea-9ed6-b8a0f8e05bac.gif) I implemented the reverse mapping (when you hover in the rust editor), but it seems overcomplicated, so I removed it Related #3682 Co-authored-by: veetaha <[email protected]> Co-authored-by: Veetaha <[email protected]>
3 parents f9f0a96 + 611adc8 + 90cc8cc commit f77fc15

File tree

4 files changed

+134
-70
lines changed

4 files changed

+134
-70
lines changed

crates/ra_hir_ty/src/display.rs

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,6 @@ impl HirDisplay for ApplicationTy {
190190
};
191191
write!(f, "{}", name)?;
192192
if self.parameters.len() > 0 {
193-
write!(f, "<")?;
194-
195193
let mut non_default_parameters = Vec::with_capacity(self.parameters.len());
196194
let parameters_to_write = if f.omit_verbose_types() {
197195
match self
@@ -200,8 +198,8 @@ impl HirDisplay for ApplicationTy {
200198
.map(|generic_def_id| f.db.generic_defaults(generic_def_id))
201199
.filter(|defaults| !defaults.is_empty())
202200
{
203-
Option::None => self.parameters.0.as_ref(),
204-
Option::Some(default_parameters) => {
201+
None => self.parameters.0.as_ref(),
202+
Some(default_parameters) => {
205203
for (i, parameter) in self.parameters.iter().enumerate() {
206204
match (parameter, default_parameters.get(i)) {
207205
(&Ty::Unknown, _) | (_, None) => {
@@ -221,7 +219,7 @@ impl HirDisplay for ApplicationTy {
221219
} else {
222220
self.parameters.0.as_ref()
223221
};
224-
222+
write!(f, "<")?;
225223
f.write_joined(parameters_to_write, ", ")?;
226224
write!(f, ">")?;
227225
}
@@ -231,9 +229,9 @@ impl HirDisplay for ApplicationTy {
231229
AssocContainerId::TraitId(it) => it,
232230
_ => panic!("not an associated type"),
233231
};
234-
let trait_name = f.db.trait_data(trait_).name.clone();
235-
let name = f.db.type_alias_data(type_alias).name.clone();
236-
write!(f, "{}::{}", trait_name, name)?;
232+
let trait_ = f.db.trait_data(trait_);
233+
let type_alias = f.db.type_alias_data(type_alias);
234+
write!(f, "{}::{}", trait_.name, type_alias.name)?;
237235
if self.parameters.len() > 0 {
238236
write!(f, "<")?;
239237
f.write_joined(&*self.parameters.0, ", ")?;
@@ -266,8 +264,8 @@ impl HirDisplay for ProjectionTy {
266264
return write!(f, "{}", TYPE_HINT_TRUNCATION);
267265
}
268266

269-
let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone();
270-
write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?;
267+
let trait_ = f.db.trait_data(self.trait_(f.db));
268+
write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_.name)?;
271269
if self.parameters.len() > 1 {
272270
write!(f, "<")?;
273271
f.write_joined(&self.parameters[1..], ", ")?;
@@ -312,7 +310,7 @@ impl HirDisplay for Ty {
312310
Ty::Opaque(_) => write!(f, "impl ")?,
313311
_ => unreachable!(),
314312
};
315-
write_bounds_like_dyn_trait(&predicates, f)?;
313+
write_bounds_like_dyn_trait(predicates, f)?;
316314
}
317315
Ty::Unknown => write!(f, "{{unknown}}")?,
318316
Ty::Infer(..) => write!(f, "_")?,
@@ -345,7 +343,7 @@ fn write_bounds_like_dyn_trait(
345343
// We assume that the self type is $0 (i.e. the
346344
// existential) here, which is the only thing that's
347345
// 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)?;
349347
if trait_ref.substs.len() > 1 {
350348
write!(f, "<")?;
351349
f.write_joined(&trait_ref.substs[1..], ", ")?;
@@ -362,9 +360,8 @@ fn write_bounds_like_dyn_trait(
362360
write!(f, "<")?;
363361
angle_open = true;
364362
}
365-
let name =
366-
f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name.clone();
367-
write!(f, "{} = ", name)?;
363+
let type_alias = f.db.type_alias_data(projection_pred.projection_ty.associated_ty);
364+
write!(f, "{} = ", type_alias.name)?;
368365
projection_pred.ty.hir_fmt(f)?;
369366
}
370367
GenericPredicate::Error => {
@@ -398,7 +395,7 @@ impl TraitRef {
398395
} else {
399396
write!(f, ": ")?;
400397
}
401-
write!(f, "{}", f.db.trait_data(self.trait_).name.clone())?;
398+
write!(f, "{}", f.db.trait_data(self.trait_).name)?;
402399
if self.substs.len() > 1 {
403400
write!(f, "<")?;
404401
f.write_joined(&self.substs[1..], ", ")?;

docs/user/features.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,12 @@ Join selected lines into one, smartly fixing up whitespace and trailing commas.
8181
Shows the parse tree of the current file. It exists mostly for debugging
8282
rust-analyzer itself.
8383

84+
You can hover over syntax nodes in the opened text file to see the appropriate
85+
rust code that it refers to and the rust editor will also highlight the proper
86+
text range.
87+
88+
<img src="https://user-images.githubusercontent.com/36276403/78043783-7425e180-737c-11ea-8653-b02b773c5aa1.png" alt="demo" height="200px" >
89+
8490
#### Expand Macro Recursively
8591

8692
Shows the full macro expansion of the macro at current cursor.
Lines changed: 113 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,24 @@
11
import * as vscode from 'vscode';
22
import * as ra from '../rust-analyzer-api';
33

4-
import { Ctx, Cmd } from '../ctx';
5-
import { isRustDocument } from '../util';
4+
import { Ctx, Cmd, Disposable } from '../ctx';
5+
import { isRustDocument, RustEditor, isRustEditor, sleep } from '../util';
6+
7+
const AST_FILE_SCHEME = "rust-analyzer";
68

79
// Opens the virtual file that will show the syntax tree
810
//
911
// The contents of the file come from the `TextDocumentContentProvider`
1012
export function syntaxTree(ctx: Ctx): Cmd {
1113
const tdcp = new TextDocumentContentProvider(ctx);
1214

13-
ctx.pushCleanup(
14-
vscode.workspace.registerTextDocumentContentProvider(
15-
'rust-analyzer',
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-
);
15+
void new AstInspector(ctx);
16+
17+
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp));
3818

3919
return async () => {
4020
const editor = vscode.window.activeTextEditor;
41-
const rangeEnabled = !!(editor && !editor.selection.isEmpty);
21+
const rangeEnabled = !!editor && !editor.selection.isEmpty;
4222

4323
const uri = rangeEnabled
4424
? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
@@ -48,45 +28,126 @@ export function syntaxTree(ctx: Ctx): Cmd {
4828

4929
tdcp.eventEmitter.fire(uri);
5030

51-
return vscode.window.showTextDocument(
52-
document,
53-
vscode.ViewColumn.Two,
54-
true,
55-
);
31+
void await vscode.window.showTextDocument(document, {
32+
viewColumn: vscode.ViewColumn.Two,
33+
preserveFocus: true
34+
});
5635
};
5736
}
5837

59-
// We need to order this after LS updates, but there's no API for that.
60-
// Hence, good old setTimeout.
61-
function afterLs(f: () => void) {
62-
setTimeout(f, 10);
63-
}
64-
65-
6638
class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
67-
uri = vscode.Uri.parse('rust-analyzer://syntaxtree');
68-
eventEmitter = new vscode.EventEmitter<vscode.Uri>();
39+
readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree');
40+
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
41+
6942

7043
constructor(private readonly ctx: Ctx) {
44+
vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
45+
vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
7146
}
7247

73-
provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> {
74-
const editor = vscode.window.activeTextEditor;
75-
const client = this.ctx.client;
76-
if (!editor || !client) return '';
48+
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
49+
if (isRustDocument(event.document)) {
50+
// We need to order this after language server updates, but there's no API for that.
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 '';
7764

7865
// When the range based query is enabled we take the range of the selection
79-
const range = uri.query === 'range=true' && !editor.selection.isEmpty
80-
? client.code2ProtocolConverter.asRange(editor.selection)
66+
const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
67+
? this.ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
8168
: null;
8269

83-
return client.sendRequest(ra.syntaxTree, {
84-
textDocument: { uri: editor.document.uri.toString() },
85-
range,
86-
});
70+
const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
71+
return this.ctx.client.sendRequest(ra.syntaxTree, params, ct);
8772
}
8873

8974
get onDidChange(): vscode.Event<vscode.Uri> {
9075
return this.eventEmitter.event;
9176
}
9277
}
78+
79+
80+
// FIXME: consider implementing this via the Tree View API?
81+
// https://code.visualstudio.com/api/extension-guides/tree-view
82+
class 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+
}

editors/code/src/util.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,12 +65,12 @@ export async function sendRequestWithRetry<TParam, TRet>(
6565
throw 'unreachable';
6666
}
6767

68-
function sleep(ms: number) {
68+
export function sleep(ms: number) {
6969
return new Promise(resolve => setTimeout(resolve, ms));
7070
}
7171

7272
export type RustDocument = vscode.TextDocument & { languageId: "rust" };
73-
export type RustEditor = vscode.TextEditor & { document: RustDocument; id: string };
73+
export type RustEditor = vscode.TextEditor & { document: RustDocument };
7474

7575
export function isRustDocument(document: vscode.TextDocument): document is RustDocument {
7676
return document.languageId === 'rust'

0 commit comments

Comments
 (0)