Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'editors/code/src/syntax_tree_provider.ts')
| -rw-r--r-- | editors/code/src/syntax_tree_provider.ts | 301 |
1 files changed, 301 insertions, 0 deletions
diff --git a/editors/code/src/syntax_tree_provider.ts b/editors/code/src/syntax_tree_provider.ts new file mode 100644 index 0000000000..c7e8007e83 --- /dev/null +++ b/editors/code/src/syntax_tree_provider.ts @@ -0,0 +1,301 @@ +import * as vscode from "vscode"; + +import { isRustEditor, setContextValue } from "./util"; +import type { CtxInit } from "./ctx"; +import * as ra from "./lsp_ext"; + +export class SyntaxTreeProvider implements vscode.TreeDataProvider<SyntaxElement> { + private _onDidChangeTreeData: vscode.EventEmitter<SyntaxElement | undefined | void> = + new vscode.EventEmitter<SyntaxElement | undefined | void>(); + readonly onDidChangeTreeData: vscode.Event<SyntaxElement | undefined | void> = + this._onDidChangeTreeData.event; + ctx: CtxInit; + root: SyntaxNode | undefined; + hideWhitespace: boolean = false; + + constructor(ctx: CtxInit) { + this.ctx = ctx; + } + + getTreeItem(element: SyntaxElement): vscode.TreeItem { + return new SyntaxTreeItem(element); + } + + getChildren(element?: SyntaxElement): vscode.ProviderResult<SyntaxElement[]> { + return this.getRawChildren(element); + } + + getParent(element: SyntaxElement): vscode.ProviderResult<SyntaxElement> { + return element.parent; + } + + resolveTreeItem( + item: SyntaxTreeItem, + element: SyntaxElement, + _token: vscode.CancellationToken, + ): vscode.ProviderResult<SyntaxTreeItem> { + const editor = vscode.window.activeTextEditor; + + if (editor !== undefined) { + const start = editor.document.positionAt(element.start); + const end = editor.document.positionAt(element.end); + const range = new vscode.Range(start, end); + + const text = editor.document.getText(range); + item.tooltip = new vscode.MarkdownString().appendCodeblock(text, "rust"); + } + + return item; + } + + private getRawChildren(element?: SyntaxElement): SyntaxElement[] { + if (element?.type === "Node") { + if (this.hideWhitespace) { + return element.children.filter((e) => e.kind !== "WHITESPACE"); + } + + return element.children; + } + + if (element?.type === "Token") { + return []; + } + + if (element === undefined && this.root !== undefined) { + return [this.root]; + } + + return []; + } + + async refresh(): Promise<void> { + const editor = vscode.window.activeTextEditor; + + if (editor && isRustEditor(editor)) { + const params = { textDocument: { uri: editor.document.uri.toString() }, range: null }; + const fileText = await this.ctx.client.sendRequest(ra.viewSyntaxTree, params); + this.root = JSON.parse(fileText, (_key, value: SyntaxElement) => { + if (value.type === "Node") { + for (const child of value.children) { + child.parent = value; + } + } + + return value; + }); + } else { + this.root = undefined; + } + + this._onDidChangeTreeData.fire(); + } + + getElementByRange(start: number, end: number): SyntaxElement | undefined { + if (this.root === undefined) { + return undefined; + } + + let result: SyntaxElement = this.root; + + if (this.root.start === start && this.root.end === end) { + return result; + } + + let children = this.getRawChildren(this.root); + + outer: while (true) { + for (const child of children) { + if (child.start <= start && child.end >= end) { + result = child; + if (start === end && start === child.end) { + // When the cursor is on the very end of a token, + // we assume the user wants the next token instead. + continue; + } + + if (child.type === "Token") { + return result; + } else { + children = this.getRawChildren(child); + continue outer; + } + } + } + + return result; + } + } + + async toggleWhitespace() { + this.hideWhitespace = !this.hideWhitespace; + this._onDidChangeTreeData.fire(); + await setContextValue("rustSyntaxTree.hideWhitespace", this.hideWhitespace); + } +} + +export type SyntaxNode = { + type: "Node"; + kind: string; + start: number; + end: number; + istart?: number; + iend?: number; + children: SyntaxElement[]; + parent?: SyntaxElement; +}; + +type SyntaxToken = { + type: "Token"; + kind: string; + start: number; + end: number; + istart?: number; + iend?: number; + parent?: SyntaxElement; +}; + +export type SyntaxElement = SyntaxNode | SyntaxToken; + +export class SyntaxTreeItem extends vscode.TreeItem { + constructor(private readonly element: SyntaxElement) { + super(element.kind); + const icon = getIcon(element.kind); + if (element.type === "Node") { + this.contextValue = "syntaxNode"; + this.iconPath = icon ?? new vscode.ThemeIcon("list-tree"); + this.collapsibleState = vscode.TreeItemCollapsibleState.Expanded; + } else { + this.contextValue = "syntaxToken"; + this.iconPath = icon ?? new vscode.ThemeIcon("symbol-string"); + this.collapsibleState = vscode.TreeItemCollapsibleState.None; + } + + if (element.istart !== undefined && element.iend !== undefined) { + this.description = `${this.element.istart}..${this.element.iend}`; + } else { + this.description = `${this.element.start}..${this.element.end}`; + } + } +} + +function getIcon(kind: string): vscode.ThemeIcon | undefined { + const icon = iconTable[kind]; + + if (icon !== undefined) { + return icon; + } + + if (kind.endsWith("_KW")) { + return new vscode.ThemeIcon( + "symbol-keyword", + new vscode.ThemeColor("symbolIcon.keywordForeground"), + ); + } + + if (operators.includes(kind)) { + return new vscode.ThemeIcon( + "symbol-operator", + new vscode.ThemeColor("symbolIcon.operatorForeground"), + ); + } + + return undefined; +} + +const iconTable: Record<string, vscode.ThemeIcon> = { + CALL_EXPR: new vscode.ThemeIcon("call-outgoing"), + COMMENT: new vscode.ThemeIcon("comment"), + ENUM: new vscode.ThemeIcon("symbol-enum", new vscode.ThemeColor("symbolIcon.enumForeground")), + FN: new vscode.ThemeIcon( + "symbol-function", + new vscode.ThemeColor("symbolIcon.functionForeground"), + ), + FLOAT_NUMBER: new vscode.ThemeIcon( + "symbol-number", + new vscode.ThemeColor("symbolIcon.numberForeground"), + ), + INDEX_EXPR: new vscode.ThemeIcon( + "symbol-array", + new vscode.ThemeColor("symbolIcon.arrayForeground"), + ), + INT_NUMBER: new vscode.ThemeIcon( + "symbol-number", + new vscode.ThemeColor("symbolIcon.numberForeground"), + ), + LITERAL: new vscode.ThemeIcon( + "symbol-misc", + new vscode.ThemeColor("symbolIcon.miscForeground"), + ), + MODULE: new vscode.ThemeIcon( + "symbol-module", + new vscode.ThemeColor("symbolIcon.moduleForeground"), + ), + METHOD_CALL_EXPR: new vscode.ThemeIcon("call-outgoing"), + PARAM: new vscode.ThemeIcon( + "symbol-parameter", + new vscode.ThemeColor("symbolIcon.parameterForeground"), + ), + RECORD_FIELD: new vscode.ThemeIcon( + "symbol-field", + new vscode.ThemeColor("symbolIcon.fieldForeground"), + ), + SOURCE_FILE: new vscode.ThemeIcon("file-code"), + STRING: new vscode.ThemeIcon("quote"), + STRUCT: new vscode.ThemeIcon( + "symbol-struct", + new vscode.ThemeColor("symbolIcon.structForeground"), + ), + TRAIT: new vscode.ThemeIcon( + "symbol-interface", + new vscode.ThemeColor("symbolIcon.interfaceForeground"), + ), + TYPE_PARAM: new vscode.ThemeIcon( + "symbol-type-parameter", + new vscode.ThemeColor("symbolIcon.typeParameterForeground"), + ), + VARIANT: new vscode.ThemeIcon( + "symbol-enum-member", + new vscode.ThemeColor("symbolIcon.enumMemberForeground"), + ), + WHITESPACE: new vscode.ThemeIcon("whitespace"), +}; + +const operators = [ + "PLUS", + "PLUSEQ", + "MINUS", + "MINUSEQ", + "STAR", + "STAREQ", + "SLASH", + "SLASHEQ", + "PERCENT", + "PERCENTEQ", + "CARET", + "CARETEQ", + "AMP", + "AMPEQ", + "AMP2", + "PIPE", + "PIPEEQ", + "PIPE2", + "SHL", + "SHLEQ", + "SHR", + "SHREQ", + "EQ", + "EQ2", + "BANG", + "NEQ", + "L_ANGLE", + "LTEQ", + "R_ANGLE", + "GTEQ", + "COLON2", + "THIN_ARROW", + "FAT_ARROW", + "DOT", + "DOT2", + "DOT2EQ", + "AT", +]; |