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:
commit
f77fc158fc
@ -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..], ", ")?;
|
||||||
|
@ -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.
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -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'
|
||||||
|
Loading…
Reference in New Issue
Block a user