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 <veetaha2@gmail.com>
Co-authored-by: Veetaha <veetaha2@gmail.com>
This commit is contained in:
bors[bot] 2020-03-31 17:40:03 +00:00 committed by GitHub
commit f77fc158fc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 133 additions and 69 deletions

View File

@ -190,8 +190,6 @@ fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result {
}; };
write!(f, "{}", name)?; write!(f, "{}", name)?;
if self.parameters.len() > 0 { if self.parameters.len() > 0 {
write!(f, "<")?;
let mut non_default_parameters = Vec::with_capacity(self.parameters.len()); let mut non_default_parameters = Vec::with_capacity(self.parameters.len());
let parameters_to_write = if f.omit_verbose_types() { let parameters_to_write = if f.omit_verbose_types() {
match self match self
@ -200,8 +198,8 @@ fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result {
.map(|generic_def_id| f.db.generic_defaults(generic_def_id)) .map(|generic_def_id| f.db.generic_defaults(generic_def_id))
.filter(|defaults| !defaults.is_empty()) .filter(|defaults| !defaults.is_empty())
{ {
Option::None => self.parameters.0.as_ref(), None => self.parameters.0.as_ref(),
Option::Some(default_parameters) => { Some(default_parameters) => {
for (i, parameter) in self.parameters.iter().enumerate() { for (i, parameter) in self.parameters.iter().enumerate() {
match (parameter, default_parameters.get(i)) { match (parameter, default_parameters.get(i)) {
(&Ty::Unknown, _) | (_, None) => { (&Ty::Unknown, _) | (_, None) => {
@ -221,7 +219,7 @@ fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result {
} else { } else {
self.parameters.0.as_ref() self.parameters.0.as_ref()
}; };
write!(f, "<")?;
f.write_joined(parameters_to_write, ", ")?; f.write_joined(parameters_to_write, ", ")?;
write!(f, ">")?; write!(f, ">")?;
} }
@ -231,9 +229,9 @@ fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result {
AssocContainerId::TraitId(it) => it, AssocContainerId::TraitId(it) => it,
_ => panic!("not an associated type"), _ => panic!("not an associated type"),
}; };
let trait_name = f.db.trait_data(trait_).name.clone(); let trait_ = f.db.trait_data(trait_);
let name = f.db.type_alias_data(type_alias).name.clone(); let type_alias = f.db.type_alias_data(type_alias);
write!(f, "{}::{}", trait_name, name)?; write!(f, "{}::{}", trait_.name, type_alias.name)?;
if self.parameters.len() > 0 { if self.parameters.len() > 0 {
write!(f, "<")?; write!(f, "<")?;
f.write_joined(&*self.parameters.0, ", ")?; f.write_joined(&*self.parameters.0, ", ")?;
@ -266,8 +264,8 @@ fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result {
return write!(f, "{}", TYPE_HINT_TRUNCATION); return write!(f, "{}", TYPE_HINT_TRUNCATION);
} }
let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone(); let trait_ = f.db.trait_data(self.trait_(f.db));
write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?; write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_.name)?;
if self.parameters.len() > 1 { if self.parameters.len() > 1 {
write!(f, "<")?; write!(f, "<")?;
f.write_joined(&self.parameters[1..], ", ")?; f.write_joined(&self.parameters[1..], ", ")?;
@ -312,7 +310,7 @@ fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result {
Ty::Opaque(_) => write!(f, "impl ")?, Ty::Opaque(_) => write!(f, "impl ")?,
_ => unreachable!(), _ => unreachable!(),
}; };
write_bounds_like_dyn_trait(&predicates, f)?; write_bounds_like_dyn_trait(predicates, f)?;
} }
Ty::Unknown => write!(f, "{{unknown}}")?, Ty::Unknown => write!(f, "{{unknown}}")?,
Ty::Infer(..) => write!(f, "_")?, Ty::Infer(..) => write!(f, "_")?,
@ -345,7 +343,7 @@ fn write_bounds_like_dyn_trait(
// We assume that the self type is $0 (i.e. the // We assume that the self type is $0 (i.e. the
// existential) here, which is the only thing that's // existential) here, which is the only thing that's
// possible in actual Rust, and hence don't print it // possible in actual Rust, and hence don't print it
write!(f, "{}", f.db.trait_data(trait_ref.trait_).name.clone())?; write!(f, "{}", f.db.trait_data(trait_ref.trait_).name)?;
if trait_ref.substs.len() > 1 { if trait_ref.substs.len() > 1 {
write!(f, "<")?; write!(f, "<")?;
f.write_joined(&trait_ref.substs[1..], ", ")?; f.write_joined(&trait_ref.substs[1..], ", ")?;
@ -362,9 +360,8 @@ fn write_bounds_like_dyn_trait(
write!(f, "<")?; write!(f, "<")?;
angle_open = true; angle_open = true;
} }
let name = let type_alias = f.db.type_alias_data(projection_pred.projection_ty.associated_ty);
f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name.clone(); write!(f, "{} = ", type_alias.name)?;
write!(f, "{} = ", name)?;
projection_pred.ty.hir_fmt(f)?; projection_pred.ty.hir_fmt(f)?;
} }
GenericPredicate::Error => { GenericPredicate::Error => {
@ -398,7 +395,7 @@ fn hir_fmt_ext(&self, f: &mut HirFormatter, use_as: bool) -> fmt::Result {
} else { } else {
write!(f, ": ")?; write!(f, ": ")?;
} }
write!(f, "{}", f.db.trait_data(self.trait_).name.clone())?; write!(f, "{}", f.db.trait_data(self.trait_).name)?;
if self.substs.len() > 1 { if self.substs.len() > 1 {
write!(f, "<")?; write!(f, "<")?;
f.write_joined(&self.substs[1..], ", ")?; f.write_joined(&self.substs[1..], ", ")?;

View File

@ -81,6 +81,12 @@ Join selected lines into one, smartly fixing up whitespace and trailing commas.
Shows the parse tree of the current file. It exists mostly for debugging Shows the parse tree of the current file. It exists mostly for debugging
rust-analyzer itself. rust-analyzer itself.
You can hover over syntax nodes in the opened text file to see the appropriate
rust code that it refers to and the rust editor will also highlight the proper
text range.
<img src="https://user-images.githubusercontent.com/36276403/78043783-7425e180-737c-11ea-8653-b02b773c5aa1.png" alt="demo" height="200px" >
#### Expand Macro Recursively #### Expand Macro Recursively
Shows the full macro expansion of the macro at current cursor. Shows the full macro expansion of the macro at current cursor.

View File

@ -1,8 +1,10 @@
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as ra from '../rust-analyzer-api'; import * as ra from '../rust-analyzer-api';
import { Ctx, Cmd } from '../ctx'; import { Ctx, Cmd, Disposable } from '../ctx';
import { isRustDocument } from '../util'; import { isRustDocument, RustEditor, isRustEditor, sleep } from '../util';
const AST_FILE_SCHEME = "rust-analyzer";
// Opens the virtual file that will show the syntax tree // Opens the virtual file that will show the syntax tree
// //
@ -10,35 +12,13 @@ import { isRustDocument } from '../util';
export function syntaxTree(ctx: Ctx): Cmd { export function syntaxTree(ctx: Ctx): Cmd {
const tdcp = new TextDocumentContentProvider(ctx); const tdcp = new TextDocumentContentProvider(ctx);
ctx.pushCleanup( void new AstInspector(ctx);
vscode.workspace.registerTextDocumentContentProvider(
'rust-analyzer',
tdcp,
),
);
vscode.workspace.onDidChangeTextDocument( ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp));
(event: vscode.TextDocumentChangeEvent) => {
const doc = event.document;
if (!isRustDocument(doc)) return;
afterLs(() => tdcp.eventEmitter.fire(tdcp.uri));
},
null,
ctx.subscriptions,
);
vscode.window.onDidChangeActiveTextEditor(
(editor: vscode.TextEditor | undefined) => {
if (!editor || !isRustDocument(editor.document)) return;
tdcp.eventEmitter.fire(tdcp.uri);
},
null,
ctx.subscriptions,
);
return async () => { return async () => {
const editor = vscode.window.activeTextEditor; const editor = vscode.window.activeTextEditor;
const rangeEnabled = !!(editor && !editor.selection.isEmpty); const rangeEnabled = !!editor && !editor.selection.isEmpty;
const uri = rangeEnabled const uri = rangeEnabled
? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`) ? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
@ -48,45 +28,126 @@ export function syntaxTree(ctx: Ctx): Cmd {
tdcp.eventEmitter.fire(uri); tdcp.eventEmitter.fire(uri);
return vscode.window.showTextDocument( void await vscode.window.showTextDocument(document, {
document, viewColumn: vscode.ViewColumn.Two,
vscode.ViewColumn.Two, preserveFocus: true
true, });
);
}; };
} }
// We need to order this after LS updates, but there's no API for that.
// Hence, good old setTimeout.
function afterLs(f: () => void) {
setTimeout(f, 10);
}
class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
uri = vscode.Uri.parse('rust-analyzer://syntaxtree'); readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree');
eventEmitter = new vscode.EventEmitter<vscode.Uri>(); readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
constructor(private readonly ctx: Ctx) { constructor(private readonly ctx: Ctx) {
vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions);
vscode.window.onDidChangeActiveTextEditor(this.onDidChangeActiveTextEditor, this, ctx.subscriptions);
} }
provideTextDocumentContent(uri: vscode.Uri): vscode.ProviderResult<string> { private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
const editor = vscode.window.activeTextEditor; if (isRustDocument(event.document)) {
const client = this.ctx.client; // We need to order this after language server updates, but there's no API for that.
if (!editor || !client) return ''; // Hence, good old sleep().
void sleep(10).then(() => this.eventEmitter.fire(this.uri));
}
}
private onDidChangeActiveTextEditor(editor: vscode.TextEditor | undefined) {
if (editor && isRustEditor(editor)) {
this.eventEmitter.fire(this.uri);
}
}
provideTextDocumentContent(uri: vscode.Uri, ct: vscode.CancellationToken): vscode.ProviderResult<string> {
const rustEditor = this.ctx.activeRustEditor;
if (!rustEditor) return '';
// When the range based query is enabled we take the range of the selection // When the range based query is enabled we take the range of the selection
const range = uri.query === 'range=true' && !editor.selection.isEmpty const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
? client.code2ProtocolConverter.asRange(editor.selection) ? this.ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
: null; : null;
return client.sendRequest(ra.syntaxTree, { const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
textDocument: { uri: editor.document.uri.toString() }, return this.ctx.client.sendRequest(ra.syntaxTree, params, ct);
range,
});
} }
get onDidChange(): vscode.Event<vscode.Uri> { get onDidChange(): vscode.Event<vscode.Uri> {
return this.eventEmitter.event; return this.eventEmitter.event;
} }
} }
// FIXME: consider implementing this via the Tree View API?
// https://code.visualstudio.com/api/extension-guides/tree-view
class AstInspector implements vscode.HoverProvider, Disposable {
private static readonly astDecorationType = vscode.window.createTextEditorDecorationType({
fontStyle: "normal",
border: "#ffffff 1px solid",
});
private rustEditor: undefined | RustEditor;
constructor(ctx: Ctx) {
ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this));
vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions);
vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions);
ctx.pushCleanup(this);
}
dispose() {
this.setRustEditor(undefined);
}
private onDidCloseTextDocument(doc: vscode.TextDocument) {
if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) {
this.setRustEditor(undefined);
}
}
private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) {
if (editors.every(suspect => suspect.document.uri.scheme !== AST_FILE_SCHEME)) {
this.setRustEditor(undefined);
return;
}
this.setRustEditor(editors.find(isRustEditor));
}
private setRustEditor(newRustEditor: undefined | RustEditor) {
if (newRustEditor !== this.rustEditor) {
this.rustEditor?.setDecorations(AstInspector.astDecorationType, []);
}
this.rustEditor = newRustEditor;
}
provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> {
if (!this.rustEditor) return;
const astTextLine = doc.lineAt(hoverPosition.line);
const rustTextRange = this.parseRustTextRange(this.rustEditor.document, astTextLine.text);
if (!rustTextRange) return;
this.rustEditor.setDecorations(AstInspector.astDecorationType, [rustTextRange]);
this.rustEditor.revealRange(rustTextRange);
const rustSourceCode = this.rustEditor.document.getText(rustTextRange);
const astTextRange = this.findAstRange(astTextLine);
return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astTextRange);
}
private findAstRange(astLine: vscode.TextLine) {
const lineOffset = astLine.range.start;
const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex);
const end = lineOffset.translate(undefined, astLine.text.trimEnd().length);
return new vscode.Range(begin, end);
}
private parseRustTextRange(doc: vscode.TextDocument, astLine: string): undefined | vscode.Range {
const parsedRange = /\[(\d+); (\d+)\)/.exec(astLine);
if (!parsedRange) return;
const [begin, end] = parsedRange.slice(1).map(off => doc.positionAt(+off));
return new vscode.Range(begin, end);
}
}

View File

@ -65,12 +65,12 @@ export async function sendRequestWithRetry<TParam, TRet>(
throw 'unreachable'; throw 'unreachable';
} }
function sleep(ms: number) { export function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms)); return new Promise(resolve => setTimeout(resolve, ms));
} }
export type RustDocument = vscode.TextDocument & { languageId: "rust" }; export type RustDocument = vscode.TextDocument & { languageId: "rust" };
export type RustEditor = vscode.TextEditor & { document: RustDocument; id: string }; export type RustEditor = vscode.TextEditor & { document: RustDocument };
export function isRustDocument(document: vscode.TextDocument): document is RustDocument { export function isRustDocument(document: vscode.TextDocument): document is RustDocument {
return document.languageId === 'rust' return document.languageId === 'rust'