Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'editors/code/src/ast_inspector.ts')
| -rw-r--r-- | editors/code/src/ast_inspector.ts | 216 |
1 files changed, 0 insertions, 216 deletions
diff --git a/editors/code/src/ast_inspector.ts b/editors/code/src/ast_inspector.ts deleted file mode 100644 index 35b705c477..0000000000 --- a/editors/code/src/ast_inspector.ts +++ /dev/null @@ -1,216 +0,0 @@ -import * as vscode from "vscode"; - -import type { Ctx, Disposable } from "./ctx"; -import { type RustEditor, isRustEditor, unwrapUndefinable } from "./util"; - -// FIXME: consider implementing this via the Tree View API? -// https://code.visualstudio.com/api/extension-guides/tree-view -export class AstInspector implements vscode.HoverProvider, vscode.DefinitionProvider, Disposable { - private readonly astDecorationType = vscode.window.createTextEditorDecorationType({ - borderColor: new vscode.ThemeColor("rust_analyzer.syntaxTreeBorder"), - borderStyle: "solid", - borderWidth: "2px", - }); - 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; - - const 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.findAstNodeRange(astLine)]); - } - return buf; - }); - - constructor(ctx: Ctx) { - ctx.pushExtCleanup( - vscode.languages.registerHoverProvider({ scheme: "rust-analyzer" }, this), - ); - ctx.pushExtCleanup(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, - ); - } - dispose() { - 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); - } - } - - private onDidChangeVisibleTextEditors(editors: readonly vscode.TextEditor[]) { - 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 === "rust-analyzer", - ); - } - - private setRustEditor(newRustEditor: undefined | RustEditor) { - 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<vscode.DefinitionLink[]> { - if (!this.rustEditor || doc.uri.toString() !== this.rustEditor.document.uri.toString()) { - return; - } - - const astEditor = this.findAstTextEditor(); - if (!astEditor) return; - - const rust2AstRanges = this.rust2Ast - .get() - ?.find(([rustRange, _]) => rustRange.contains(pos)); - 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<vscode.Hover> { - if (!this.rustEditor) return; - - const astFileLine = doc.lineAt(hoverPosition.line); - - const rustFileRange = this.parseRustTextRange(this.rustEditor.document, astFileLine.text); - if (!rustFileRange) return; - - this.rustEditor.setDecorations(this.astDecorationType, [rustFileRange]); - this.rustEditor.revealRange(rustFileRange); - - const rustSourceCode = this.rustEditor.document.getText(rustFileRange); - const astFileRange = this.findAstNodeRange(astFileLine); - - return new vscode.Hover(["```rust\n" + rustSourceCode + "\n```"], astFileRange); - } - - private findAstNodeRange(astLine: vscode.TextLine): vscode.Range { - 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) => this.positionAt(doc, +off)); - const actualBegin = unwrapUndefinable(begin); - const actualEnd = unwrapUndefinable(end); - return new vscode.Range(actualBegin, actualEnd); - } - - // Memoize the last value, otherwise the CPU is at 100% single core - // with quadratic lookups when we build rust2Ast cache - cache?: { doc: vscode.TextDocument; offset: number; line: number }; - - positionAt(doc: vscode.TextDocument, targetOffset: number): vscode.Position { - if (doc.eol === vscode.EndOfLine.LF) { - return doc.positionAt(targetOffset); - } - - // Dirty workaround for crlf line endings - // We are still in this prehistoric era of carriage returns here... - - let line = 0; - let offset = 0; - - const cache = this.cache; - if (cache?.doc === doc && cache.offset <= targetOffset) { - ({ line, offset } = cache); - } - - while (true) { - const lineLenWithLf = doc.lineAt(line).text.length + 1; - if (offset + lineLenWithLf > targetOffset) { - this.cache = { doc, offset, line }; - return doc.positionAt(targetOffset + line); - } - offset += lineLenWithLf; - line += 1; - } - } -} - -class Lazy<T> { - val: undefined | T; - - constructor(private readonly compute: () => undefined | T) {} - - get() { - return this.val ?? (this.val = this.compute()); - } - - reset() { - this.val = undefined; - } -} |