From 785a03c95924027d078260a6170b640b7d385d0b Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 12 Mar 2026 15:51:27 +0100 Subject: [PATCH] Clean up Tree sitter code and move things out of the extensions file --- packages/common/src/types/TreeSitter.ts | 19 +-- .../disabledComponents/DisabledTreeSitter.ts | 10 +- .../src/testUtil/TestTreeSitter.ts | 9 +- .../src/createScopeVisualizer.ts | 64 ++++++++ .../cursorless-vscode/src/createTreeSitter.ts | 10 ++ .../cursorless-vscode/src/createVscodeIde.ts | 44 ++++++ packages/cursorless-vscode/src/extension.ts | 141 +----------------- packages/vscode-common/src/getExtensionApi.ts | 5 +- 8 files changed, 140 insertions(+), 162 deletions(-) create mode 100644 packages/cursorless-vscode/src/createScopeVisualizer.ts create mode 100644 packages/cursorless-vscode/src/createTreeSitter.ts create mode 100644 packages/cursorless-vscode/src/createVscodeIde.ts diff --git a/packages/common/src/types/TreeSitter.ts b/packages/common/src/types/TreeSitter.ts index 5127df6a9e..16005745aa 100644 --- a/packages/common/src/types/TreeSitter.ts +++ b/packages/common/src/types/TreeSitter.ts @@ -1,17 +1,7 @@ -import type { Range, TextDocument } from "@cursorless/common"; -import type { Node, Query, Tree } from "web-tree-sitter"; +import type { TextDocument } from "@cursorless/common"; +import type { Query, Tree } from "web-tree-sitter"; export interface TreeSitter { - /** - * Function to access nodes in the tree sitter. - */ - getNodeAtLocation(document: TextDocument, range: Range): Node; - - /** - * Function to access the tree sitter tree. - */ - getTree(document: TextDocument): Tree; - /** * Loads a language, returning true if it was successfully loaded * @@ -20,6 +10,11 @@ export interface TreeSitter { */ loadLanguage(languageId: string): Promise; + /** + * Function to access the tree sitter tree. + */ + getTree(document: TextDocument): Tree; + /** * Create a query if the language is loaded. * diff --git a/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts b/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts index 41263578b5..afc2f24221 100644 --- a/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts +++ b/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts @@ -1,16 +1,12 @@ -import type { Range, TextDocument, TreeSitter } from "@cursorless/common"; -import type { Node, Query, Tree } from "web-tree-sitter"; +import type { TextDocument, TreeSitter } from "@cursorless/common"; +import type { Query, Tree } from "web-tree-sitter"; export class DisabledTreeSitter implements TreeSitter { - getTree(_document: TextDocument): Tree { - throw new Error("Tree sitter not provided"); - } - loadLanguage(_languageId: string): Promise { return Promise.resolve(false); } - getNodeAtLocation(_document: TextDocument, _range: Range): Node { + getTree(_document: TextDocument): Tree { throw new Error("Tree sitter not provided"); } diff --git a/packages/cursorless-engine/src/testUtil/TestTreeSitter.ts b/packages/cursorless-engine/src/testUtil/TestTreeSitter.ts index 661e22f8bd..bc810ecb55 100644 --- a/packages/cursorless-engine/src/testUtil/TestTreeSitter.ts +++ b/packages/cursorless-engine/src/testUtil/TestTreeSitter.ts @@ -1,11 +1,10 @@ -import type { Range, TextDocument, TreeSitter } from "@cursorless/common"; +import type { TextDocument, TreeSitter } from "@cursorless/common"; import { createRequire } from "node:module"; import * as path from "node:path"; import type { - Node, Tree, - Parser as TreeSitterParser, Language as TreeSitterLanguage, + Parser as TreeSitterParser, Query as TreeSitterQuery, } from "web-tree-sitter"; @@ -38,10 +37,6 @@ function initTreeSitter() { } export class TestTreeSitter implements TreeSitter { - getNodeAtLocation(_document: TextDocument, _range: Range): Node { - throw new Error("getNodeAtLocation: not implemented."); - } - getTree(document: TextDocument): Tree { const language = languageCache.get(document.languageId); diff --git a/packages/cursorless-vscode/src/createScopeVisualizer.ts b/packages/cursorless-vscode/src/createScopeVisualizer.ts new file mode 100644 index 0000000000..9a2b9ebf7b --- /dev/null +++ b/packages/cursorless-vscode/src/createScopeVisualizer.ts @@ -0,0 +1,64 @@ +import type { + Disposable, + IDE, + ScopeProvider, + ScopeType, +} from "@cursorless/common"; +import { pull } from "lodash-es"; +import { + type VscodeScopeVisualizer, + createVscodeScopeVisualizer, +} from "./ide/vscode/VSCodeScopeVisualizer"; +import type { + ScopeVisualizer, + ScopeVisualizerListener, + VisualizationType, +} from "./ScopeVisualizerCommandApi"; + +export function createScopeVisualizer( + ide: IDE, + scopeProvider: ScopeProvider, +): ScopeVisualizer { + let scopeVisualizer: VscodeScopeVisualizer | undefined; + let currentScopeType: ScopeType | undefined; + + const listeners: ScopeVisualizerListener[] = []; + + return { + start(scopeType: ScopeType, visualizationType: VisualizationType) { + scopeVisualizer?.dispose(); + scopeVisualizer = createVscodeScopeVisualizer( + ide, + scopeProvider, + scopeType, + visualizationType, + ); + scopeVisualizer.start(); + currentScopeType = scopeType; + listeners + .slice() + .forEach((listener) => listener(scopeType, visualizationType)); + }, + + stop() { + scopeVisualizer?.dispose(); + scopeVisualizer = undefined; + currentScopeType = undefined; + listeners.slice().forEach((listener) => listener(undefined, undefined)); + }, + + get scopeType() { + return currentScopeType; + }, + + onDidChangeScopeType(listener: ScopeVisualizerListener): Disposable { + listeners.push(listener); + + return { + dispose() { + pull(listeners, listener); + }, + }; + }, + }; +} diff --git a/packages/cursorless-vscode/src/createTreeSitter.ts b/packages/cursorless-vscode/src/createTreeSitter.ts new file mode 100644 index 0000000000..115167f918 --- /dev/null +++ b/packages/cursorless-vscode/src/createTreeSitter.ts @@ -0,0 +1,10 @@ +import type { TreeSitter } from "@cursorless/common"; +import type { ParseTreeApi } from "@cursorless/vscode-common"; + +export function createTreeSitter(parseTreeApi: ParseTreeApi): TreeSitter { + return { + loadLanguage: parseTreeApi.loadLanguage, + createQuery: parseTreeApi.createQuery, + getTree: (document) => parseTreeApi.getTreeForUri(document.uri), + }; +} diff --git a/packages/cursorless-vscode/src/createVscodeIde.ts b/packages/cursorless-vscode/src/createVscodeIde.ts new file mode 100644 index 0000000000..5870f53235 --- /dev/null +++ b/packages/cursorless-vscode/src/createVscodeIde.ts @@ -0,0 +1,44 @@ +import * as crypto from "crypto"; +import * as os from "node:os"; +import * as path from "node:path"; +import type { ExtensionContext } from "vscode"; +import { FakeFontMeasurements } from "./ide/vscode/hats/FakeFontMeasurements"; +import { FontMeasurementsImpl } from "./ide/vscode/hats/FontMeasurementsImpl"; +import { VscodeHats } from "./ide/vscode/hats/VscodeHats"; +import { VscodeFileSystem } from "./ide/vscode/VscodeFileSystem"; +import { VscodeIDE } from "./ide/vscode/VscodeIDE"; +import { vscodeApi } from "./vscodeApi"; + +export async function createVscodeIde(context: ExtensionContext) { + const vscodeIDE = new VscodeIDE(context); + + const hats = new VscodeHats( + vscodeIDE, + vscodeApi, + context, + vscodeIDE.runMode === "test" + ? new FakeFontMeasurements() + : new FontMeasurementsImpl(context), + ); + + await hats.init(); + + // FIXME: Inject this from test harness. Would need to arrange to delay + // extension initialization, probably by returning a function from extension + // init that has parameters consisting of test configuration, and have that + // function do the actual initialization. + const cursorlessDir = + vscodeIDE.runMode === "test" + ? path.join(os.tmpdir(), crypto.randomBytes(16).toString("hex")) + : path.join(os.homedir(), ".cursorless"); + + const fileSystem = new VscodeFileSystem( + context, + vscodeIDE.runMode, + cursorlessDir, + ); + + await fileSystem.initialize(); + + return { vscodeIDE, hats, fileSystem }; +} diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index 08ceac262b..a84b257e4d 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -1,13 +1,4 @@ -import type { - Disposable, - EnforceUndefined, - IDE, - Range, - ScopeProvider, - ScopeType, - TextDocument, - TreeSitter, -} from "@cursorless/common"; +import type { EnforceUndefined } from "@cursorless/common"; import { FakeCommandServerApi, FakeIDE, @@ -27,36 +18,22 @@ import { ScopeTestRecorder, TestCaseRecorder, } from "@cursorless/test-case-recorder"; -import type { CursorlessApi, ParseTreeApi } from "@cursorless/vscode-common"; +import type { CursorlessApi } from "@cursorless/vscode-common"; import { getCommandServerApi, getParseTreeApi, - toVscodeRange, } from "@cursorless/vscode-common"; -import * as crypto from "crypto"; -import { pull } from "lodash-es"; -import * as os from "node:os"; -import * as path from "node:path"; -import * as vscode from "vscode"; +import type { ExtensionContext } from "vscode"; import { InstallationDependencies } from "./InstallationDependencies"; import { ReleaseNotes } from "./ReleaseNotes"; import { ScopeTreeProvider } from "./ScopeTreeProvider"; -import type { - ScopeVisualizer, - ScopeVisualizerListener, - VisualizationType, -} from "./ScopeVisualizerCommandApi"; import { StatusBarItem } from "./StatusBarItem"; import { VscodeSnippets } from "./VscodeSnippets"; import { constructTestHelpers } from "./constructTestHelpers"; +import { createScopeVisualizer } from "./createScopeVisualizer"; +import { createTreeSitter } from "./createTreeSitter"; import { createTutorial } from "./createTutorial"; -import type { VscodeScopeVisualizer } from "./ide/vscode/VSCodeScopeVisualizer"; -import { createVscodeScopeVisualizer } from "./ide/vscode/VSCodeScopeVisualizer"; -import { VscodeFileSystem } from "./ide/vscode/VscodeFileSystem"; -import { VscodeIDE } from "./ide/vscode/VscodeIDE"; -import { FakeFontMeasurements } from "./ide/vscode/hats/FakeFontMeasurements"; -import { FontMeasurementsImpl } from "./ide/vscode/hats/FontMeasurementsImpl"; -import { VscodeHats } from "./ide/vscode/hats/VscodeHats"; +import { createVscodeIde } from "./createVscodeIde"; import { KeyboardCommands } from "./keyboard/KeyboardCommands"; import { registerCommands } from "./registerCommands"; import { revisualizeOnCustomRegexChange } from "./revisualizeOnCustomRegexChange"; @@ -72,9 +49,10 @@ import { vscodeApi } from "./vscodeApi"; * - Creates an entrypoint for running commands {@link CommandRunner}. */ export async function activate( - context: vscode.ExtensionContext, + context: ExtensionContext, ): Promise { const parseTreeApi = await getParseTreeApi(); + const treeSitter = createTreeSitter(parseTreeApi); const { vscodeIDE, hats, fileSystem } = await createVscodeIde(context); const isTesting = vscodeIDE.runMode === "test"; @@ -89,7 +67,6 @@ export async function activate( ? fakeCommandServerApi : await getCommandServerApi(); - const treeSitter = createTreeSitter(parseTreeApi); const talonSpokenForms = new FileSystemTalonSpokenForms(fileSystem); const snippets = new VscodeSnippets(normalizedIde); @@ -212,105 +189,3 @@ export async function activate( : undefined, }; } - -async function createVscodeIde(context: vscode.ExtensionContext) { - const vscodeIDE = new VscodeIDE(context); - - const hats = new VscodeHats( - vscodeIDE, - vscodeApi, - context, - vscodeIDE.runMode === "test" - ? new FakeFontMeasurements() - : new FontMeasurementsImpl(context), - ); - await hats.init(); - - // FIXME: Inject this from test harness. Would need to arrange to delay - // extension initialization, probably by returning a function from extension - // init that has parameters consisting of test configuration, and have that - // function do the actual initialization. - const cursorlessDir = - vscodeIDE.runMode === "test" - ? path.join(os.tmpdir(), crypto.randomBytes(16).toString("hex")) - : path.join(os.homedir(), ".cursorless"); - - const fileSystem = new VscodeFileSystem( - context, - vscodeIDE.runMode, - cursorlessDir, - ); - await fileSystem.initialize(); - - return { vscodeIDE, hats, fileSystem }; -} - -function createTreeSitter(parseTreeApi: ParseTreeApi): TreeSitter { - return { - getNodeAtLocation(document: TextDocument, range: Range) { - return parseTreeApi.getNodeAtLocation( - new vscode.Location(document.uri, toVscodeRange(range)), - ); - }, - - getTree(document: TextDocument) { - return parseTreeApi.getTreeForUri(document.uri); - }, - - loadLanguage: parseTreeApi.loadLanguage, - createQuery: parseTreeApi.createQuery, - }; -} - -function createScopeVisualizer( - ide: IDE, - scopeProvider: ScopeProvider, -): ScopeVisualizer { - let scopeVisualizer: VscodeScopeVisualizer | undefined; - let currentScopeType: ScopeType | undefined; - - const listeners: ScopeVisualizerListener[] = []; - - return { - start(scopeType: ScopeType, visualizationType: VisualizationType) { - scopeVisualizer?.dispose(); - scopeVisualizer = createVscodeScopeVisualizer( - ide, - scopeProvider, - scopeType, - visualizationType, - ); - scopeVisualizer.start(); - currentScopeType = scopeType; - listeners - .slice() - .forEach((listener) => listener(scopeType, visualizationType)); - }, - - stop() { - scopeVisualizer?.dispose(); - scopeVisualizer = undefined; - currentScopeType = undefined; - listeners.slice().forEach((listener) => listener(undefined, undefined)); - }, - - get scopeType() { - return currentScopeType; - }, - - onDidChangeScopeType(listener: ScopeVisualizerListener): Disposable { - listeners.push(listener); - - return { - dispose() { - pull(listeners, listener); - }, - }; - }, - }; -} - -// this method is called when your extension is deactivated -export function deactivate() { - // do nothing -} diff --git a/packages/vscode-common/src/getExtensionApi.ts b/packages/vscode-common/src/getExtensionApi.ts index 851584f81b..88fdf75756 100644 --- a/packages/vscode-common/src/getExtensionApi.ts +++ b/packages/vscode-common/src/getExtensionApi.ts @@ -1,6 +1,6 @@ import type { CommandServerApi } from "@cursorless/common"; import * as vscode from "vscode"; -import type { Node, Query, Tree } from "web-tree-sitter"; +import type { Query, Tree } from "web-tree-sitter"; import type { VscodeTestHelpers } from "./TestHelpers"; export interface CursorlessApi { @@ -8,9 +8,8 @@ export interface CursorlessApi { } export interface ParseTreeApi { - getNodeAtLocation(location: vscode.Location): Node; - getTreeForUri(uri: vscode.Uri): Tree; loadLanguage(languageId: string): Promise; + getTreeForUri(uri: vscode.Uri): Tree; createQuery(languageId: string, source: string): Query | undefined; }