aboutsummaryrefslogtreecommitdiff
path: root/editors/code/src/commands/syntax_tree.ts
diff options
context:
space:
mode:
Diffstat (limited to 'editors/code/src/commands/syntax_tree.ts')
-rw-r--r--editors/code/src/commands/syntax_tree.ts111
1 files changed, 94 insertions, 17 deletions
diff --git a/editors/code/src/commands/syntax_tree.ts b/editors/code/src/commands/syntax_tree.ts
index 996c7a716..b7a397414 100644
--- a/editors/code/src/commands/syntax_tree.ts
+++ b/editors/code/src/commands/syntax_tree.ts
@@ -15,6 +15,9 @@ export function syntaxTree(ctx: Ctx): Cmd {
15 void new AstInspector(ctx); 15 void new AstInspector(ctx);
16 16
17 ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp)); 17 ctx.pushCleanup(vscode.workspace.registerTextDocumentContentProvider(AST_FILE_SCHEME, tdcp));
18 ctx.pushCleanup(vscode.languages.setLanguageConfiguration("ra_syntax_tree", {
19 brackets: [["[", ")"]],
20 }));
18 21
19 return async () => { 22 return async () => {
20 const editor = vscode.window.activeTextEditor; 23 const editor = vscode.window.activeTextEditor;
@@ -36,7 +39,7 @@ export function syntaxTree(ctx: Ctx): Cmd {
36} 39}
37 40
38class TextDocumentContentProvider implements vscode.TextDocumentContentProvider { 41class TextDocumentContentProvider implements vscode.TextDocumentContentProvider {
39 readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree'); 42 readonly uri = vscode.Uri.parse('rust-analyzer://syntaxtree/tree.rast');
40 readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>(); 43 readonly eventEmitter = new vscode.EventEmitter<vscode.Uri>();
41 44
42 45
@@ -79,16 +82,41 @@ class TextDocumentContentProvider implements vscode.TextDocumentContentProvider
79 82
80// FIXME: consider implementing this via the Tree View API? 83// FIXME: consider implementing this via the Tree View API?
81// https://code.visualstudio.com/api/extension-guides/tree-view 84// https://code.visualstudio.com/api/extension-guides/tree-view
82class AstInspector implements vscode.HoverProvider, Disposable { 85class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable {
83 private static readonly astDecorationType = vscode.window.createTextEditorDecorationType({ 86 private readonly astDecorationType = vscode.window.createTextEditorDecorationType({
84 fontStyle: "normal", 87 borderColor: new vscode.ThemeColor('rust_analyzer.syntaxTreeBorder'),
85 border: "#ffffff 1px solid", 88 borderStyle: "solid",
89 borderWidth: "2px",
90
86 }); 91 });
87 private rustEditor: undefined | RustEditor; 92 private rustEditor: undefined | RustEditor;
88 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
89 constructor(ctx: Ctx) { 115 constructor(ctx: Ctx) {
90 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));
91 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);
92 vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions); 120 vscode.window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, ctx.subscriptions);
93 121
94 ctx.pushCleanup(this); 122 ctx.pushCleanup(this);
@@ -97,6 +125,12 @@ class AstInspector implements vscode.HoverProvider, Disposable {
97 this.setRustEditor(undefined); 125 this.setRustEditor(undefined);
98 } 126 }
99 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
100 private onDidCloseTextDocument(doc: vscode.TextDocument) { 134 private onDidCloseTextDocument(doc: vscode.TextDocument) {
101 if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) { 135 if (this.rustEditor && doc.uri.toString() === this.rustEditor.document.uri.toString()) {
102 this.setRustEditor(undefined); 136 this.setRustEditor(undefined);
@@ -104,38 +138,67 @@ class AstInspector implements vscode.HoverProvider, Disposable {
104 } 138 }
105 139
106 private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) { 140 private onDidChangeVisibleTextEditors(editors: vscode.TextEditor[]) {
107 if (editors.every(suspect => suspect.document.uri.scheme !== AST_FILE_SCHEME)) { 141 if (!this.findAstTextEditor()) {
108 this.setRustEditor(undefined); 142 this.setRustEditor(undefined);
109 return; 143 return;
110 } 144 }
111 this.setRustEditor(editors.find(isRustEditor)); 145 this.setRustEditor(editors.find(isRustEditor));
112 } 146 }
113 147
148 private findAstTextEditor(): undefined | vscode.TextEditor {
149 return vscode.window.visibleTextEditors.find(it => it.document.uri.scheme === AST_FILE_SCHEME);
150 }
151
114 private setRustEditor(newRustEditor: undefined | RustEditor) { 152 private setRustEditor(newRustEditor: undefined | RustEditor) {
115 if (newRustEditor !== this.rustEditor) { 153 if (this.rustEditor && this.rustEditor !== newRustEditor) {
116 this.rustEditor?.setDecorations(AstInspector.astDecorationType, []); 154 this.rustEditor.setDecorations(this.astDecorationType, []);
155 this.rust2Ast.reset();
117 } 156 }
118 this.rustEditor = newRustEditor; 157 this.rustEditor = newRustEditor;
119 } 158 }
120 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
121 provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> { 184 provideHover(doc: vscode.TextDocument, hoverPosition: vscode.Position): vscode.ProviderResult<vscode.Hover> {
122 if (!this.rustEditor) return; 185 if (!this.rustEditor) return;
123 186
124 const astTextLine = doc.lineAt(hoverPosition.line); 187 const astFileLine = doc.lineAt(hoverPosition.line);
125 188
126 const rustTextRange = this.parseRustTextRange(this.rustEditor.document, astTextLine.text); 189 const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text);
127 if (!rustTextRange) return; 190 if (!rustFileRange) return;
128 191
129 this.rustEditor.setDecorations(AstInspector.astDecorationType, [rustTextRange]); 192 this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]);
130 this.rustEditor.revealRange(rustTextRange); 193 this.rustEditor.revealRange(rustFileRange);
131 194
132 const rustSourceCode = this.rustEditor.document.getText(rustTextRange); 195 const rustSourceCode = this.rustEditor.document.getText(rustFileRange);
133 const astTextRange = this.findAstRange(astTextLine); 196 const astFileRange = this.findAstNodeRange(astFileLine);
134 197
135 return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astTextRange); 198 return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange);
136 } 199 }
137 200
138 private findAstRange(astLine: vscode.TextLine) { 201 private findAstNodeRange(astLine: vscode.TextLine) {
139 const lineOffset = astLine.range.start; 202 const lineOffset = astLine.range.start;
140 const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex); 203 const begin = lineOffset.translate(undefined, astLine.firstNonWhitespaceCharacterIndex);
141 const end = lineOffset.translate(undefined, astLine.text.trimEnd().length); 204 const end = lineOffset.translate(undefined, astLine.text.trimEnd().length);
@@ -151,3 +214,17 @@ class AstInspector implements vscode.HoverProvider, Disposable {
151 return new vscode.Range(begin, end); 214 return new vscode.Range(begin, end);
152 } 215 }
153} 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}