From d453281bb2b5608c5655f116b7722a2514063555 Mon Sep 17 00:00:00 2001 From: veetaha Date: Thu, 2 Apr 2020 03:24:30 +0300 Subject: [PATCH 1/5] vscode: add goto definition from rust file to syntax tree editor --- editors/code/src/commands/syntax_tree.ts | 88 ++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts index 8d71cb39e315..c1af7122fb2d 100644 --- a/editors/code/src/commands/syntax_tree.ts +++ b/editors/code/src/commands/syntax_tree.ts @@ -83,7 +83,7 @@ class TextDocumentContentProvider implements vscode.TextDocumentContentProvider // 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({ + private readonly astDecorationType = vscode.window.createTextEditorDecorationType({ borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'), borderStyle: "solid", borderWidth: "2px", @@ -91,9 +91,35 @@ class AstInspector implements vscode.HoverProvider, Disposable { }); private rustEditor: undefined | RustEditor; + // Lazy rust token range -> syntax tree file range. + private readonly rust2Ast = new Lazy(() => { + const astEditor = this.findAstTextEditor(); + if (!this.rustEditor || !astEditor) return undefined; + + console.time("Build goto def index"); + let buf: [vscode.Range, vscode.Range][] = []; + for (let i = 0; i < astEditor.document.lineCount; ++i) { + const astLine = astEditor.document.lineAt(i); + + // Heuristically look for nodes with quoted text (which are token nodes) + const isTokenNode = astLine.text.lastIndexOf('"') >= 0; + if (!isTokenNode) continue; + + const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text); + if (!rustRange) continue; + + buf.push([rustRange, this.findAstRange(astLine)]); + } + + console.timeEnd("Build goto def index"); + return buf; + }); + constructor(ctx: Ctx) { ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this)); + ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this)); vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions); + vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions); vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions); ctx.pushCleanup(this); @@ -102,6 +128,12 @@ class AstInspector implements vscode.HoverProvider, Disposable { this.setRustEditor(undefined); } + private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) { + if (this.rustEditor && event.document.uri.toString() === this.rustEditor.document.uri.toString()) { + this.rust2Ast.reset(); + } + } + private onDidCloseTextDocument(doc: vscode.TextDocument) { if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) { this.setRustEditor(undefined); @@ -109,20 +141,51 @@ class AstInspector implements vscode.HoverProvider, Disposable { } private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) { - if (editors.every(suspect => suspect.document.uri.scheme !== AST_FILE_SCHEME)) { + if (!this.findAstTextEditor()) { this.setRustEditor(undefined); return; } this.setRustEditor(editors.find(isRustEditor)); } + private findAstTextEditor(): undefined | vscode.TextEditor { + return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === AST_FILE_SCHEME); + } + private setRustEditor(newRustEditor: undefined | RustEditor) { - if (newRustEditor !== this.rustEditor) { - this.rustEditor?.setDecorations(AstInspector.astDecorationType, []); + if (this.rustEditor && this.rustEditor !== newRustEditor) { + this.rustEditor.setDecorations(this.astDecorationType, []); + this.rust2Ast.reset(); } this.rustEditor = newRustEditor; } + // additional positional params are omitted + provideDefinition(doc: vscode.TextDocument, pos: vscode.Position): vscode.ProviderResult { + if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) return; + + const astEditor = this.findAstTextEditor(); + if (!astEditor) return; + + console.time("Goto def"); + const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos)); + console.timeEnd("Goto def"); + if (!rust2AstRanges) return; + + const [rustFileRange, astFileRange] = rust2AstRanges; + + astEditor.revealRange(astFileRange); + astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end); + + return [{ + targetRange: astFileRange, + targetUri: astEditor.document.uri, + originSelectionRange: rustFileRange, + targetSelectionRange: astFileRange, + }]; + } + + // additional positional params are omitted provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult { if (!this.rustEditor) return; @@ -131,7 +194,7 @@ class AstInspector implements vscode.HoverProvider, Disposable { const rustTextRange = this.parseRustTextRange(this.rustEditor.document, astTextLine.text); if (!rustTextRange) return; - this.rustEditor.setDecorations(AstInspector.astDecorationType, [rustTextRange]); + this.rustEditor.setDecorations(this.astDecorationType, [rustTextRange]); this.rustEditor.revealRange(rustTextRange); const rustSourceCode = this.rustEditor.document.getText(rustTextRange); @@ -156,3 +219,18 @@ class AstInspector implements vscode.HoverProvider, Disposable { return new vscode.Range(begin, end); } } + +class Lazy { + val: undefined | T; + + constructor(private readonly compute: () => undefined | T) {} + + get() { + return this.val ?? (this.val = this.compute()); + } + + reset() { + this.val = undefined; + } + +} From e763b279a887cfc518e46b52af9c0ef0572068fc Mon Sep 17 00:00:00 2001 From: veetaha Date: Thu, 2 Apr 2020 03:24:45 +0300 Subject: [PATCH 2/5] vscode: postrefactor variable names --- editors/code/src/commands/syntax_tree.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts index c1af7122fb2d..7b58cf7889e0 100644 --- a/editors/code/src/commands/syntax_tree.ts +++ b/editors/code/src/commands/syntax_tree.ts @@ -189,18 +189,18 @@ class AstInspector implements vscode.HoverProvider, Disposable { provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult { if (!this.rustEditor) return; - const astTextLine = doc.lineAt(hoverPosition.line); + const astFileLine = doc.lineAt(hoverPosition.line); - const rustTextRange = this.parseRustTextRange(this.rustEditor.document, astTextLine.text); - if (!rustTextRange) return; + const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text); + if (!rustFileRange) return; - this.rustEditor.setDecorations(this.astDecorationType, [rustTextRange]); - this.rustEditor.revealRange(rustTextRange); + this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]); + this.rustEditor.revealRange(rustFileRange); - const rustSourceCode = this.rustEditor.document.getText(rustTextRange); - const astTextRange = this.findAstRange(astTextLine); + const rustSourceCode = this.rustEditor.document.getText(rustFileRange); + const astFileRange = this.findAstRange(astFileLine); - return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astTextRange); + return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange); } private findAstRange(astLine: vscode.TextLine) { From e4c69443e7fac2843262d6890557de1f9803e34a Mon Sep 17 00:00:00 2001 From: veetaha Date: Thu, 2 Apr 2020 03:26:37 +0300 Subject: [PATCH 3/5] vscode: add docs about goto-definition for rust syntax tree --- docs/user/features.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/user/features.md b/docs/user/features.md index 8aeec2e81efb..4cba66529449 100644 --- a/docs/user/features.md +++ b/docs/user/features.md @@ -85,6 +85,10 @@ 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. +If you press Ctrl (i.e. trigger goto definition) in the inspected +Rust source file the syntax tree readonly editor should scroll to and select the +appropriate syntax node token. + demo #### Expand Macro Recursively From 036a8aee2af960428218cecd68ea2ea1813ab7f6 Mon Sep 17 00:00:00 2001 From: veetaha Date: Thu, 2 Apr 2020 03:35:58 +0300 Subject: [PATCH 4/5] vscode: postrefactor --- editors/code/src/commands/syntax_tree.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts index 7b58cf7889e0..b7a397414ebd 100644 --- a/editors/code/src/commands/syntax_tree.ts +++ b/editors/code/src/commands/syntax_tree.ts @@ -82,7 +82,7 @@ class TextDocumentContentProvider implements vscode.TextDocumentContentProvider // FIXME: consider implementing this via the Tree View API? // https://code.visualstudio.com/api/extension-guides/tree-view -class AstInspector implements vscode.HoverProvider, Disposable { +class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable { private readonly astDecorationType = vscode.window.createTextEditorDecorationType({ borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'), borderStyle: "solid", @@ -96,8 +96,7 @@ class AstInspector implements vscode.HoverProvider, Disposable { const astEditor = this.findAstTextEditor(); if (!this.rustEditor || !astEditor) return undefined; - console.time("Build goto def index"); - let buf: [vscode.Range, vscode.Range][] = []; + const buf: [vscode.Range, vscode.Range][] = []; for (let i = 0; i < astEditor.document.lineCount; ++i) { const astLine = astEditor.document.lineAt(i); @@ -108,10 +107,8 @@ class AstInspector implements vscode.HoverProvider, Disposable { const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text); if (!rustRange) continue; - buf.push([rustRange, this.findAstRange(astLine)]); + buf.push([rustRange, this.findAstNodeRange(astLine)]); } - - console.timeEnd("Build goto def index"); return buf; }); @@ -167,9 +164,7 @@ class AstInspector implements vscode.HoverProvider, Disposable { const astEditor = this.findAstTextEditor(); if (!astEditor) return; - console.time("Goto def"); const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos)); - console.timeEnd("Goto def"); if (!rust2AstRanges) return; const [rustFileRange, astFileRange] = rust2AstRanges; @@ -198,12 +193,12 @@ class AstInspector implements vscode.HoverProvider, Disposable { this.rustEditor.revealRange(rustFileRange); const rustSourceCode = this.rustEditor.document.getText(rustFileRange); - const astFileRange = this.findAstRange(astFileLine); + const astFileRange = this.findAstNodeRange(astFileLine); return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange); } - private findAstRange(astLine: vscode.TextLine) { + private findAstNodeRange(astLine: vscode.TextLine) { const lineOffset = astLine.range.start; const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex); const end = lineOffset.translate(undefined, astLine.text.trimEnd().length); @@ -223,7 +218,7 @@ class AstInspector implements vscode.HoverProvider, Disposable { class Lazy { val: undefined | T; - constructor(private readonly compute: () => undefined | T) {} + constructor(private readonly compute: () => undefined | T) { } get() { return this.val ?? (this.val = this.compute()); @@ -232,5 +227,4 @@ class Lazy { reset() { this.val = undefined; } - } From dd5e4d4870b4e59bc82d285c481bb6971d016912 Mon Sep 17 00:00:00 2001 From: veetaha Date: Thu, 2 Apr 2020 11:23:56 +0300 Subject: [PATCH 5/5] vscode: move docks about syntax tree to dev/README.md --- docs/dev/README.md | 10 ++++++++++ docs/user/features.md | 10 ---------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/dev/README.md b/docs/dev/README.md index 8d7e18010927..f230dc1db1a4 100644 --- a/docs/dev/README.md +++ b/docs/dev/README.md @@ -155,6 +155,16 @@ There's also two VS Code commands which might be of interest: * `Rust Analyzer: Syntax Tree` shows syntax tree of the current file/selection. + 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. + + If you press Ctrl (i.e. trigger goto definition) in the inspected + Rust source file the syntax tree read-only editor should scroll to and select the + appropriate syntax node token. + + ![demo](https://user-images.githubusercontent.com/36276403/78225773-6636a480-74d3-11ea-9d9f-1c9d42da03b0.png) + # Profiling We have a built-in hierarchical profiler, you can enable it by using `RA_PROFILE` env-var: diff --git a/docs/user/features.md b/docs/user/features.md index 4cba66529449..56d2969fd43e 100644 --- a/docs/user/features.md +++ b/docs/user/features.md @@ -81,16 +81,6 @@ 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. - -If you press Ctrl (i.e. trigger goto definition) in the inspected -Rust source file the syntax tree readonly editor should scroll to and select the -appropriate syntax node token. - -demo - #### Expand Macro Recursively Shows the full macro expansion of the macro at current cursor.