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)?;
if self.parameters.len() > 0 {
write!(f, "<")?;
let mut non_default_parameters = Vec::with_capacity(self.parameters.len());
let parameters_to_write = if f.omit_verbose_types() {
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))
.filter(|defaults| !defaults.is_empty())
{
Option::None => self.parameters.0.as_ref(),
Option::Some(default_parameters) => {
None => self.parameters.0.as_ref(),
Some(default_parameters) => {
for (i, parameter) in self.parameters.iter().enumerate() {
match (parameter, default_parameters.get(i)) {
(&Ty::Unknown, _) | (_, None) => {
@ -221,7 +219,7 @@ fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result {
} else {
self.parameters.0.as_ref()
};
write!(f, "<")?;
f.write_joined(parameters_to_write, ", ")?;
write!(f, ">")?;
}
@ -231,9 +229,9 @@ fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result {
AssocContainerId::TraitId(it) => it,
_ => panic!("not an associated type"),
};
let trait_name = f.db.trait_data(trait_).name.clone();
let name = f.db.type_alias_data(type_alias).name.clone();
write!(f, "{}::{}", trait_name, name)?;
let trait_ = f.db.trait_data(trait_);
let type_alias = f.db.type_alias_data(type_alias);
write!(f, "{}::{}", trait_.name, type_alias.name)?;
if self.parameters.len() > 0 {
write!(f, "<")?;
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);
}
let trait_name = f.db.trait_data(self.trait_(f.db)).name.clone();
write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_name,)?;
let trait_ = f.db.trait_data(self.trait_(f.db));
write!(f, "<{} as {}", self.parameters[0].display(f.db), trait_.name)?;
if self.parameters.len() > 1 {
write!(f, "<")?;
f.write_joined(&self.parameters[1..], ", ")?;
@ -312,7 +310,7 @@ fn hir_fmt(&self, f: &mut HirFormatter) -> fmt::Result {
Ty::Opaque(_) => write!(f, "impl ")?,
_ => unreachable!(),
};
write_bounds_like_dyn_trait(&predicates, f)?;
write_bounds_like_dyn_trait(predicates, f)?;
}
Ty::Unknown => write!(f, "{{unknown}}")?,
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
// existential) here, which is the only thing that's
// 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 {
write!(f, "<")?;
f.write_joined(&trait_ref.substs[1..], ", ")?;
@ -362,9 +360,8 @@ fn write_bounds_like_dyn_trait(
write!(f, "<")?;
angle_open = true;
}
let name =
f.db.type_alias_data(projection_pred.projection_ty.associated_ty).name.clone();
write!(f, "{} = ", name)?;
let type_alias = f.db.type_alias_data(projection_pred.projection_ty.associated_ty);
write!(f, "{} = ", type_alias.name)?;
projection_pred.ty.hir_fmt(f)?;
}
GenericPredicate::Error => {
@ -398,7 +395,7 @@ fn hir_fmt_ext(&self, f: &mut HirFormatter, use_as: bool) -> fmt::Result {
} else {
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 {
write!(f, "<")?;
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
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
Shows the full macro expansion of the macro at current cursor.

View File

@ -1,8 +1,10 @@
import * as vscode from 'vscode';
import * as ra from '../rust-analyzer-api';
import { Ctx, Cmd } from '../ctx';
import { isRustDocument } from '../util';
import { Ctx, Cmd, Disposable } from '../ctx';
import { isRustDocument, RustEditor, isRustEditor, sleep } from '../util';
const AST_FILE_SCHEME = "rust-analyzer";
// Opens the virtual file that will show the syntax tree
//
@ -10,35 +12,13 @@ import { isRustDocument } from '../util';
export function syntaxTree(ctx: Ctx): Cmd {
const tdcp = new TextDocumentContentProvider(ctx);
ctx.pushCleanup(
vscode.workspace.registerTextDocumentContentProvider(
'rust-analyzer',
tdcp,
),
);
void new AstInspector(ctx);
vscode.workspace.onDidChangeTextDocument(
(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,
);
ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp));
return async () => {
const editor = vscode.window.activeTextEditor;
const rangeEnabled = !!(editor && !editor.selection.isEmpty);
const rangeEnabled = !!editor && !editor.selection.isEmpty;
const uri = rangeEnabled
? vscode.Uri.parse(`${tdcp.uri.toString()}?range=true`)
@ -48,45 +28,126 @@ export function syntaxTree(ctx: Ctx): Cmd {
tdcp.eventEmitter.fire(uri);
return vscode.window.showTextDocument(
document,
vscode.ViewColumn.Two,
true,
);
void await vscode.window.showTextDocument(document, {
viewColumn: vscode.ViewColumn.Two,
preserveFocus: 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 {
uri = vscode.Uri.parse('rust-analyzer://syntaxtree');
eventEmitter = new vscode.EventEmitter<vscode.Uri>();
readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree');
readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
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> {
const editor = vscode.window.activeTextEditor;
const client = this.ctx.client;
if (!editor || !client) return '';
private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) {
if (isRustDocument(event.document)) {
// We need to order this after language server updates, but there's no API for that.
// 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
const range = uri.query === 'range=true' && !editor.selection.isEmpty
? client.code2ProtocolConverter.asRange(editor.selection)
const range = uri.query === 'range=true' && !rustEditor.selection.isEmpty
? this.ctx.client.code2ProtocolConverter.asRange(rustEditor.selection)
: null;
return client.sendRequest(ra.syntaxTree, {
textDocument: { uri: editor.document.uri.toString() },
range,
});
const params = { textDocument: { uri: rustEditor.document.uri.toString() }, range, };
return this.ctx.client.sendRequest(ra.syntaxTree, params, ct);
}
get onDidChange(): vscode.Event<vscode.Uri> {
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';
}
function sleep(ms: number) {
export function sleep(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
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 {
return document.languageId === 'rust'