diff options
-rw-r--r-- | docs/dev/README.md | 10 | ||||
-rw-r--r-- | docs/user/features.md | 6 | ||||
-rw-r--r-- | editors/code/src/commands/syntax_tree.ts | 100 |
3 files changed, 96 insertions, 20 deletions
diff --git a/docs/dev/README.md b/docs/dev/README.md index 8d7e18010..f230dc1db 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: | |||
155 | 155 | ||
156 | * `Rust Analyzer: Syntax Tree` shows syntax tree of the current file/selection. | 156 | * `Rust Analyzer: Syntax Tree` shows syntax tree of the current file/selection. |
157 | 157 | ||
158 | You can hover over syntax nodes in the opened text file to see the appropriate | ||
159 | rust code that it refers to and the rust editor will also highlight the proper | ||
160 | text range. | ||
161 | |||
162 | If you press <kbd>Ctrl</kbd> (i.e. trigger goto definition) in the inspected | ||
163 | Rust source file the syntax tree read-only editor should scroll to and select the | ||
164 | appropriate syntax node token. | ||
165 | |||
166 | ![demo](https://user-images.githubusercontent.com/36276403/78225773-6636a480-74d3-11ea-9d9f-1c9d42da03b0.png) | ||
167 | |||
158 | # Profiling | 168 | # Profiling |
159 | 169 | ||
160 | We have a built-in hierarchical profiler, you can enable it by using `RA_PROFILE` env-var: | 170 | 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 8aeec2e81..56d2969fd 100644 --- a/docs/user/features.md +++ b/docs/user/features.md | |||
@@ -81,12 +81,6 @@ Join selected lines into one, smartly fixing up whitespace and trailing commas. | |||
81 | Shows the parse tree of the current file. It exists mostly for debugging | 81 | Shows the parse tree of the current file. It exists mostly for debugging |
82 | rust-analyzer itself. | 82 | rust-analyzer itself. |
83 | 83 | ||
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 | |||
90 | #### Expand Macro Recursively | 84 | #### Expand Macro Recursively |
91 | 85 | ||
92 | Shows the full macro expansion of the macro at current cursor. | 86 | Shows the full macro expansion of the macro at current cursor. |
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts index 8d71cb39e..b7a397414 100644 --- a/editors/code/src/commands/syntax_tree.ts +++ b/editors/code/src/commands/syntax_tree.ts | |||
@@ -82,8 +82,8 @@ class TextDocumentContentProvider implements vscode.TextDocumentContentProvider | |||
82 | 82 | ||
83 | // FIXME: consider implementing this via the Tree View API? | 83 | // FIXME: consider implementing this via the Tree View API? |
84 | // https://code.visualstudio.com/api/extension-guides/tree-view | 84 | // https://code.visualstudio.com/api/extension-guides/tree-view |
85 | class AstInspector implements vscode.HoverProvider, Disposable { | 85 | class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable { |
86 | private static readonly astDecorationType = vscode.window.createTextEditorDecorationType({ | 86 | private readonly astDecorationType = vscode.window.createTextEditorDecorationType({ |
87 | borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'), | 87 | borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'), |
88 | borderStyle: "solid", | 88 | borderStyle: "solid", |
89 | borderWidth: "2px", | 89 | borderWidth: "2px", |
@@ -91,9 +91,32 @@ class AstInspector implements vscode.HoverProvider, Disposable { | |||
91 | }); | 91 | }); |
92 | private rustEditor: undefined | RustEditor; | 92 | private rustEditor: undefined | RustEditor; |
93 | 93 | ||
94 | // Lazy rust token range -> syntax tree file range. | ||
95 | private readonly rust2Ast = new Lazy(() => { | ||
96 | const astEditor = this.findAstTextEditor(); | ||
97 | if (!this.rustEditor || !astEditor) return undefined; | ||
98 | |||
99 | const buf: [vscode.Range, vscode.Range][] = []; | ||
100 | for (let i = 0; i < astEditor.document.lineCount; ++i) { | ||
101 | const astLine = astEditor.document.lineAt(i); | ||
102 | |||
103 | // Heuristically look for nodes with quoted text (which are token nodes) | ||
104 | const isTokenNode = astLine.text.lastIndexOf('"') >= 0; | ||
105 | if (!isTokenNode) continue; | ||
106 | |||
107 | const rustRange = this.parseRustTextRange(this.rustEditor.document, astLine.text); | ||
108 | if (!rustRange) continue; | ||
109 | |||
110 | buf.push([rustRange, this.findAstNodeRange(astLine)]); | ||
111 | } | ||
112 | return buf; | ||
113 | }); | ||
114 | |||
94 | constructor(ctx: Ctx) { | 115 | constructor(ctx: Ctx) { |
95 | ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this)); | 116 | ctx.pushCleanup(vscode.languages.registerHoverProvider({ scheme: AST_FILE_SCHEME }, this)); |
117 | ctx.pushCleanup(vscode.languages.registerDefinitionProvider({ language: "rust" }, this)); | ||
96 | vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions); | 118 | vscode.workspace.onDidCloseTextDocument(this.onDidCloseTextDocument, this, ctx.subscriptions); |
119 | vscode.workspace.onDidChangeTextDocument(this.onDidChangeTextDocument, this, ctx.subscriptions); | ||
97 | vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions); | 120 | vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions); |
98 | 121 | ||
99 | ctx.pushCleanup(this); | 122 | ctx.pushCleanup(this); |
@@ -102,6 +125,12 @@ class AstInspector implements vscode.HoverProvider, Disposable { | |||
102 | this.setRustEditor(undefined); | 125 | this.setRustEditor(undefined); |
103 | } | 126 | } |
104 | 127 | ||
128 | private onDidChangeTextDocument(event: vscode.TextDocumentChangeEvent) { | ||
129 | if (this.rustEditor && event.document.uri.toString() === this.rustEditor.document.uri.toString()) { | ||
130 | this.rust2Ast.reset(); | ||
131 | } | ||
132 | } | ||
133 | |||
105 | private onDidCloseTextDocument(doc: vscode.TextDocument) { | 134 | private onDidCloseTextDocument(doc: vscode.TextDocument) { |
106 | if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) { | 135 | if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) { |
107 | this.setRustEditor(undefined); | 136 | this.setRustEditor(undefined); |
@@ -109,38 +138,67 @@ class AstInspector implements vscode.HoverProvider, Disposable { | |||
109 | } | 138 | } |
110 | 139 | ||
111 | private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) { | 140 | private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) { |
112 | if (editors.every(suspect => suspect.document.uri.scheme !== AST_FILE_SCHEME)) { | 141 | if (!this.findAstTextEditor()) { |
113 | this.setRustEditor(undefined); | 142 | this.setRustEditor(undefined); |
114 | return; | 143 | return; |
115 | } | 144 | } |
116 | this.setRustEditor(editors.find(isRustEditor)); | 145 | this.setRustEditor(editors.find(isRustEditor)); |
117 | } | 146 | } |
118 | 147 | ||
148 | private findAstTextEditor(): undefined | vscode.TextEditor { | ||
149 | return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === AST_FILE_SCHEME); | ||
150 | } | ||
151 | |||
119 | private setRustEditor(newRustEditor: undefined | RustEditor) { | 152 | private setRustEditor(newRustEditor: undefined | RustEditor) { |
120 | if (newRustEditor !== this.rustEditor) { | 153 | if (this.rustEditor && this.rustEditor !== newRustEditor) { |
121 | this.rustEditor?.setDecorations(AstInspector.astDecorationType, []); | 154 | this.rustEditor.setDecorations(this.astDecorationType, []); |
155 | this.rust2Ast.reset(); | ||
122 | } | 156 | } |
123 | this.rustEditor = newRustEditor; | 157 | this.rustEditor = newRustEditor; |
124 | } | 158 | } |
125 | 159 | ||
160 | // additional positional params are omitted | ||
161 | provideDefinition(doc: vscode.TextDocument, pos: vscode.Position): vscode.ProviderResult<vscode.DefinitionLink[]> { | ||
162 | if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) return; | ||
163 | |||
164 | const astEditor = this.findAstTextEditor(); | ||
165 | if (!astEditor) return; | ||
166 | |||
167 | const rust2AstRanges = this.rust2Ast.get()?.find(([rustRange, _]) => rustRange.contains(pos)); | ||
168 | if (!rust2AstRanges) return; | ||
169 | |||
170 | const [rustFileRange, astFileRange] = rust2AstRanges; | ||
171 | |||
172 | astEditor.revealRange(astFileRange); | ||
173 | astEditor.selection = new vscode.Selection(astFileRange.start, astFileRange.end); | ||
174 | |||
175 | return [{ | ||
176 | targetRange: astFileRange, | ||
177 | targetUri: astEditor.document.uri, | ||
178 | originSelectionRange: rustFileRange, | ||
179 | targetSelectionRange: astFileRange, | ||
180 | }]; | ||
181 | } | ||
182 | |||
183 | // additional positional params are omitted | ||
126 | provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> { | 184 | provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> { |
127 | if (!this.rustEditor) return; | 185 | if (!this.rustEditor) return; |
128 | 186 | ||
129 | const astTextLine = doc.lineAt(hoverPosition.line); | 187 | const astFileLine = doc.lineAt(hoverPosition.line); |
130 | 188 | ||
131 | const rustTextRange = this.parseRustTextRange(this.rustEditor.document, astTextLine.text); | 189 | const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text); |
132 | if (!rustTextRange) return; | 190 | if (!rustFileRange) return; |
133 | 191 | ||
134 | this.rustEditor.setDecorations(AstInspector.astDecorationType, [rustTextRange]); | 192 | this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]); |
135 | this.rustEditor.revealRange(rustTextRange); | 193 | this.rustEditor.revealRange(rustFileRange); |
136 | 194 | ||
137 | const rustSourceCode = this.rustEditor.document.getText(rustTextRange); | 195 | const rustSourceCode = this.rustEditor.document.getText(rustFileRange); |
138 | const astTextRange = this.findAstRange(astTextLine); | 196 | const astFileRange = this.findAstNodeRange(astFileLine); |
139 | 197 | ||
140 | return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astTextRange); | 198 | return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange); |
141 | } | 199 | } |
142 | 200 | ||
143 | private findAstRange(astLine: vscode.TextLine) { | 201 | private findAstNodeRange(astLine: vscode.TextLine) { |
144 | const lineOffset = astLine.range.start; | 202 | const lineOffset = astLine.range.start; |
145 | const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex); | 203 | const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex); |
146 | const end = lineOffset.translate(undefined, astLine.text.trimEnd().length); | 204 | const end = lineOffset.translate(undefined, astLine.text.trimEnd().length); |
@@ -156,3 +214,17 @@ class AstInspector implements vscode.HoverProvider, Disposable { | |||
156 | return new vscode.Range(begin, end); | 214 | return new vscode.Range(begin, end); |
157 | } | 215 | } |
158 | } | 216 | } |
217 | |||
218 | class Lazy<T> { | ||
219 | val: undefined | T; | ||
220 | |||
221 | constructor(private readonly compute: () => undefined | T) { } | ||
222 | |||
223 | get() { | ||
224 | return this.val ?? (this.val = this.compute()); | ||
225 | } | ||
226 | |||
227 | reset() { | ||
228 | this.val = undefined; | ||
229 | } | ||
230 | } | ||