aboutsummaryrefslogtreecommitdiff
path: root/editors
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-04-02 09:34:03 +0100
committerGitHub <[email protected]>2020-04-02 09:34:03 +0100
commit9ee96dcf4a2b47a6df0e3ea379d36aec2e6e1784 (patch)
tree672f5ebda7447c06ff28556515e41b13cef261e1 /editors
parent98f7842e408587fdaca3d15d8eda677b689a035a (diff)
parentdd5e4d4870b4e59bc82d285c481bb6971d016912 (diff)
Merge #3816
3816: vscode: add goto ast node definition from rust source code r=Veetaha a=Veetaha By holding the `Ctrl` key you can now goto-definition of the appropriate syntax token in the syntax tree read-only editor. But actually going to the definition is not very convenient, since it opens the new editor, you'd rather just hold the `Ctrl` and look at the syntax tree because it is automatically scrolled to the proper node and the node itself is enclosed with text selection. Unfortunately, the algorithm is very simple (because we don't do any elaborate parsing of the syntax tree text received from the server), but it is enough to debug not very large source files. I tested the performance and in a bad case (rust source file with 5K lines of code) it takes `1.3` seconds to build the `rust -> ast` mapping index (lazily once on the first goto definition request) and each lookup in this worst-case is approx `20-120` ms. I think this is good enough. In the simple case where the file is < 100 lines of code, it is instant. One peculiarity that I've noticed is that vscode doesn't trigger the goto-definition provider when the user triggers it on some punctuation characters (i.e. it doesn't underline them and invoke te goto-definition provider), but if you explicitly click `Ctrl+LMB` it will only then invoke the provider and navigate to the definition in a new editor. I think this is fine ;D ![rust2ast](https://user-images.githubusercontent.com/36276403/78198718-24d1d500-7492-11ea-91f6-2687cedf26ee.gif) Related: #3682 Co-authored-by: veetaha <[email protected]>
Diffstat (limited to 'editors')
-rw-r--r--editors/code/src/commands/syntax_tree.ts100
1 files changed, 86 insertions, 14 deletions
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
85class AstInspector implements vscode.HoverProvider, Disposable { 85class 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
218class 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}