From f954f2f0fbc2e0f59be4665e080a12397897921c Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 9 Jul 2024 21:43:48 +0200 Subject: [PATCH 01/23] Make arguments to engine optional --- .../cursorless-engine/src/CommandHistory.ts | 2 +- .../cursorless-engine/src/actions/Actions.ts | 2 +- .../src/actions/ShowParseTree.ts | 6 ++- packages/cursorless-engine/src/core/Debug.ts | 6 +-- .../src/core/HatTokenMapImpl.ts | 23 +++++++- .../core/commandRunner/CommandRunnerImpl.ts | 2 +- .../src/core/getCommandFallback.ts | 2 +- .../cursorless-engine/src/cursorlessEngine.ts | 53 ++++++++++++------- .../src/languages/LanguageDefinitions.ts | 29 ++++++---- .../nodeCommon/TalonSpokenFormsJsonReader.ts | 14 +++++ .../LegacyContainingSyntaxScopeStage.ts | 6 ++- .../modifiers/surroundingPair/index.ts | 2 +- packages/cursorless-engine/src/runCommand.ts | 8 +-- .../src/runIntegrationTests.ts | 6 +-- .../src/testCaseRecorder/TestCaseRecorder.ts | 2 +- packages/cursorless-vscode/src/extension.ts | 6 +-- packages/vscode-common/src/getExtensionApi.ts | 2 +- 17 files changed, 117 insertions(+), 54 deletions(-) diff --git a/packages/cursorless-engine/src/CommandHistory.ts b/packages/cursorless-engine/src/CommandHistory.ts index e5d4f8415f..4550d5e269 100644 --- a/packages/cursorless-engine/src/CommandHistory.ts +++ b/packages/cursorless-engine/src/CommandHistory.ts @@ -29,7 +29,7 @@ export class CommandHistory implements CommandRunnerDecorator { constructor( private ide: IDE, - private commandServerApi: CommandServerApi | null, + private commandServerApi: CommandServerApi | undefined, fileSystem: FileSystem, ) { this.dirPath = fileSystem.cursorlessCommandHistoryDirPath; diff --git a/packages/cursorless-engine/src/actions/Actions.ts b/packages/cursorless-engine/src/actions/Actions.ts index 15d2e0f3b6..a37e6e087a 100644 --- a/packages/cursorless-engine/src/actions/Actions.ts +++ b/packages/cursorless-engine/src/actions/Actions.ts @@ -68,7 +68,7 @@ import { Decrement, Increment } from "./incrementDecrement"; */ export class Actions implements ActionRecord { constructor( - private treeSitter: TreeSitter, + private treeSitter: TreeSitter | undefined, private snippets: Snippets, private rangeUpdater: RangeUpdater, private modifierStageFactory: ModifierStageFactory, diff --git a/packages/cursorless-engine/src/actions/ShowParseTree.ts b/packages/cursorless-engine/src/actions/ShowParseTree.ts index afe6f91bf8..8992044fe6 100644 --- a/packages/cursorless-engine/src/actions/ShowParseTree.ts +++ b/packages/cursorless-engine/src/actions/ShowParseTree.ts @@ -8,11 +8,15 @@ import { flashTargets } from "../util/targetUtils"; import type { ActionReturnValue } from "./actions.types"; export default class ShowParseTree { - constructor(private treeSitter: TreeSitter) { + constructor(private treeSitter?: TreeSitter) { this.run = this.run.bind(this); } async run(targets: Target[]): Promise { + if (this.treeSitter == null) { + throw new Error("Tree-sitter is not available"); + } + await flashTargets(ide(), targets, FlashStyle.referenced); const results: string[] = ["# Cursorless parse tree"]; diff --git a/packages/cursorless-engine/src/core/Debug.ts b/packages/cursorless-engine/src/core/Debug.ts index 255b05cf8d..bc59d1c003 100644 --- a/packages/cursorless-engine/src/core/Debug.ts +++ b/packages/cursorless-engine/src/core/Debug.ts @@ -11,7 +11,7 @@ export class Debug { private disposableSelection?: Disposable; active: boolean; - constructor(private treeSitter: TreeSitter) { + constructor(private treeSitter?: TreeSitter) { ide().disposeOnExit(this); this.evaluateSetting = this.evaluateSetting.bind(this); @@ -68,7 +68,7 @@ export class Debug { private evaluateSetting() { const debugEnabled = ide().configuration.getOwnConfiguration("debug"); - if (debugEnabled) { + if (debugEnabled && this.treeSitter != null) { this.enableDebugLog(); } else { this.disableDebugLog(); @@ -78,7 +78,7 @@ export class Debug { private logBranchTypes(event: TextEditorSelectionChangeEvent) { let node: SyntaxNode; try { - node = this.treeSitter.getNodeAtLocation( + node = this.treeSitter!.getNodeAtLocation( ide().activeTextEditor!.document, event.selections[0], ); diff --git a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts index 2df45a8859..a60cd2f523 100644 --- a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts +++ b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts @@ -43,7 +43,7 @@ export class HatTokenMapImpl implements HatTokenMap { rangeUpdater: RangeUpdater, private debug: Debug, hats: Hats, - private commandServerApi: CommandServerApi | null, + private commandServerApi: CommandServerApi | undefined, ) { ide().disposeOnExit(this); this.activeMap = new IndividualHatMap(rangeUpdater); @@ -155,3 +155,24 @@ export class HatTokenMapImpl implements HatTokenMap { this.prePhraseMapsSnapshotTimestamp = hrtime.bigint(); } } + +export class DisabledHatTokenMap implements HatTokenMap { + async allocateHats() { + // Do nothing + } + + async getReadableMap() { + return { + getEntries() { + return []; + }, + getToken() { + throw new Error("Hat map is disabled"); + }, + }; + } + + dispose() { + // Do nothing + } +} diff --git a/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts b/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts index 34738cd1e2..e5f67578a0 100644 --- a/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts +++ b/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts @@ -26,7 +26,7 @@ export class CommandRunnerImpl implements CommandRunner { private noAutomaticTokenExpansion: boolean | undefined; constructor( - private commandServerApi: CommandServerApi | null, + private commandServerApi: CommandServerApi | undefined, private debug: Debug, private storedTargets: StoredTargetMap, private pipelineRunner: TargetPipelineRunner, diff --git a/packages/cursorless-engine/src/core/getCommandFallback.ts b/packages/cursorless-engine/src/core/getCommandFallback.ts index 0474062c13..54c0f27618 100644 --- a/packages/cursorless-engine/src/core/getCommandFallback.ts +++ b/packages/cursorless-engine/src/core/getCommandFallback.ts @@ -10,7 +10,7 @@ import { import type { ActionReturnValue } from "../actions/actions.types"; export async function getCommandFallback( - commandServerApi: CommandServerApi | null, + commandServerApi: CommandServerApi | undefined, runAction: (actionDescriptor: ActionDescriptor) => Promise, command: CommandComplete, ): Promise { diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index 0b4eafb33f..072f383927 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -1,24 +1,28 @@ import { Command, CommandServerApi, + ensureCommandShape, FileSystem, Hats, IDE, - ensureCommandShape, ScopeProvider, } from "@cursorless/common"; +import { KeyboardTargetUpdater } from "./KeyboardTargetUpdater"; import { CommandRunnerDecorator, CursorlessEngine, } from "./api/CursorlessEngineApi"; import { Debug } from "./core/Debug"; -import { HatTokenMapImpl } from "./core/HatTokenMapImpl"; +import { DisabledHatTokenMap, HatTokenMapImpl } from "./core/HatTokenMapImpl"; import { Snippets } from "./core/Snippets"; import { StoredTargetMap } from "./core/StoredTargets"; import { RangeUpdater } from "./core/updateSelections/RangeUpdater"; import { CustomSpokenFormGeneratorImpl } from "./generateSpokenForm/CustomSpokenFormGeneratorImpl"; import { LanguageDefinitions } from "./languages/LanguageDefinitions"; -import { TalonSpokenFormsJsonReader } from "./nodeCommon/TalonSpokenFormsJsonReader"; +import { + DisabledTalonSpokenFormsJsonReader, + TalonSpokenFormsJsonReader, +} from "./nodeCommon/TalonSpokenFormsJsonReader"; import { ModifierStageFactoryImpl } from "./processTargets/ModifierStageFactoryImpl"; import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandlers"; import { runCommand } from "./runCommand"; @@ -30,15 +34,22 @@ import { ScopeSupportChecker } from "./scopeProviders/ScopeSupportChecker"; import { ScopeSupportWatcher } from "./scopeProviders/ScopeSupportWatcher"; import { injectIde } from "./singletons/ide.singleton"; import { TreeSitter } from "./typings/TreeSitter"; -import { KeyboardTargetUpdater } from "./KeyboardTargetUpdater"; -export async function createCursorlessEngine( - treeSitter: TreeSitter, - ide: IDE, - hats: Hats, - commandServerApi: CommandServerApi | null, - fileSystem: FileSystem, -): Promise { +interface Props { + ide: IDE; + fileSystem?: FileSystem; + hats?: Hats; + treeSitter?: TreeSitter; + commandServerApi?: CommandServerApi; +} + +export async function createCursorlessEngine({ + ide, + fileSystem, + hats, + treeSitter, + commandServerApi, +}: Props): Promise { injectIde(ide); const debug = new Debug(treeSitter); @@ -46,15 +57,14 @@ export async function createCursorlessEngine( const rangeUpdater = new RangeUpdater(); const snippets = new Snippets(); - snippets.init(); + void snippets.init(); - const hatTokenMap = new HatTokenMapImpl( - rangeUpdater, - debug, - hats, - commandServerApi, - ); - hatTokenMap.allocateHats(); + const hatTokenMap = + hats != null + ? new HatTokenMapImpl(rangeUpdater, debug, hats, commandServerApi) + : new DisabledHatTokenMap(); + + void hatTokenMap.allocateHats(); const storedTargets = new StoredTargetMap(); @@ -63,7 +73,10 @@ export async function createCursorlessEngine( const languageDefinitions = new LanguageDefinitions(fileSystem, treeSitter); await languageDefinitions.init(); - const talonSpokenForms = new TalonSpokenFormsJsonReader(fileSystem); + const talonSpokenForms = + fileSystem != null + ? new TalonSpokenFormsJsonReader(fileSystem) + : new DisabledTalonSpokenFormsJsonReader(); const customSpokenFormGenerator = new CustomSpokenFormGeneratorImpl( talonSpokenForms, diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts index 416e861ead..a061716167 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts @@ -46,8 +46,8 @@ export class LanguageDefinitions { private disposables: Disposable[] = []; constructor( - private fileSystem: FileSystem, - private treeSitter: TreeSitter, + private fileSystem?: FileSystem, + private treeSitter?: TreeSitter, ) { ide().onDidOpenTextDocument((document) => { this.loadLanguage(document.languageId); @@ -63,7 +63,7 @@ export class LanguageDefinitions { ? join(getCursorlessRepoRoot(), "queries") : "queries"; - if (ide().runMode === "development") { + if (fileSystem != null && ide().runMode === "development") { this.disposables.push( fileSystem.watchDir(this.queryDir, () => { this.reloadLanguageDefinitions(); @@ -103,12 +103,14 @@ export class LanguageDefinitions { } const definition = - (await LanguageDefinition.create( - this.treeSitter, - this.fileSystem, - this.queryDir, - languageId, - )) ?? LANGUAGE_UNDEFINED; + (this.treeSitter != null && this.fileSystem != null + ? await LanguageDefinition.create( + this.treeSitter, + this.fileSystem, + this.queryDir, + languageId, + ) + : null) ?? LANGUAGE_UNDEFINED; this.languageDefinitions.set(languageId, definition); } @@ -143,8 +145,13 @@ export class LanguageDefinitions { /** * @deprecated Only for use in legacy containing scope stage */ - public getNodeAtLocation(document: TextDocument, range: Range): SyntaxNode { - return this.treeSitter.getNodeAtLocation(document, range); + public getNodeAtLocation( + document: TextDocument, + range: Range, + ): SyntaxNode | null { + return this.treeSitter != null + ? this.treeSitter.getNodeAtLocation(document, range) + : null; } onDidChangeDefinition = this.notifier.registerListener; diff --git a/packages/cursorless-engine/src/nodeCommon/TalonSpokenFormsJsonReader.ts b/packages/cursorless-engine/src/nodeCommon/TalonSpokenFormsJsonReader.ts index 26c6db93bb..d266710022 100644 --- a/packages/cursorless-engine/src/nodeCommon/TalonSpokenFormsJsonReader.ts +++ b/packages/cursorless-engine/src/nodeCommon/TalonSpokenFormsJsonReader.ts @@ -75,3 +75,17 @@ export class TalonSpokenFormsJsonReader implements TalonSpokenForms { function isErrnoException(error: any): error is NodeJS.ErrnoException { return error instanceof Error && "code" in error; } + +export class DisabledTalonSpokenFormsJsonReader implements TalonSpokenForms { + getSpokenFormEntries(): Promise { + return Promise.resolve([]); + } + + onDidChange(): Disposable { + return { + dispose() { + // Do nothing + }, + }; + } +} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts index b099e10dca..c9d693efb4 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts @@ -38,11 +38,15 @@ export class LegacyContainingSyntaxScopeStage implements ModifierStage { this.modifier.type === "everyScope", ); - const node: SyntaxNode | null = this.languageDefinitions.getNodeAtLocation( + const node = this.languageDefinitions.getNodeAtLocation( target.editor.document, target.contentRange, ); + if (node == null) { + throw new NoContainingScopeError(this.modifier.scopeType.type); + } + const scopeNodes = findNearestContainingAncestorNode(node, nodeMatcher, { editor: target.editor, selection: new Selection( diff --git a/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts b/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts index f81ef8ebab..096abe7698 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts @@ -75,7 +75,7 @@ function processSurroundingPairCore( // Error nodes are unreliable and should be ignored. Fall back to text based // algorithm. - if (nodeHasError(node)) { + if (node == null || nodeHasError(node)) { return findSurroundingPairTextBased( editor, range, diff --git a/packages/cursorless-engine/src/runCommand.ts b/packages/cursorless-engine/src/runCommand.ts index fb2a4b2abc..cb627aa676 100644 --- a/packages/cursorless-engine/src/runCommand.ts +++ b/packages/cursorless-engine/src/runCommand.ts @@ -34,8 +34,8 @@ import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandler * 5. Call {@link CommandRunnerImpl.run} to run the actual command. */ export async function runCommand( - treeSitter: TreeSitter, - commandServerApi: CommandServerApi | null, + treeSitter: TreeSitter | undefined, + commandServerApi: CommandServerApi | undefined, debug: Debug, hatTokenMap: HatTokenMap, snippets: Snippets, @@ -90,8 +90,8 @@ async function unwrapLegacyCommandResponse( } function createCommandRunner( - treeSitter: TreeSitter, - commandServerApi: CommandServerApi | null, + treeSitter: TreeSitter | undefined, + commandServerApi: CommandServerApi | undefined, languageDefinitions: LanguageDefinitions, debug: Debug, storedTargets: StoredTargetMap, diff --git a/packages/cursorless-engine/src/runIntegrationTests.ts b/packages/cursorless-engine/src/runIntegrationTests.ts index 03aa06a2f0..429a1376e1 100644 --- a/packages/cursorless-engine/src/runIntegrationTests.ts +++ b/packages/cursorless-engine/src/runIntegrationTests.ts @@ -14,19 +14,19 @@ import { unsafeKeys } from "./util/object"; * @param languageDefinitions The language definitions instance */ export async function runIntegrationTests( - treeSitter: TreeSitter, + treeSitter: TreeSitter | undefined, languageDefinitions: LanguageDefinitions, ) { await assertNoScopesBothLegacyAndNew(treeSitter, languageDefinitions); } async function assertNoScopesBothLegacyAndNew( - treeSitter: TreeSitter, + treeSitter: TreeSitter | undefined, languageDefinitions: LanguageDefinitions, ) { const errors: string[] = []; for (const languageId of legacyLanguageIds) { - await treeSitter.loadLanguage(languageId); + await treeSitter?.loadLanguage(languageId); await languageDefinitions.loadLanguage(languageId); unsafeKeys(languageMatchers[languageId] ?? {}).map((scopeTypeType) => { diff --git a/packages/cursorless-engine/src/testCaseRecorder/TestCaseRecorder.ts b/packages/cursorless-engine/src/testCaseRecorder/TestCaseRecorder.ts index 31436e4f9e..530e67c368 100644 --- a/packages/cursorless-engine/src/testCaseRecorder/TestCaseRecorder.ts +++ b/packages/cursorless-engine/src/testCaseRecorder/TestCaseRecorder.ts @@ -65,7 +65,7 @@ export class TestCaseRecorder { private spokenFormGenerator = new SpokenFormGenerator(defaultSpokenFormMap); constructor( - private commandServerApi: CommandServerApi | null, + private commandServerApi: CommandServerApi | undefined, private hatTokenMap: HatTokenMap, private storedTargets: StoredTargetMap, ) { diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index e6d5a3f6e5..daef08ca8a 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -93,13 +93,13 @@ export async function activate( runIntegrationTests, addCommandRunnerDecorator, customSpokenFormGenerator, - } = await createCursorlessEngine( + } = await createCursorlessEngine({ treeSitter, - normalizedIde, + ide: normalizedIde, hats, commandServerApi, fileSystem, - ); + }); addCommandRunnerDecorator( new CommandHistory(normalizedIde, commandServerApi, fileSystem), diff --git a/packages/vscode-common/src/getExtensionApi.ts b/packages/vscode-common/src/getExtensionApi.ts index 78138514e8..182bd2f827 100644 --- a/packages/vscode-common/src/getExtensionApi.ts +++ b/packages/vscode-common/src/getExtensionApi.ts @@ -24,7 +24,7 @@ export interface ParseTreeApi { export async function getExtensionApi(extensionId: string) { const extension = vscode.extensions.getExtension(extensionId); - return extension == null ? null : ((await extension.activate()) as T); + return extension == null ? undefined : ((await extension.activate()) as T); } export async function getExtensionApiStrict(extensionId: string) { From 4cf1a71d493d53f75887d0232f1dde0952607bcc Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 9 Jul 2024 21:46:09 +0200 Subject: [PATCH 02/23] clean up --- packages/cursorless-engine/src/core/Debug.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/cursorless-engine/src/core/Debug.ts b/packages/cursorless-engine/src/core/Debug.ts index bc59d1c003..5dd09fe6df 100644 --- a/packages/cursorless-engine/src/core/Debug.ts +++ b/packages/cursorless-engine/src/core/Debug.ts @@ -52,10 +52,12 @@ export class Debug { } private enableDebugLog() { - this.active = true; - this.disposableSelection = ide().onDidChangeTextEditorSelection( - this.logBranchTypes, - ); + if (this.treeSitter != null) { + this.active = true; + this.disposableSelection = ide().onDidChangeTextEditorSelection( + this.logBranchTypes, + ); + } } private disableDebugLog() { @@ -68,7 +70,7 @@ export class Debug { private evaluateSetting() { const debugEnabled = ide().configuration.getOwnConfiguration("debug"); - if (debugEnabled && this.treeSitter != null) { + if (debugEnabled) { this.enableDebugLog(); } else { this.disableDebugLog(); From b48f309715496aa40b9c97cfff170b3c22799ae9 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 9 Jul 2024 21:51:28 +0200 Subject: [PATCH 03/23] Fix --- packages/cursorless-engine/src/core/Debug.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cursorless-engine/src/core/Debug.ts b/packages/cursorless-engine/src/core/Debug.ts index 5dd09fe6df..573dbb30c4 100644 --- a/packages/cursorless-engine/src/core/Debug.ts +++ b/packages/cursorless-engine/src/core/Debug.ts @@ -52,8 +52,8 @@ export class Debug { } private enableDebugLog() { + this.active = true; if (this.treeSitter != null) { - this.active = true; this.disposableSelection = ide().onDidChangeTextEditorSelection( this.logBranchTypes, ); From 50f504453836a4102ded89777a2deba9a24892cb Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 9 Jul 2024 21:55:23 +0200 Subject: [PATCH 04/23] clean up --- .../cursorless-engine/src/languages/LanguageDefinitions.ts | 4 ++-- .../src/processTargets/modifiers/surroundingPair/index.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts index a061716167..ab0b9a7d2e 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts @@ -148,10 +148,10 @@ export class LanguageDefinitions { public getNodeAtLocation( document: TextDocument, range: Range, - ): SyntaxNode | null { + ): SyntaxNode | undefined { return this.treeSitter != null ? this.treeSitter.getNodeAtLocation(document, range) - : null; + : undefined; } onDidChangeDefinition = this.notifier.registerListener; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts b/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts index 096abe7698..635d9c1ba7 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts @@ -68,7 +68,7 @@ function processSurroundingPairCore( scopeType.delimiter as ComplexSurroundingPairName ] ?? [scopeType.delimiter]; - let node: SyntaxNode | null; + let node: SyntaxNode | undefined; try { node = languageDefinitions.getNodeAtLocation(document, range); From 4299388e8317914319e4aa73aac6f017e65d89ec Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 17:44:43 +0200 Subject: [PATCH 05/23] clean up --- .../src/core/HatTokenMapImpl.ts | 21 ------------------ .../disabledComponents/DisabledHatTokenMap.ts | 22 +++++++++++++++++++ 2 files changed, 22 insertions(+), 21 deletions(-) create mode 100644 packages/cursorless-engine/src/disabledComponents/DisabledHatTokenMap.ts diff --git a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts index a60cd2f523..7f08fd35fe 100644 --- a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts +++ b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts @@ -155,24 +155,3 @@ export class HatTokenMapImpl implements HatTokenMap { this.prePhraseMapsSnapshotTimestamp = hrtime.bigint(); } } - -export class DisabledHatTokenMap implements HatTokenMap { - async allocateHats() { - // Do nothing - } - - async getReadableMap() { - return { - getEntries() { - return []; - }, - getToken() { - throw new Error("Hat map is disabled"); - }, - }; - } - - dispose() { - // Do nothing - } -} diff --git a/packages/cursorless-engine/src/disabledComponents/DisabledHatTokenMap.ts b/packages/cursorless-engine/src/disabledComponents/DisabledHatTokenMap.ts new file mode 100644 index 0000000000..3e9ef947a0 --- /dev/null +++ b/packages/cursorless-engine/src/disabledComponents/DisabledHatTokenMap.ts @@ -0,0 +1,22 @@ +import type { HatTokenMap } from "@cursorless/common"; + +export class DisabledHatTokenMap implements HatTokenMap { + async allocateHats() { + // Do nothing + } + + async getReadableMap() { + return { + getEntries() { + return []; + }, + getToken() { + throw new Error("Hat map is disabled"); + }, + }; + } + + dispose() { + // Do nothing + } +} From d86dfedecfbc538e6248bb2501a6cff4ec952ea6 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 17:45:49 +0200 Subject: [PATCH 06/23] fix --- packages/cursorless-engine/src/cursorlessEngine.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index 11570048db..11a3abfc30 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -52,7 +52,7 @@ export async function createCursorlessEngine({ hats, treeSitter, commandServerApi, - snippets, + snippets = new DisabledSnippets(), }: Props): Promise { injectIde(ide); @@ -83,8 +83,6 @@ export async function createCursorlessEngine({ talonSpokenForms, ); - snippets = snippets ?? new DisabledSnippets(); - ide.disposeOnExit( rangeUpdater, languageDefinitions, From 697a678950107111cbe816a6b3397dcda6855b7b Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 17:53:16 +0200 Subject: [PATCH 07/23] Remove the tree sitter from debug print --- packages/cursorless-engine/src/core/Debug.ts | 87 ++----------------- .../cursorless-engine/src/cursorlessEngine.ts | 2 +- 2 files changed, 7 insertions(+), 82 deletions(-) diff --git a/packages/cursorless-engine/src/core/Debug.ts b/packages/cursorless-engine/src/core/Debug.ts index 573dbb30c4..f5ec1d09bb 100644 --- a/packages/cursorless-engine/src/core/Debug.ts +++ b/packages/cursorless-engine/src/core/Debug.ts @@ -1,7 +1,4 @@ -import { Disposable, TextEditorSelectionChangeEvent } from "@cursorless/common"; -import type { SyntaxNode, TreeCursor } from "web-tree-sitter"; -import { ide } from "../singletons/ide.singleton"; -import { TreeSitter } from "../typings/TreeSitter"; +import type { Disposable, IDE } from "@cursorless/common"; /** * Debug logger @@ -11,14 +8,13 @@ export class Debug { private disposableSelection?: Disposable; active: boolean; - constructor(private treeSitter?: TreeSitter) { - ide().disposeOnExit(this); + constructor(private ide: IDE) { + ide.disposeOnExit(this); this.evaluateSetting = this.evaluateSetting.bind(this); - this.logBranchTypes = this.logBranchTypes.bind(this); this.active = true; - switch (ide().runMode) { + switch (ide.runMode) { // Development mode. Always enable. case "development": this.enableDebugLog(); @@ -31,7 +27,7 @@ export class Debug { case "production": this.evaluateSetting(); this.disposableConfiguration = - ide().configuration.onDidChangeConfiguration(this.evaluateSetting); + ide.configuration.onDidChangeConfiguration(this.evaluateSetting); break; } } @@ -53,11 +49,6 @@ export class Debug { private enableDebugLog() { this.active = true; - if (this.treeSitter != null) { - this.disposableSelection = ide().onDidChangeTextEditorSelection( - this.logBranchTypes, - ); - } } private disableDebugLog() { @@ -69,77 +60,11 @@ export class Debug { } private evaluateSetting() { - const debugEnabled = ide().configuration.getOwnConfiguration("debug"); + const debugEnabled = this.ide.configuration.getOwnConfiguration("debug"); if (debugEnabled) { this.enableDebugLog(); } else { this.disableDebugLog(); } } - - private logBranchTypes(event: TextEditorSelectionChangeEvent) { - let node: SyntaxNode; - try { - node = this.treeSitter!.getNodeAtLocation( - ide().activeTextEditor!.document, - event.selections[0], - ); - } catch (error) { - return; - } - - const ancestors: SyntaxNode[] = [node]; - while (node.parent != null) { - ancestors.unshift(node.parent); - node = node.parent; - } - - const cursor = node.tree.walk(); - this.printCursorLocationInfo(ancestors, cursor, 0); - } - - private printCursorLocationInfo( - nodes: SyntaxNode[], - cursor: TreeCursor, - index: number, - ) { - const field = cursor.currentFieldName; - const fieldText = field != null ? `${field}: ` : ""; - const indent = " ".repeat(index); - const nodeIsLast = index === nodes.length - 1; - const { nodeIsNamed } = cursor; - let text = `${indent}${fieldText}`; - - if (nodeIsNamed) { - text += `(${cursor.nodeType}`; - if (nodeIsLast) { - text += ")"; - } - } else { - text += `"${cursor.nodeType}"`; - } - - console.log(text); - - if ( - !nodeIsLast && - this.cursorGoToChildWithId(cursor, nodes[index + 1].id) - ) { - this.printCursorLocationInfo(nodes, cursor, index + 1); - } - - if (nodeIsNamed && !nodeIsLast) { - console.log(`${indent})`); - } - } - - private cursorGoToChildWithId(cursor: TreeCursor, id: number): boolean { - cursor.gotoFirstChild(); - while (cursor.currentNode.id !== id) { - if (!cursor.gotoNextSibling()) { - return false; - } - } - return true; - } } diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index dd61d90da7..32b842a095 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -56,7 +56,7 @@ export async function createCursorlessEngine({ }: Props): Promise { injectIde(ide); - const debug = new Debug(treeSitter); + const debug = new Debug(ide); const rangeUpdater = new RangeUpdater(); From 5d3076a488171d3ef8c8070d3e39df616c2315e8 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 18:00:52 +0200 Subject: [PATCH 08/23] Created disabled command server api --- .../src/core/HatTokenMapImpl.ts | 4 ++-- .../core/commandRunner/CommandRunnerImpl.ts | 2 +- .../src/core/getCommandFallback.ts | 4 ++-- .../cursorless-engine/src/cursorlessEngine.ts | 3 ++- .../DisabledCommandServerApi.ts | 19 +++++++++++++++++++ packages/cursorless-engine/src/runCommand.ts | 4 ++-- 6 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 packages/cursorless-engine/src/disabledComponents/DisabledCommandServerApi.ts diff --git a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts index 7f08fd35fe..f4c08fd7aa 100644 --- a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts +++ b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts @@ -43,7 +43,7 @@ export class HatTokenMapImpl implements HatTokenMap { rangeUpdater: RangeUpdater, private debug: Debug, hats: Hats, - private commandServerApi: CommandServerApi | undefined, + private commandServerApi: CommandServerApi, ) { ide().disposeOnExit(this); this.activeMap = new IndividualHatMap(rangeUpdater); @@ -130,7 +130,7 @@ export class HatTokenMapImpl implements HatTokenMap { } private async maybeTakePrePhraseSnapshot() { - const phraseStartSignal = this.commandServerApi?.signals?.prePhrase; + const phraseStartSignal = this.commandServerApi.signals.prePhrase; if (phraseStartSignal != null) { const newSignalVersion = await phraseStartSignal.getVersion(); diff --git a/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts b/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts index e5f67578a0..903650870c 100644 --- a/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts +++ b/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts @@ -26,7 +26,7 @@ export class CommandRunnerImpl implements CommandRunner { private noAutomaticTokenExpansion: boolean | undefined; constructor( - private commandServerApi: CommandServerApi | undefined, + private commandServerApi: CommandServerApi, private debug: Debug, private storedTargets: StoredTargetMap, private pipelineRunner: TargetPipelineRunner, diff --git a/packages/cursorless-engine/src/core/getCommandFallback.ts b/packages/cursorless-engine/src/core/getCommandFallback.ts index 54c0f27618..6e6510bbb8 100644 --- a/packages/cursorless-engine/src/core/getCommandFallback.ts +++ b/packages/cursorless-engine/src/core/getCommandFallback.ts @@ -10,11 +10,11 @@ import { import type { ActionReturnValue } from "../actions/actions.types"; export async function getCommandFallback( - commandServerApi: CommandServerApi | undefined, + commandServerApi: CommandServerApi, runAction: (actionDescriptor: ActionDescriptor) => Promise, command: CommandComplete, ): Promise { - const focusedElementType = await commandServerApi?.getFocusedElementType(); + const focusedElementType = await commandServerApi.getFocusedElementType(); if (focusedElementType == null || focusedElementType === "textEditor") { return null; diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index 32b842a095..55dab047d1 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -17,6 +17,7 @@ import { HatTokenMapImpl } from "./core/HatTokenMapImpl"; import type { Snippets } from "./core/Snippets"; import { StoredTargetMap } from "./core/StoredTargets"; import { RangeUpdater } from "./core/updateSelections/RangeUpdater"; +import { createDisabledCommandServerApi } from "./disabledComponents/DisabledCommandServerApi"; import { DisabledHatTokenMap } from "./disabledComponents/DisabledHatTokenMap"; import { DisabledSnippets } from "./disabledComponents/DisabledSnippets"; import { DisabledTalonSpokenForms } from "./disabledComponents/DisabledTalonSpokenForms"; @@ -50,7 +51,7 @@ export async function createCursorlessEngine({ fileSystem, hats, treeSitter, - commandServerApi, + commandServerApi = createDisabledCommandServerApi(), talonSpokenForms = new DisabledTalonSpokenForms(), snippets = new DisabledSnippets(), }: Props): Promise { diff --git a/packages/cursorless-engine/src/disabledComponents/DisabledCommandServerApi.ts b/packages/cursorless-engine/src/disabledComponents/DisabledCommandServerApi.ts new file mode 100644 index 0000000000..0700cd51b3 --- /dev/null +++ b/packages/cursorless-engine/src/disabledComponents/DisabledCommandServerApi.ts @@ -0,0 +1,19 @@ +import type { CommandServerApi } from "@cursorless/common"; + +const DisabledCommandServerApi: CommandServerApi = { + getFocusedElementType() { + return Promise.resolve(undefined); + }, + + signals: { + prePhrase: { + getVersion() { + return Promise.resolve(null); + }, + }, + }, +}; + +export function createDisabledCommandServerApi() { + return DisabledCommandServerApi; +} diff --git a/packages/cursorless-engine/src/runCommand.ts b/packages/cursorless-engine/src/runCommand.ts index 014933cc7b..660ed3dfbb 100644 --- a/packages/cursorless-engine/src/runCommand.ts +++ b/packages/cursorless-engine/src/runCommand.ts @@ -34,7 +34,7 @@ import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandler */ export async function runCommand( treeSitter: TreeSitter | undefined, - commandServerApi: CommandServerApi | undefined, + commandServerApi: CommandServerApi, debug: Debug, hatTokenMap: HatTokenMap, snippets: Snippets, @@ -90,7 +90,7 @@ async function unwrapLegacyCommandResponse( function createCommandRunner( treeSitter: TreeSitter | undefined, - commandServerApi: CommandServerApi | undefined, + commandServerApi: CommandServerApi, languageDefinitions: LanguageDefinitions, debug: Debug, storedTargets: StoredTargetMap, From cfbdadf9c57fb01c1bfb52c0917e6238b6813159 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 18:41:04 +0200 Subject: [PATCH 09/23] more work --- .../ide/types/LanguageDefinitionsProvider.ts | 6 ++ packages/common/src/index.ts | 1 + .../cursorless-engine/src/actions/Actions.ts | 2 +- .../src/actions/ShowParseTree.ts | 6 +- .../cursorless-engine/src/cursorlessEngine.ts | 26 ++++-- .../DisabledLanguageDefinitions.ts | 33 +++++++ .../disabledComponents/DisabledTreeSitter.ts | 21 +++++ .../src/languages/LanguageDefinition.ts | 37 ++++---- .../src/languages/LanguageDefinitions.ts | 93 +++++++++---------- packages/cursorless-engine/src/runCommand.ts | 4 +- .../src/runIntegrationTests.ts | 4 +- packages/cursorless-vscode/src/extension.ts | 9 +- .../FileSystemLanguageDefinitionsProvider.ts | 48 ++++++++++ packages/file-system-common/src/index.ts | 1 + 14 files changed, 204 insertions(+), 87 deletions(-) create mode 100644 packages/common/src/ide/types/LanguageDefinitionsProvider.ts create mode 100644 packages/cursorless-engine/src/disabledComponents/DisabledLanguageDefinitions.ts create mode 100644 packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts create mode 100644 packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts diff --git a/packages/common/src/ide/types/LanguageDefinitionsProvider.ts b/packages/common/src/ide/types/LanguageDefinitionsProvider.ts new file mode 100644 index 0000000000..463aa8c63d --- /dev/null +++ b/packages/common/src/ide/types/LanguageDefinitionsProvider.ts @@ -0,0 +1,6 @@ +import { Disposable } from "@cursorless/common"; + +export interface LanguageDefinitionsProvider { + onChanges(listener: () => void): Disposable; + readQueryFile(filename: string): Promise; +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index a56e80ba5b..88fd9727fc 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -31,6 +31,7 @@ export * from "./ide/types/QuickPickOptions"; export * from "./ide/types/events.types"; export * from "./ide/types/Paths"; export * from "./ide/types/CommandHistoryStorage"; +export * from "./ide/types/LanguageDefinitionsProvider"; export * from "./ide/types/FileSystem.types"; export * from "./types/RangeExpansionBehavior"; export * from "./types/InputBoxOptions"; diff --git a/packages/cursorless-engine/src/actions/Actions.ts b/packages/cursorless-engine/src/actions/Actions.ts index 15d588becd..6ec25e74b0 100644 --- a/packages/cursorless-engine/src/actions/Actions.ts +++ b/packages/cursorless-engine/src/actions/Actions.ts @@ -68,7 +68,7 @@ import { Decrement, Increment } from "./incrementDecrement"; */ export class Actions implements ActionRecord { constructor( - private treeSitter: TreeSitter | undefined, + private treeSitter: TreeSitter, private snippets: Snippets, private rangeUpdater: RangeUpdater, private modifierStageFactory: ModifierStageFactory, diff --git a/packages/cursorless-engine/src/actions/ShowParseTree.ts b/packages/cursorless-engine/src/actions/ShowParseTree.ts index bc75b47043..7ef056878a 100644 --- a/packages/cursorless-engine/src/actions/ShowParseTree.ts +++ b/packages/cursorless-engine/src/actions/ShowParseTree.ts @@ -8,15 +8,11 @@ import { flashTargets } from "../util/targetUtils"; import type { ActionReturnValue } from "./actions.types"; export default class ShowParseTree { - constructor(private treeSitter?: TreeSitter) { + constructor(private treeSitter: TreeSitter) { this.run = this.run.bind(this); } async run(targets: Target[]): Promise { - if (this.treeSitter == null) { - throw new Error("Tree-sitter is not available"); - } - await flashTargets(ide(), targets, FlashStyle.referenced); const results: string[] = ["# Cursorless parse tree"]; diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index 55dab047d1..b8094adf84 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -1,11 +1,11 @@ import { Command, CommandServerApi, - FileSystem, Hats, IDE, ScopeProvider, ensureCommandShape, + type LanguageDefinitionsProvider, } from "@cursorless/common"; import { KeyboardTargetUpdater } from "./KeyboardTargetUpdater"; import { @@ -22,7 +22,10 @@ import { DisabledHatTokenMap } from "./disabledComponents/DisabledHatTokenMap"; import { DisabledSnippets } from "./disabledComponents/DisabledSnippets"; import { DisabledTalonSpokenForms } from "./disabledComponents/DisabledTalonSpokenForms"; import { CustomSpokenFormGeneratorImpl } from "./generateSpokenForm/CustomSpokenFormGeneratorImpl"; -import { LanguageDefinitions } from "./languages/LanguageDefinitions"; +import { + LanguageDefinitions, + LanguageDefinitionsImpl, +} from "./languages/LanguageDefinitions"; import { ModifierStageFactoryImpl } from "./processTargets/ModifierStageFactoryImpl"; import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandlers"; import { runCommand } from "./runCommand"; @@ -35,22 +38,24 @@ import { ScopeSupportWatcher } from "./scopeProviders/ScopeSupportWatcher"; import { type TalonSpokenForms } from "./scopeProviders/TalonSpokenForms"; import { injectIde } from "./singletons/ide.singleton"; import { TreeSitter } from "./typings/TreeSitter"; +import { DisabledLanguageDefinitions } from "./disabledComponents/DisabledLanguageDefinitions"; +import { DisabledTreeSitter } from "./disabledComponents/DisabledTreeSitter"; interface Props { ide: IDE; - fileSystem?: FileSystem; hats?: Hats; + languageDefinitionsProvider?: LanguageDefinitionsProvider; treeSitter?: TreeSitter; commandServerApi?: CommandServerApi; - snippets?: Snippets; talonSpokenForms?: TalonSpokenForms; + snippets?: Snippets; } export async function createCursorlessEngine({ ide, - fileSystem, hats, - treeSitter, + languageDefinitionsProvider, + treeSitter = new DisabledTreeSitter(), commandServerApi = createDisabledCommandServerApi(), talonSpokenForms = new DisabledTalonSpokenForms(), snippets = new DisabledSnippets(), @@ -72,7 +77,14 @@ export async function createCursorlessEngine({ const keyboardTargetUpdater = new KeyboardTargetUpdater(storedTargets); - const languageDefinitions = new LanguageDefinitions(fileSystem, treeSitter); + const languageDefinitions = + languageDefinitionsProvider != null + ? new LanguageDefinitionsImpl( + ide, + languageDefinitionsProvider, + treeSitter, + ) + : new DisabledLanguageDefinitions(); await languageDefinitions.init(); const customSpokenFormGenerator = new CustomSpokenFormGeneratorImpl( diff --git a/packages/cursorless-engine/src/disabledComponents/DisabledLanguageDefinitions.ts b/packages/cursorless-engine/src/disabledComponents/DisabledLanguageDefinitions.ts new file mode 100644 index 0000000000..cffda317f2 --- /dev/null +++ b/packages/cursorless-engine/src/disabledComponents/DisabledLanguageDefinitions.ts @@ -0,0 +1,33 @@ +import type { TextDocument, Range, Listener } from "@cursorless/common"; +import type { SyntaxNode } from "web-tree-sitter"; +import type { LanguageDefinition } from "../languages/LanguageDefinition"; +import type { LanguageDefinitions } from "../languages/LanguageDefinitions"; + +export class DisabledLanguageDefinitions implements LanguageDefinitions { + init(): Promise { + return Promise.resolve(); + } + + onDidChangeDefinition(_listener: Listener) { + return { dispose: () => {} }; + } + + loadLanguage(_languageId: string): Promise { + return Promise.resolve(); + } + + get(_languageId: string): LanguageDefinition | undefined { + return undefined; + } + + getNodeAtLocation( + _document: TextDocument, + _range: Range, + ): SyntaxNode | undefined { + return undefined; + } + + dispose(): void { + // Do nothing + } +} diff --git a/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts b/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts new file mode 100644 index 0000000000..480e4473f4 --- /dev/null +++ b/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts @@ -0,0 +1,21 @@ +import type { TextDocument, Range } from "@cursorless/common"; +import type { SyntaxNode, Tree, Language } from "web-tree-sitter"; +import type { TreeSitter } from "../typings/TreeSitter"; + +export class DisabledTreeSitter implements TreeSitter { + getNodeAtLocation(_document: TextDocument, _range: Range): SyntaxNode { + throw new Error("Tree sitter not provided"); + } + + getTree(_document: TextDocument): Tree { + throw new Error("Tree sitter not provided"); + } + + getLanguage(_languageId: string): Language | undefined { + return undefined; + } + + loadLanguage(_languageId: string): Promise { + return Promise.resolve(false); + } +} diff --git a/packages/cursorless-engine/src/languages/LanguageDefinition.ts b/packages/cursorless-engine/src/languages/LanguageDefinition.ts index 83252927d4..fc96229100 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinition.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinition.ts @@ -1,10 +1,10 @@ import { - FileSystem, ScopeType, SimpleScopeType, showError, + type LanguageDefinitionsProvider, } from "@cursorless/common"; -import { basename, dirname, join } from "pathe"; +import { dirname, join } from "pathe"; import { TreeSitterScopeHandler } from "../processTargets/modifiers/scopeHandlers"; import { ide } from "../singletons/ide.singleton"; import { TreeSitter } from "../typings/TreeSitter"; @@ -36,16 +36,13 @@ export class LanguageDefinition { * id doesn't have a new-style query definition */ static async create( + provider: LanguageDefinitionsProvider, treeSitter: TreeSitter, - fileSystem: FileSystem, - queryDir: string, languageId: string, ): Promise { - const languageQueryPath = join(queryDir, `${languageId}.scm`); - const rawLanguageQueryString = await readQueryFileAndImports( - fileSystem, - languageQueryPath, + provider, + `${languageId}.scm`, ); if (rawLanguageQueryString == null) { @@ -91,12 +88,12 @@ export class LanguageDefinition { * @returns The text of the query file, with all imports inlined */ async function readQueryFileAndImports( - fileSystem: FileSystem, - languageQueryPath: string, + provider: LanguageDefinitionsProvider, + languageFilename: string, ) { // Seed the map with the query file itself const rawQueryStrings: Record = { - [languageQueryPath]: null, + [languageFilename]: null, }; const doValidation = ide().runMode !== "production"; @@ -105,17 +102,15 @@ async function readQueryFileAndImports( // encounter an import in a query file, we add it to the map with a value // of null, so that it will be read on the next iteration while (Object.values(rawQueryStrings).some((v) => v == null)) { - for (const [queryPath, rawQueryString] of Object.entries(rawQueryStrings)) { + for (const [filename, rawQueryString] of Object.entries(rawQueryStrings)) { if (rawQueryString != null) { continue; } - const fileName = basename(queryPath); - - let rawQuery = await fileSystem.readBundledFile(queryPath); + let rawQuery = await provider.readQueryFile(filename); if (rawQuery == null) { - if (queryPath === languageQueryPath) { + if (filename === languageFilename) { // If this is the main query file, then we know that this language // just isn't defined using new-style queries return undefined; @@ -124,7 +119,7 @@ async function readQueryFileAndImports( showError( ide().messages, "LanguageDefinition.readQueryFileAndImports.queryNotFound", - `Could not find imported query file ${queryPath}`, + `Could not find imported query file ${filename}`, ); if (ide().runMode === "test") { @@ -136,10 +131,10 @@ async function readQueryFileAndImports( } if (doValidation) { - validateQueryCaptures(fileName, rawQuery); + validateQueryCaptures(filename, rawQuery); } - rawQueryStrings[queryPath] = rawQuery; + rawQueryStrings[filename] = rawQuery; matchAll( rawQuery, // Matches lines like: @@ -154,10 +149,10 @@ async function readQueryFileAndImports( const relativeImportPath = match[1]; if (doValidation) { - validateImportSyntax(fileName, relativeImportPath, match[0]); + validateImportSyntax(filename, relativeImportPath, match[0]); } - const importQueryPath = join(dirname(queryPath), relativeImportPath); + const importQueryPath = join(dirname(filename), relativeImportPath); rawQueryStrings[importQueryPath] = rawQueryStrings[importQueryPath] ?? null; }, diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts index 2d9d135383..4a5c491980 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts @@ -1,19 +1,19 @@ import { Disposable, - FileSystem, Notifier, Range, TextDocument, - getCursorlessRepoRoot, isTesting, showError, + type IDE, + type LanguageDefinitionsProvider, + type Listener, } from "@cursorless/common"; -import { join } from "pathe"; +import { toString } from "lodash-es"; import { SyntaxNode } from "web-tree-sitter"; -import { TreeSitter } from "../typings/TreeSitter"; import { ide } from "../singletons/ide.singleton"; +import { TreeSitter } from "../typings/TreeSitter"; import { LanguageDefinition } from "./LanguageDefinition"; -import { toString } from "lodash-es"; /** * Sentinel value to indicate that a language doesn't have @@ -21,11 +21,37 @@ import { toString } from "lodash-es"; */ const LANGUAGE_UNDEFINED = Symbol("LANGUAGE_UNDEFINED"); +export interface LanguageDefinitions extends Disposable { + init(): Promise; + + onDidChangeDefinition: (listener: Listener) => Disposable; + + loadLanguage(languageId: string): Promise; + + /** + * Get a language definition for the given language id, if the language + * has a new-style query definition, or return undefined if the language doesn't + * + * @param languageId The language id for which to get a language definition + * @returns A language definition for the given language id, or undefined if + * the given language id doesn't have a new-style query definition + */ + get(languageId: string): LanguageDefinition | undefined; + + /** + * @deprecated Only for use in legacy containing scope stage + */ + getNodeAtLocation( + document: TextDocument, + range: Range, + ): SyntaxNode | undefined; +} + /** * Keeps a map from language ids to {@link LanguageDefinition} instances, * constructing them as necessary */ -export class LanguageDefinitions { +export class LanguageDefinitionsImpl implements LanguageDefinitions { private notifier: Notifier = new Notifier(); /** @@ -42,34 +68,23 @@ export class LanguageDefinitions { string, LanguageDefinition | typeof LANGUAGE_UNDEFINED > = new Map(); - private queryDir: string; private disposables: Disposable[] = []; constructor( - private fileSystem?: FileSystem, - private treeSitter?: TreeSitter, + ide: IDE, + private provider: LanguageDefinitionsProvider, + private treeSitter: TreeSitter, ) { - ide().onDidOpenTextDocument((document) => { + ide.onDidOpenTextDocument((document) => { this.loadLanguage(document.languageId); }); - ide().onDidChangeVisibleTextEditors((editors) => { + ide.onDidChangeVisibleTextEditors((editors) => { editors.forEach(({ document }) => this.loadLanguage(document.languageId)); }); - // Use the repo root as the root for development mode, so that we can - // we can make hot-reloading work for the queries - this.queryDir = - ide().runMode === "development" - ? join(getCursorlessRepoRoot(), "queries") - : "queries"; - - if (fileSystem != null && ide().runMode === "development") { - this.disposables.push( - fileSystem.watchDir(this.queryDir, () => { - this.reloadLanguageDefinitions(); - }), - ); - } + this.disposables.push( + provider.onChanges(() => this.reloadLanguageDefinitions()), + ); } public async init(): Promise { @@ -103,14 +118,11 @@ export class LanguageDefinitions { } const definition = - (this.treeSitter != null && this.fileSystem != null - ? await LanguageDefinition.create( - this.treeSitter, - this.fileSystem, - this.queryDir, - languageId, - ) - : null) ?? LANGUAGE_UNDEFINED; + (await LanguageDefinition.create( + this.provider, + this.treeSitter, + languageId, + )) ?? LANGUAGE_UNDEFINED; this.languageDefinitions.set(languageId, definition); } @@ -121,14 +133,6 @@ export class LanguageDefinitions { this.notifier.notifyListeners(); } - /** - * Get a language definition for the given language id, if the language - * has a new-style query definition, or return undefined if the language doesn't - * - * @param languageId The language id for which to get a language definition - * @returns A language definition for the given language id, or undefined if - * the given language id doesn't have a new-style query definition - */ get(languageId: string): LanguageDefinition | undefined { const definition = this.languageDefinitions.get(languageId); @@ -142,16 +146,11 @@ export class LanguageDefinitions { return definition === LANGUAGE_UNDEFINED ? undefined : definition; } - /** - * @deprecated Only for use in legacy containing scope stage - */ public getNodeAtLocation( document: TextDocument, range: Range, ): SyntaxNode | undefined { - return this.treeSitter != null - ? this.treeSitter.getNodeAtLocation(document, range) - : undefined; + return this.treeSitter.getNodeAtLocation(document, range); } onDidChangeDefinition = this.notifier.registerListener; diff --git a/packages/cursorless-engine/src/runCommand.ts b/packages/cursorless-engine/src/runCommand.ts index 660ed3dfbb..c8a12b3888 100644 --- a/packages/cursorless-engine/src/runCommand.ts +++ b/packages/cursorless-engine/src/runCommand.ts @@ -33,7 +33,7 @@ import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandler * 5. Call {@link CommandRunnerImpl.run} to run the actual command. */ export async function runCommand( - treeSitter: TreeSitter | undefined, + treeSitter: TreeSitter, commandServerApi: CommandServerApi, debug: Debug, hatTokenMap: HatTokenMap, @@ -89,7 +89,7 @@ async function unwrapLegacyCommandResponse( } function createCommandRunner( - treeSitter: TreeSitter | undefined, + treeSitter: TreeSitter, commandServerApi: CommandServerApi, languageDefinitions: LanguageDefinitions, debug: Debug, diff --git a/packages/cursorless-engine/src/runIntegrationTests.ts b/packages/cursorless-engine/src/runIntegrationTests.ts index 429a1376e1..b2003e77d7 100644 --- a/packages/cursorless-engine/src/runIntegrationTests.ts +++ b/packages/cursorless-engine/src/runIntegrationTests.ts @@ -14,14 +14,14 @@ import { unsafeKeys } from "./util/object"; * @param languageDefinitions The language definitions instance */ export async function runIntegrationTests( - treeSitter: TreeSitter | undefined, + treeSitter: TreeSitter, languageDefinitions: LanguageDefinitions, ) { await assertNoScopesBothLegacyAndNew(treeSitter, languageDefinitions); } async function assertNoScopesBothLegacyAndNew( - treeSitter: TreeSitter | undefined, + treeSitter: TreeSitter, languageDefinitions: LanguageDefinitions, ) { const errors: string[] = []; diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index 7c279ba6ab..53ff15db2b 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -19,6 +19,7 @@ import { } from "@cursorless/cursorless-engine"; import { FileSystemCommandHistoryStorage, + FileSystemLanguageDefinitionsProvider, FileSystemTalonSpokenForms, } from "@cursorless/file-system-common"; import { @@ -86,8 +87,12 @@ export async function activate( ? fakeCommandServerApi : await getCommandServerApi(); - const treeSitter: TreeSitter = createTreeSitter(parseTreeApi); + const treeSitter = createTreeSitter(parseTreeApi); const talonSpokenForms = new FileSystemTalonSpokenForms(fileSystem); + const languageDefinitionsProvider = new FileSystemLanguageDefinitionsProvider( + normalizedIde, + fileSystem, + ); const snippets = new VscodeSnippets(normalizedIde); void snippets.init(); @@ -106,9 +111,9 @@ export async function activate( ide: normalizedIde, hats, commandServerApi, - fileSystem, talonSpokenForms, snippets, + languageDefinitionsProvider, }); const commandHistoryStorage = new FileSystemCommandHistoryStorage( diff --git a/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts b/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts new file mode 100644 index 0000000000..0e0b2b880f --- /dev/null +++ b/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts @@ -0,0 +1,48 @@ +import { + getCursorlessRepoRoot, + type Disposable, + type FileSystem, + type IDE, + type LanguageDefinitionsProvider, + Notifier, +} from "@cursorless/common"; +import path from "node:path"; + +export class FileSystemLanguageDefinitionsProvider + implements LanguageDefinitionsProvider +{ + private queryDir: string; + private notifier: Notifier = new Notifier(); + private disposables: Disposable[] = []; + + constructor( + ide: IDE, + private fileSystem: FileSystem, + ) { + // Use the repo root as the root for development mode, so that we can + // we can make hot-reloading work for the queries + this.queryDir = + ide.runMode === "development" + ? path.join(getCursorlessRepoRoot(), "queries") + : "queries"; + + if (ide.runMode === "development") { + this.disposables.push( + fileSystem.watchDir(this.queryDir, () => { + this.notifier.notifyListeners(); + }), + ); + } + } + + onChanges = this.notifier.registerListener; + + readQueryFile(filename: string): Promise { + const queryPath = path.join(this.queryDir, filename); + return this.fileSystem.readBundledFile(queryPath); + } + + dispose() { + this.disposables.forEach((disposable) => disposable.dispose()); + } +} diff --git a/packages/file-system-common/src/index.ts b/packages/file-system-common/src/index.ts index 5c217a785d..fd4d3b8470 100644 --- a/packages/file-system-common/src/index.ts +++ b/packages/file-system-common/src/index.ts @@ -1,2 +1,3 @@ export * from "./FileSystemTalonSpokenForms"; export * from "./FileSystemCommandHistoryStorage"; +export * from "./FileSystemLanguageDefinitionsProvider"; From bcbdfa59850db32cc563ac895a68793a2cc1ceb0 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 18:53:47 +0200 Subject: [PATCH 10/23] clean up --- .../cursorless-engine/src/cursorlessEngine.ts | 16 +++++----------- .../src/disabledComponents/DisabledTreeSitter.ts | 8 ++++---- packages/cursorless-vscode/src/extension.ts | 2 +- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index b8094adf84..728c31c839 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -63,20 +63,19 @@ export async function createCursorlessEngine({ injectIde(ide); const debug = new Debug(ide); - const rangeUpdater = new RangeUpdater(); + const storedTargets = new StoredTargetMap(); + const keyboardTargetUpdater = new KeyboardTargetUpdater(storedTargets); + const customSpokenFormGenerator = new CustomSpokenFormGeneratorImpl( + talonSpokenForms, + ); const hatTokenMap = hats != null ? new HatTokenMapImpl(rangeUpdater, debug, hats, commandServerApi) : new DisabledHatTokenMap(); - void hatTokenMap.allocateHats(); - const storedTargets = new StoredTargetMap(); - - const keyboardTargetUpdater = new KeyboardTargetUpdater(storedTargets); - const languageDefinitions = languageDefinitionsProvider != null ? new LanguageDefinitionsImpl( @@ -87,10 +86,6 @@ export async function createCursorlessEngine({ : new DisabledLanguageDefinitions(); await languageDefinitions.init(); - const customSpokenFormGenerator = new CustomSpokenFormGeneratorImpl( - talonSpokenForms, - ); - ide.disposeOnExit( rangeUpdater, languageDefinitions, @@ -100,7 +95,6 @@ export async function createCursorlessEngine({ ); const commandRunnerDecorators: CommandRunnerDecorator[] = []; - let previousCommand: Command | undefined = undefined; const runCommandClosure = (command: Command) => { diff --git a/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts b/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts index 480e4473f4..c9b10b37a2 100644 --- a/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts +++ b/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts @@ -11,11 +11,11 @@ export class DisabledTreeSitter implements TreeSitter { throw new Error("Tree sitter not provided"); } - getLanguage(_languageId: string): Language | undefined { - return undefined; - } - loadLanguage(_languageId: string): Promise { return Promise.resolve(false); } + + getLanguage(_languageId: string): Language | undefined { + throw new Error("Tree sitter not provided"); + } } diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index 53ff15db2b..e914d6fa7e 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -107,8 +107,8 @@ export async function activate( addCommandRunnerDecorator, customSpokenFormGenerator, } = await createCursorlessEngine({ - treeSitter, ide: normalizedIde, + treeSitter, hats, commandServerApi, talonSpokenForms, From 60f0584313ed5ac110ade066d778998931cb0312 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 18:59:58 +0200 Subject: [PATCH 11/23] tidy up error message --- .../cursorless-engine/src/spokenForms/CustomSpokenForms.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts b/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts index 845edc944e..e94385249a 100644 --- a/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts +++ b/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts @@ -84,12 +84,11 @@ export class CustomSpokenForms { this.needsInitialTalonUpdate_ = true; } else { console.error("Error loading custom spoken forms", err); + const msg = (err as Error).message.replace(/\.$/, ""); showError( ide().messages, "CustomSpokenForms.updateSpokenFormMaps", - `Error loading custom spoken forms: ${ - (err as Error).message - }}}. Falling back to default spoken forms.`, + `Error loading custom spoken forms: ${msg}. Falling back to default spoken forms.`, ); } From a46bfd576a74ca63fe750f693b37f8e5877c7205 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 19:01:54 +0200 Subject: [PATCH 12/23] clean up --- .../cursorless-engine/src/languages/LanguageDefinitions.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts index 4a5c491980..02b1dc6c6c 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts @@ -146,10 +146,7 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { return definition === LANGUAGE_UNDEFINED ? undefined : definition; } - public getNodeAtLocation( - document: TextDocument, - range: Range, - ): SyntaxNode | undefined { + public getNodeAtLocation(document: TextDocument, range: Range): SyntaxNode { return this.treeSitter.getNodeAtLocation(document, range); } From d13831928381716204900490237843f78a5bc247 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 19:03:51 +0200 Subject: [PATCH 13/23] Remove unused --- .../src/FileSystemTalonSpokenForms.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/file-system-common/src/FileSystemTalonSpokenForms.ts b/packages/file-system-common/src/FileSystemTalonSpokenForms.ts index 66272e5730..6215dbab03 100644 --- a/packages/file-system-common/src/FileSystemTalonSpokenForms.ts +++ b/packages/file-system-common/src/FileSystemTalonSpokenForms.ts @@ -81,17 +81,3 @@ export class FileSystemTalonSpokenForms implements TalonSpokenForms { function isErrnoException(error: any): error is NodeJS.ErrnoException { return error instanceof Error && "code" in error; } - -export class DisabledTalonSpokenFormsJsonReader implements TalonSpokenForms { - getSpokenFormEntries(): Promise { - return Promise.resolve([]); - } - - onDidChange(): Disposable { - return { - dispose() { - // Do nothing - }, - }; - } -} From 9a3575f2f561c06e1c753f71cfe622b95a784237 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 19:21:16 +0200 Subject: [PATCH 14/23] clean up --- packages/cursorless-engine/src/cursorlessEngine.ts | 8 ++++---- .../disabledComponents/DisabledCommandServerApi.ts | 12 ++++-------- .../src/FileSystemLanguageDefinitionsProvider.ts | 2 +- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index 728c31c839..15ffc56bfa 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -17,10 +17,12 @@ import { HatTokenMapImpl } from "./core/HatTokenMapImpl"; import type { Snippets } from "./core/Snippets"; import { StoredTargetMap } from "./core/StoredTargets"; import { RangeUpdater } from "./core/updateSelections/RangeUpdater"; -import { createDisabledCommandServerApi } from "./disabledComponents/DisabledCommandServerApi"; +import { DisabledCommandServerApi } from "./disabledComponents/DisabledCommandServerApi"; import { DisabledHatTokenMap } from "./disabledComponents/DisabledHatTokenMap"; +import { DisabledLanguageDefinitions } from "./disabledComponents/DisabledLanguageDefinitions"; import { DisabledSnippets } from "./disabledComponents/DisabledSnippets"; import { DisabledTalonSpokenForms } from "./disabledComponents/DisabledTalonSpokenForms"; +import { DisabledTreeSitter } from "./disabledComponents/DisabledTreeSitter"; import { CustomSpokenFormGeneratorImpl } from "./generateSpokenForm/CustomSpokenFormGeneratorImpl"; import { LanguageDefinitions, @@ -38,8 +40,6 @@ import { ScopeSupportWatcher } from "./scopeProviders/ScopeSupportWatcher"; import { type TalonSpokenForms } from "./scopeProviders/TalonSpokenForms"; import { injectIde } from "./singletons/ide.singleton"; import { TreeSitter } from "./typings/TreeSitter"; -import { DisabledLanguageDefinitions } from "./disabledComponents/DisabledLanguageDefinitions"; -import { DisabledTreeSitter } from "./disabledComponents/DisabledTreeSitter"; interface Props { ide: IDE; @@ -56,7 +56,7 @@ export async function createCursorlessEngine({ hats, languageDefinitionsProvider, treeSitter = new DisabledTreeSitter(), - commandServerApi = createDisabledCommandServerApi(), + commandServerApi = new DisabledCommandServerApi(), talonSpokenForms = new DisabledTalonSpokenForms(), snippets = new DisabledSnippets(), }: Props): Promise { diff --git a/packages/cursorless-engine/src/disabledComponents/DisabledCommandServerApi.ts b/packages/cursorless-engine/src/disabledComponents/DisabledCommandServerApi.ts index 0700cd51b3..63c3bf9946 100644 --- a/packages/cursorless-engine/src/disabledComponents/DisabledCommandServerApi.ts +++ b/packages/cursorless-engine/src/disabledComponents/DisabledCommandServerApi.ts @@ -1,19 +1,15 @@ import type { CommandServerApi } from "@cursorless/common"; -const DisabledCommandServerApi: CommandServerApi = { +export class DisabledCommandServerApi implements CommandServerApi { getFocusedElementType() { return Promise.resolve(undefined); - }, + } - signals: { + readonly signals = { prePhrase: { getVersion() { return Promise.resolve(null); }, }, - }, -}; - -export function createDisabledCommandServerApi() { - return DisabledCommandServerApi; + }; } diff --git a/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts b/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts index 0e0b2b880f..5dec75e7e8 100644 --- a/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts +++ b/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts @@ -6,7 +6,7 @@ import { type LanguageDefinitionsProvider, Notifier, } from "@cursorless/common"; -import path from "node:path"; +import path from "pathe"; export class FileSystemLanguageDefinitionsProvider implements LanguageDefinitionsProvider From aab995f55de3b82cecebc9f1049d58c29562a7e9 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 19:28:26 +0200 Subject: [PATCH 15/23] clean up --- .../ide/types/LanguageDefinitionsProvider.ts | 9 ++++++++- .../src/languages/LanguageDefinition.ts | 20 +++++++++++-------- .../src/languages/LanguageDefinitions.ts | 3 ++- .../FileSystemLanguageDefinitionsProvider.ts | 2 +- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/packages/common/src/ide/types/LanguageDefinitionsProvider.ts b/packages/common/src/ide/types/LanguageDefinitionsProvider.ts index 463aa8c63d..51b5cdca9f 100644 --- a/packages/common/src/ide/types/LanguageDefinitionsProvider.ts +++ b/packages/common/src/ide/types/LanguageDefinitionsProvider.ts @@ -1,6 +1,13 @@ import { Disposable } from "@cursorless/common"; export interface LanguageDefinitionsProvider { + /** + * Listen for changes to language definitions + */ onChanges(listener: () => void): Disposable; - readQueryFile(filename: string): Promise; + + /** + * Read a query definition. The query name is the name of one of our `.scm` files. + */ + readQuery(name: string): Promise; } diff --git a/packages/cursorless-engine/src/languages/LanguageDefinition.ts b/packages/cursorless-engine/src/languages/LanguageDefinition.ts index fc96229100..f5536d2a45 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinition.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinition.ts @@ -2,11 +2,11 @@ import { ScopeType, SimpleScopeType, showError, + type IDE, type LanguageDefinitionsProvider, } from "@cursorless/common"; import { dirname, join } from "pathe"; import { TreeSitterScopeHandler } from "../processTargets/modifiers/scopeHandlers"; -import { ide } from "../singletons/ide.singleton"; import { TreeSitter } from "../typings/TreeSitter"; import { matchAll } from "../util/regex"; import { TreeSitterQuery } from "./TreeSitterQuery"; @@ -36,11 +36,13 @@ export class LanguageDefinition { * id doesn't have a new-style query definition */ static async create( + ide: IDE, provider: LanguageDefinitionsProvider, treeSitter: TreeSitter, languageId: string, ): Promise { const rawLanguageQueryString = await readQueryFileAndImports( + ide, provider, `${languageId}.scm`, ); @@ -88,6 +90,7 @@ export class LanguageDefinition { * @returns The text of the query file, with all imports inlined */ async function readQueryFileAndImports( + ide: IDE, provider: LanguageDefinitionsProvider, languageFilename: string, ) { @@ -96,7 +99,7 @@ async function readQueryFileAndImports( [languageFilename]: null, }; - const doValidation = ide().runMode !== "production"; + const doValidation = ide.runMode !== "production"; // Keep reading imports until we've read all the imports. Every time we // encounter an import in a query file, we add it to the map with a value @@ -107,7 +110,7 @@ async function readQueryFileAndImports( continue; } - let rawQuery = await provider.readQueryFile(filename); + let rawQuery = await provider.readQuery(filename); if (rawQuery == null) { if (filename === languageFilename) { @@ -117,12 +120,12 @@ async function readQueryFileAndImports( } showError( - ide().messages, + ide.messages, "LanguageDefinition.readQueryFileAndImports.queryNotFound", `Could not find imported query file ${filename}`, ); - if (ide().runMode === "test") { + if (ide.runMode === "test") { throw new Error("Invalid import statement"); } @@ -149,7 +152,7 @@ async function readQueryFileAndImports( const relativeImportPath = match[1]; if (doValidation) { - validateImportSyntax(filename, relativeImportPath, match[0]); + validateImportSyntax(ide, filename, relativeImportPath, match[0]); } const importQueryPath = join(dirname(filename), relativeImportPath); @@ -164,6 +167,7 @@ async function readQueryFileAndImports( } function validateImportSyntax( + ide: IDE, file: string, relativeImportPath: string, actual: string, @@ -172,12 +176,12 @@ function validateImportSyntax( if (actual !== canonicalSyntax) { showError( - ide().messages, + ide.messages, "LanguageDefinition.readQueryFileAndImports.malformedImport", `Malformed import statement in ${file}: "${actual}". Import statements must be of the form "${canonicalSyntax}"`, ); - if (ide().runMode === "test") { + if (ide.runMode === "test") { throw new Error("Invalid import statement"); } } diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts index 02b1dc6c6c..73ec0b169c 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts @@ -71,7 +71,7 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { private disposables: Disposable[] = []; constructor( - ide: IDE, + private ide: IDE, private provider: LanguageDefinitionsProvider, private treeSitter: TreeSitter, ) { @@ -119,6 +119,7 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { const definition = (await LanguageDefinition.create( + this.ide, this.provider, this.treeSitter, languageId, diff --git a/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts b/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts index 5dec75e7e8..70f5c92fa4 100644 --- a/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts +++ b/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts @@ -37,7 +37,7 @@ export class FileSystemLanguageDefinitionsProvider onChanges = this.notifier.registerListener; - readQueryFile(filename: string): Promise { + readQuery(filename: string): Promise { const queryPath = path.join(this.queryDir, filename); return this.fileSystem.readBundledFile(queryPath); } From 7bd0e0506855eba1a469f3b9938a7639e4cb00f3 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 20:17:32 +0200 Subject: [PATCH 16/23] clean up --- .../src/disabledComponents/DisabledTreeSitter.ts | 8 ++++---- .../src/languages/LanguageDefinition.ts | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts b/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts index c9b10b37a2..2db6900b00 100644 --- a/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts +++ b/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts @@ -3,10 +3,6 @@ import type { SyntaxNode, Tree, Language } from "web-tree-sitter"; import type { TreeSitter } from "../typings/TreeSitter"; export class DisabledTreeSitter implements TreeSitter { - getNodeAtLocation(_document: TextDocument, _range: Range): SyntaxNode { - throw new Error("Tree sitter not provided"); - } - getTree(_document: TextDocument): Tree { throw new Error("Tree sitter not provided"); } @@ -18,4 +14,8 @@ export class DisabledTreeSitter implements TreeSitter { getLanguage(_languageId: string): Language | undefined { throw new Error("Tree sitter not provided"); } + + getNodeAtLocation(_document: TextDocument, _range: Range): SyntaxNode { + throw new Error("Tree sitter not provided"); + } } diff --git a/packages/cursorless-engine/src/languages/LanguageDefinition.ts b/packages/cursorless-engine/src/languages/LanguageDefinition.ts index f5536d2a45..87d9a89e85 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinition.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinition.ts @@ -105,15 +105,15 @@ async function readQueryFileAndImports( // encounter an import in a query file, we add it to the map with a value // of null, so that it will be read on the next iteration while (Object.values(rawQueryStrings).some((v) => v == null)) { - for (const [filename, rawQueryString] of Object.entries(rawQueryStrings)) { + for (const [queryName, rawQueryString] of Object.entries(rawQueryStrings)) { if (rawQueryString != null) { continue; } - let rawQuery = await provider.readQuery(filename); + let rawQuery = await provider.readQuery(queryName); if (rawQuery == null) { - if (filename === languageFilename) { + if (queryName === languageFilename) { // If this is the main query file, then we know that this language // just isn't defined using new-style queries return undefined; @@ -122,7 +122,7 @@ async function readQueryFileAndImports( showError( ide.messages, "LanguageDefinition.readQueryFileAndImports.queryNotFound", - `Could not find imported query file ${filename}`, + `Could not find imported query file ${queryName}`, ); if (ide.runMode === "test") { @@ -134,10 +134,10 @@ async function readQueryFileAndImports( } if (doValidation) { - validateQueryCaptures(filename, rawQuery); + validateQueryCaptures(queryName, rawQuery); } - rawQueryStrings[filename] = rawQuery; + rawQueryStrings[queryName] = rawQuery; matchAll( rawQuery, // Matches lines like: @@ -152,10 +152,10 @@ async function readQueryFileAndImports( const relativeImportPath = match[1]; if (doValidation) { - validateImportSyntax(ide, filename, relativeImportPath, match[0]); + validateImportSyntax(ide, queryName, relativeImportPath, match[0]); } - const importQueryPath = join(dirname(filename), relativeImportPath); + const importQueryPath = join(dirname(queryName), relativeImportPath); rawQueryStrings[importQueryPath] = rawQueryStrings[importQueryPath] ?? null; }, From 1c4146633b7714e5be559fb811e33c97c69a506e Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 20:18:26 +0200 Subject: [PATCH 17/23] rename --- .../cursorless-engine/src/languages/LanguageDefinition.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cursorless-engine/src/languages/LanguageDefinition.ts b/packages/cursorless-engine/src/languages/LanguageDefinition.ts index 87d9a89e85..da2a010056 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinition.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinition.ts @@ -92,11 +92,11 @@ export class LanguageDefinition { async function readQueryFileAndImports( ide: IDE, provider: LanguageDefinitionsProvider, - languageFilename: string, + languageQueryName: string, ) { // Seed the map with the query file itself const rawQueryStrings: Record = { - [languageFilename]: null, + [languageQueryName]: null, }; const doValidation = ide.runMode !== "production"; @@ -113,7 +113,7 @@ async function readQueryFileAndImports( let rawQuery = await provider.readQuery(queryName); if (rawQuery == null) { - if (queryName === languageFilename) { + if (queryName === languageQueryName) { // If this is the main query file, then we know that this language // just isn't defined using new-style queries return undefined; From b95c169dc330c0ec004a97a1854e95e356b46481 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Thu, 11 Jul 2024 19:25:32 +0100 Subject: [PATCH 18/23] tweaks --- packages/cursorless-engine/src/core/Debug.ts | 8 -------- .../src/core/HatTokenMapImpl.ts | 17 +++++++---------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/packages/cursorless-engine/src/core/Debug.ts b/packages/cursorless-engine/src/core/Debug.ts index f5ec1d09bb..91f341193c 100644 --- a/packages/cursorless-engine/src/core/Debug.ts +++ b/packages/cursorless-engine/src/core/Debug.ts @@ -5,7 +5,6 @@ import type { Disposable, IDE } from "@cursorless/common"; */ export class Debug { private disposableConfiguration?: Disposable; - private disposableSelection?: Disposable; active: boolean; constructor(private ide: IDE) { @@ -42,9 +41,6 @@ export class Debug { if (this.disposableConfiguration) { this.disposableConfiguration.dispose(); } - if (this.disposableSelection) { - this.disposableSelection.dispose(); - } } private enableDebugLog() { @@ -53,10 +49,6 @@ export class Debug { private disableDebugLog() { this.active = false; - if (this.disposableSelection) { - this.disposableSelection.dispose(); - this.disposableSelection = undefined; - } } private evaluateSetting() { diff --git a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts index f4c08fd7aa..3be6a51bbf 100644 --- a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts +++ b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts @@ -130,18 +130,15 @@ export class HatTokenMapImpl implements HatTokenMap { } private async maybeTakePrePhraseSnapshot() { - const phraseStartSignal = this.commandServerApi.signals.prePhrase; + const newSignalVersion = + await this.commandServerApi.signals.prePhrase.getVersion(); - if (phraseStartSignal != null) { - const newSignalVersion = await phraseStartSignal.getVersion(); + if (newSignalVersion !== this.lastSignalVersion) { + this.debug.log("taking snapshot"); + this.lastSignalVersion = newSignalVersion; - if (newSignalVersion !== this.lastSignalVersion) { - this.debug.log("taking snapshot"); - this.lastSignalVersion = newSignalVersion; - - if (newSignalVersion != null) { - this.takePrePhraseSnapshot(); - } + if (newSignalVersion != null) { + this.takePrePhraseSnapshot(); } } } From 7b84ebf5cf8217c9da2eca099180134a5040f033 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 21:34:54 +0200 Subject: [PATCH 19/23] Rename provider --- ...finitionsProvider.ts => RawTreeSitterQueryProvider.ts} | 2 +- packages/common/src/index.ts | 2 +- packages/cursorless-engine/src/cursorlessEngine.ts | 4 ++-- .../cursorless-engine/src/languages/LanguageDefinition.ts | 8 ++++---- .../src/languages/LanguageDefinitions.ts | 8 ++++---- .../src/FileSystemLanguageDefinitionsProvider.ts | 4 ++-- 6 files changed, 14 insertions(+), 14 deletions(-) rename packages/common/src/ide/types/{LanguageDefinitionsProvider.ts => RawTreeSitterQueryProvider.ts} (86%) diff --git a/packages/common/src/ide/types/LanguageDefinitionsProvider.ts b/packages/common/src/ide/types/RawTreeSitterQueryProvider.ts similarity index 86% rename from packages/common/src/ide/types/LanguageDefinitionsProvider.ts rename to packages/common/src/ide/types/RawTreeSitterQueryProvider.ts index 51b5cdca9f..c9f1a02b66 100644 --- a/packages/common/src/ide/types/LanguageDefinitionsProvider.ts +++ b/packages/common/src/ide/types/RawTreeSitterQueryProvider.ts @@ -1,6 +1,6 @@ import { Disposable } from "@cursorless/common"; -export interface LanguageDefinitionsProvider { +export interface RawTreeSitterQueryProvider { /** * Listen for changes to language definitions */ diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 88fd9727fc..b369884366 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -31,7 +31,7 @@ export * from "./ide/types/QuickPickOptions"; export * from "./ide/types/events.types"; export * from "./ide/types/Paths"; export * from "./ide/types/CommandHistoryStorage"; -export * from "./ide/types/LanguageDefinitionsProvider"; +export * from "./ide/types/RawTreeSitterQueryProvider"; export * from "./ide/types/FileSystem.types"; export * from "./types/RangeExpansionBehavior"; export * from "./types/InputBoxOptions"; diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index 15ffc56bfa..caf2aa1583 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -5,7 +5,7 @@ import { IDE, ScopeProvider, ensureCommandShape, - type LanguageDefinitionsProvider, + type RawTreeSitterQueryProvider, } from "@cursorless/common"; import { KeyboardTargetUpdater } from "./KeyboardTargetUpdater"; import { @@ -44,7 +44,7 @@ import { TreeSitter } from "./typings/TreeSitter"; interface Props { ide: IDE; hats?: Hats; - languageDefinitionsProvider?: LanguageDefinitionsProvider; + languageDefinitionsProvider?: RawTreeSitterQueryProvider; treeSitter?: TreeSitter; commandServerApi?: CommandServerApi; talonSpokenForms?: TalonSpokenForms; diff --git a/packages/cursorless-engine/src/languages/LanguageDefinition.ts b/packages/cursorless-engine/src/languages/LanguageDefinition.ts index da2a010056..8b1d320a09 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinition.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinition.ts @@ -3,7 +3,7 @@ import { SimpleScopeType, showError, type IDE, - type LanguageDefinitionsProvider, + type RawTreeSitterQueryProvider, } from "@cursorless/common"; import { dirname, join } from "pathe"; import { TreeSitterScopeHandler } from "../processTargets/modifiers/scopeHandlers"; @@ -37,13 +37,13 @@ export class LanguageDefinition { */ static async create( ide: IDE, - provider: LanguageDefinitionsProvider, + treeSitterQueryProvider: RawTreeSitterQueryProvider, treeSitter: TreeSitter, languageId: string, ): Promise { const rawLanguageQueryString = await readQueryFileAndImports( ide, - provider, + treeSitterQueryProvider, `${languageId}.scm`, ); @@ -91,7 +91,7 @@ export class LanguageDefinition { */ async function readQueryFileAndImports( ide: IDE, - provider: LanguageDefinitionsProvider, + provider: RawTreeSitterQueryProvider, languageQueryName: string, ) { // Seed the map with the query file itself diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts index 73ec0b169c..6d4aea1d03 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts @@ -6,7 +6,7 @@ import { isTesting, showError, type IDE, - type LanguageDefinitionsProvider, + type RawTreeSitterQueryProvider, type Listener, } from "@cursorless/common"; import { toString } from "lodash-es"; @@ -72,7 +72,7 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { constructor( private ide: IDE, - private provider: LanguageDefinitionsProvider, + private treeSitterQueryProvider: RawTreeSitterQueryProvider, private treeSitter: TreeSitter, ) { ide.onDidOpenTextDocument((document) => { @@ -83,7 +83,7 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { }); this.disposables.push( - provider.onChanges(() => this.reloadLanguageDefinitions()), + treeSitterQueryProvider.onChanges(() => this.reloadLanguageDefinitions()), ); } @@ -120,7 +120,7 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { const definition = (await LanguageDefinition.create( this.ide, - this.provider, + this.treeSitterQueryProvider, this.treeSitter, languageId, )) ?? LANGUAGE_UNDEFINED; diff --git a/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts b/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts index 70f5c92fa4..c76a7bcdf8 100644 --- a/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts +++ b/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts @@ -3,13 +3,13 @@ import { type Disposable, type FileSystem, type IDE, - type LanguageDefinitionsProvider, + type RawTreeSitterQueryProvider, Notifier, } from "@cursorless/common"; import path from "pathe"; export class FileSystemLanguageDefinitionsProvider - implements LanguageDefinitionsProvider + implements RawTreeSitterQueryProvider { private queryDir: string; private notifier: Notifier = new Notifier(); From 464847ccf62f8943baa3a90518eb1568157fe8b3 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 21:45:45 +0200 Subject: [PATCH 20/23] Split pr --- .../ide/types/RawTreeSitterQueryProvider.ts | 13 ---- packages/common/src/index.ts | 1 - .../cursorless-engine/src/cursorlessEngine.ts | 26 ++----- .../DisabledLanguageDefinitions.ts | 33 -------- .../disabledComponents/DisabledTreeSitter.ts | 21 ----- .../src/languages/LanguageDefinition.ts | 53 ++++++------- .../src/languages/LanguageDefinitions.ts | 78 +++++++++---------- packages/cursorless-vscode/src/extension.ts | 7 +- .../FileSystemLanguageDefinitionsProvider.ts | 48 ------------ packages/file-system-common/src/index.ts | 1 - 10 files changed, 72 insertions(+), 209 deletions(-) delete mode 100644 packages/common/src/ide/types/RawTreeSitterQueryProvider.ts delete mode 100644 packages/cursorless-engine/src/disabledComponents/DisabledLanguageDefinitions.ts delete mode 100644 packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts delete mode 100644 packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts diff --git a/packages/common/src/ide/types/RawTreeSitterQueryProvider.ts b/packages/common/src/ide/types/RawTreeSitterQueryProvider.ts deleted file mode 100644 index c9f1a02b66..0000000000 --- a/packages/common/src/ide/types/RawTreeSitterQueryProvider.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Disposable } from "@cursorless/common"; - -export interface RawTreeSitterQueryProvider { - /** - * Listen for changes to language definitions - */ - onChanges(listener: () => void): Disposable; - - /** - * Read a query definition. The query name is the name of one of our `.scm` files. - */ - readQuery(name: string): Promise; -} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index b369884366..a56e80ba5b 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -31,7 +31,6 @@ export * from "./ide/types/QuickPickOptions"; export * from "./ide/types/events.types"; export * from "./ide/types/Paths"; export * from "./ide/types/CommandHistoryStorage"; -export * from "./ide/types/RawTreeSitterQueryProvider"; export * from "./ide/types/FileSystem.types"; export * from "./types/RangeExpansionBehavior"; export * from "./types/InputBoxOptions"; diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index caf2aa1583..adb3fa7121 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -5,7 +5,7 @@ import { IDE, ScopeProvider, ensureCommandShape, - type RawTreeSitterQueryProvider, + type FileSystem, } from "@cursorless/common"; import { KeyboardTargetUpdater } from "./KeyboardTargetUpdater"; import { @@ -19,15 +19,10 @@ import { StoredTargetMap } from "./core/StoredTargets"; import { RangeUpdater } from "./core/updateSelections/RangeUpdater"; import { DisabledCommandServerApi } from "./disabledComponents/DisabledCommandServerApi"; import { DisabledHatTokenMap } from "./disabledComponents/DisabledHatTokenMap"; -import { DisabledLanguageDefinitions } from "./disabledComponents/DisabledLanguageDefinitions"; import { DisabledSnippets } from "./disabledComponents/DisabledSnippets"; import { DisabledTalonSpokenForms } from "./disabledComponents/DisabledTalonSpokenForms"; -import { DisabledTreeSitter } from "./disabledComponents/DisabledTreeSitter"; import { CustomSpokenFormGeneratorImpl } from "./generateSpokenForm/CustomSpokenFormGeneratorImpl"; -import { - LanguageDefinitions, - LanguageDefinitionsImpl, -} from "./languages/LanguageDefinitions"; +import { LanguageDefinitions } from "./languages/LanguageDefinitions"; import { ModifierStageFactoryImpl } from "./processTargets/ModifierStageFactoryImpl"; import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandlers"; import { runCommand } from "./runCommand"; @@ -44,8 +39,8 @@ import { TreeSitter } from "./typings/TreeSitter"; interface Props { ide: IDE; hats?: Hats; - languageDefinitionsProvider?: RawTreeSitterQueryProvider; - treeSitter?: TreeSitter; + treeSitter: TreeSitter; + fileSystem: FileSystem; commandServerApi?: CommandServerApi; talonSpokenForms?: TalonSpokenForms; snippets?: Snippets; @@ -54,8 +49,8 @@ interface Props { export async function createCursorlessEngine({ ide, hats, - languageDefinitionsProvider, - treeSitter = new DisabledTreeSitter(), + treeSitter, + fileSystem, commandServerApi = new DisabledCommandServerApi(), talonSpokenForms = new DisabledTalonSpokenForms(), snippets = new DisabledSnippets(), @@ -76,14 +71,7 @@ export async function createCursorlessEngine({ : new DisabledHatTokenMap(); void hatTokenMap.allocateHats(); - const languageDefinitions = - languageDefinitionsProvider != null - ? new LanguageDefinitionsImpl( - ide, - languageDefinitionsProvider, - treeSitter, - ) - : new DisabledLanguageDefinitions(); + const languageDefinitions = new LanguageDefinitions(fileSystem, treeSitter); await languageDefinitions.init(); ide.disposeOnExit( diff --git a/packages/cursorless-engine/src/disabledComponents/DisabledLanguageDefinitions.ts b/packages/cursorless-engine/src/disabledComponents/DisabledLanguageDefinitions.ts deleted file mode 100644 index cffda317f2..0000000000 --- a/packages/cursorless-engine/src/disabledComponents/DisabledLanguageDefinitions.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { TextDocument, Range, Listener } from "@cursorless/common"; -import type { SyntaxNode } from "web-tree-sitter"; -import type { LanguageDefinition } from "../languages/LanguageDefinition"; -import type { LanguageDefinitions } from "../languages/LanguageDefinitions"; - -export class DisabledLanguageDefinitions implements LanguageDefinitions { - init(): Promise { - return Promise.resolve(); - } - - onDidChangeDefinition(_listener: Listener) { - return { dispose: () => {} }; - } - - loadLanguage(_languageId: string): Promise { - return Promise.resolve(); - } - - get(_languageId: string): LanguageDefinition | undefined { - return undefined; - } - - getNodeAtLocation( - _document: TextDocument, - _range: Range, - ): SyntaxNode | undefined { - return undefined; - } - - dispose(): void { - // Do nothing - } -} diff --git a/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts b/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts deleted file mode 100644 index 2db6900b00..0000000000 --- a/packages/cursorless-engine/src/disabledComponents/DisabledTreeSitter.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { TextDocument, Range } from "@cursorless/common"; -import type { SyntaxNode, Tree, Language } from "web-tree-sitter"; -import type { TreeSitter } from "../typings/TreeSitter"; - -export class DisabledTreeSitter implements TreeSitter { - getTree(_document: TextDocument): Tree { - throw new Error("Tree sitter not provided"); - } - - loadLanguage(_languageId: string): Promise { - return Promise.resolve(false); - } - - getLanguage(_languageId: string): Language | undefined { - throw new Error("Tree sitter not provided"); - } - - getNodeAtLocation(_document: TextDocument, _range: Range): SyntaxNode { - throw new Error("Tree sitter not provided"); - } -} diff --git a/packages/cursorless-engine/src/languages/LanguageDefinition.ts b/packages/cursorless-engine/src/languages/LanguageDefinition.ts index 8b1d320a09..83252927d4 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinition.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinition.ts @@ -1,12 +1,12 @@ import { + FileSystem, ScopeType, SimpleScopeType, showError, - type IDE, - type RawTreeSitterQueryProvider, } from "@cursorless/common"; -import { dirname, join } from "pathe"; +import { basename, dirname, join } from "pathe"; import { TreeSitterScopeHandler } from "../processTargets/modifiers/scopeHandlers"; +import { ide } from "../singletons/ide.singleton"; import { TreeSitter } from "../typings/TreeSitter"; import { matchAll } from "../util/regex"; import { TreeSitterQuery } from "./TreeSitterQuery"; @@ -36,15 +36,16 @@ export class LanguageDefinition { * id doesn't have a new-style query definition */ static async create( - ide: IDE, - treeSitterQueryProvider: RawTreeSitterQueryProvider, treeSitter: TreeSitter, + fileSystem: FileSystem, + queryDir: string, languageId: string, ): Promise { + const languageQueryPath = join(queryDir, `${languageId}.scm`); + const rawLanguageQueryString = await readQueryFileAndImports( - ide, - treeSitterQueryProvider, - `${languageId}.scm`, + fileSystem, + languageQueryPath, ); if (rawLanguageQueryString == null) { @@ -90,42 +91,43 @@ export class LanguageDefinition { * @returns The text of the query file, with all imports inlined */ async function readQueryFileAndImports( - ide: IDE, - provider: RawTreeSitterQueryProvider, - languageQueryName: string, + fileSystem: FileSystem, + languageQueryPath: string, ) { // Seed the map with the query file itself const rawQueryStrings: Record = { - [languageQueryName]: null, + [languageQueryPath]: null, }; - const doValidation = ide.runMode !== "production"; + const doValidation = ide().runMode !== "production"; // Keep reading imports until we've read all the imports. Every time we // encounter an import in a query file, we add it to the map with a value // of null, so that it will be read on the next iteration while (Object.values(rawQueryStrings).some((v) => v == null)) { - for (const [queryName, rawQueryString] of Object.entries(rawQueryStrings)) { + for (const [queryPath, rawQueryString] of Object.entries(rawQueryStrings)) { if (rawQueryString != null) { continue; } - let rawQuery = await provider.readQuery(queryName); + const fileName = basename(queryPath); + + let rawQuery = await fileSystem.readBundledFile(queryPath); if (rawQuery == null) { - if (queryName === languageQueryName) { + if (queryPath === languageQueryPath) { // If this is the main query file, then we know that this language // just isn't defined using new-style queries return undefined; } showError( - ide.messages, + ide().messages, "LanguageDefinition.readQueryFileAndImports.queryNotFound", - `Could not find imported query file ${queryName}`, + `Could not find imported query file ${queryPath}`, ); - if (ide.runMode === "test") { + if (ide().runMode === "test") { throw new Error("Invalid import statement"); } @@ -134,10 +136,10 @@ async function readQueryFileAndImports( } if (doValidation) { - validateQueryCaptures(queryName, rawQuery); + validateQueryCaptures(fileName, rawQuery); } - rawQueryStrings[queryName] = rawQuery; + rawQueryStrings[queryPath] = rawQuery; matchAll( rawQuery, // Matches lines like: @@ -152,10 +154,10 @@ async function readQueryFileAndImports( const relativeImportPath = match[1]; if (doValidation) { - validateImportSyntax(ide, queryName, relativeImportPath, match[0]); + validateImportSyntax(fileName, relativeImportPath, match[0]); } - const importQueryPath = join(dirname(queryName), relativeImportPath); + const importQueryPath = join(dirname(queryPath), relativeImportPath); rawQueryStrings[importQueryPath] = rawQueryStrings[importQueryPath] ?? null; }, @@ -167,7 +169,6 @@ async function readQueryFileAndImports( } function validateImportSyntax( - ide: IDE, file: string, relativeImportPath: string, actual: string, @@ -176,12 +177,12 @@ function validateImportSyntax( if (actual !== canonicalSyntax) { showError( - ide.messages, + ide().messages, "LanguageDefinition.readQueryFileAndImports.malformedImport", `Malformed import statement in ${file}: "${actual}". Import statements must be of the form "${canonicalSyntax}"`, ); - if (ide.runMode === "test") { + if (ide().runMode === "test") { throw new Error("Invalid import statement"); } } diff --git a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts index 6d4aea1d03..4a205950b9 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinitions.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinitions.ts @@ -1,19 +1,19 @@ import { Disposable, + FileSystem, Notifier, Range, TextDocument, + getCursorlessRepoRoot, isTesting, showError, - type IDE, - type RawTreeSitterQueryProvider, - type Listener, } from "@cursorless/common"; -import { toString } from "lodash-es"; +import { join } from "pathe"; import { SyntaxNode } from "web-tree-sitter"; -import { ide } from "../singletons/ide.singleton"; import { TreeSitter } from "../typings/TreeSitter"; +import { ide } from "../singletons/ide.singleton"; import { LanguageDefinition } from "./LanguageDefinition"; +import { toString } from "lodash-es"; /** * Sentinel value to indicate that a language doesn't have @@ -21,37 +21,11 @@ import { LanguageDefinition } from "./LanguageDefinition"; */ const LANGUAGE_UNDEFINED = Symbol("LANGUAGE_UNDEFINED"); -export interface LanguageDefinitions extends Disposable { - init(): Promise; - - onDidChangeDefinition: (listener: Listener) => Disposable; - - loadLanguage(languageId: string): Promise; - - /** - * Get a language definition for the given language id, if the language - * has a new-style query definition, or return undefined if the language doesn't - * - * @param languageId The language id for which to get a language definition - * @returns A language definition for the given language id, or undefined if - * the given language id doesn't have a new-style query definition - */ - get(languageId: string): LanguageDefinition | undefined; - - /** - * @deprecated Only for use in legacy containing scope stage - */ - getNodeAtLocation( - document: TextDocument, - range: Range, - ): SyntaxNode | undefined; -} - /** * Keeps a map from language ids to {@link LanguageDefinition} instances, * constructing them as necessary */ -export class LanguageDefinitionsImpl implements LanguageDefinitions { +export class LanguageDefinitions { private notifier: Notifier = new Notifier(); /** @@ -68,23 +42,34 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { string, LanguageDefinition | typeof LANGUAGE_UNDEFINED > = new Map(); + private queryDir: string; private disposables: Disposable[] = []; constructor( - private ide: IDE, - private treeSitterQueryProvider: RawTreeSitterQueryProvider, + private fileSystem: FileSystem, private treeSitter: TreeSitter, ) { - ide.onDidOpenTextDocument((document) => { + ide().onDidOpenTextDocument((document) => { this.loadLanguage(document.languageId); }); - ide.onDidChangeVisibleTextEditors((editors) => { + ide().onDidChangeVisibleTextEditors((editors) => { editors.forEach(({ document }) => this.loadLanguage(document.languageId)); }); - this.disposables.push( - treeSitterQueryProvider.onChanges(() => this.reloadLanguageDefinitions()), - ); + // Use the repo root as the root for development mode, so that we can + // we can make hot-reloading work for the queries + this.queryDir = + ide().runMode === "development" + ? join(getCursorlessRepoRoot(), "queries") + : "queries"; + + if (ide().runMode === "development") { + this.disposables.push( + fileSystem.watchDir(this.queryDir, () => { + this.reloadLanguageDefinitions(); + }), + ); + } } public async init(): Promise { @@ -119,9 +104,9 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { const definition = (await LanguageDefinition.create( - this.ide, - this.treeSitterQueryProvider, this.treeSitter, + this.fileSystem, + this.queryDir, languageId, )) ?? LANGUAGE_UNDEFINED; @@ -134,6 +119,14 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { this.notifier.notifyListeners(); } + /** + * Get a language definition for the given language id, if the language + * has a new-style query definition, or return undefined if the language doesn't + * + * @param languageId The language id for which to get a language definition + * @returns A language definition for the given language id, or undefined if + * the given language id doesn't have a new-style query definition + */ get(languageId: string): LanguageDefinition | undefined { const definition = this.languageDefinitions.get(languageId); @@ -147,6 +140,9 @@ export class LanguageDefinitionsImpl implements LanguageDefinitions { return definition === LANGUAGE_UNDEFINED ? undefined : definition; } + /** + * @deprecated Only for use in legacy containing scope stage + */ public getNodeAtLocation(document: TextDocument, range: Range): SyntaxNode { return this.treeSitter.getNodeAtLocation(document, range); } diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index e914d6fa7e..ebe29500bd 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -19,7 +19,6 @@ import { } from "@cursorless/cursorless-engine"; import { FileSystemCommandHistoryStorage, - FileSystemLanguageDefinitionsProvider, FileSystemTalonSpokenForms, } from "@cursorless/file-system-common"; import { @@ -89,10 +88,6 @@ export async function activate( const treeSitter = createTreeSitter(parseTreeApi); const talonSpokenForms = new FileSystemTalonSpokenForms(fileSystem); - const languageDefinitionsProvider = new FileSystemLanguageDefinitionsProvider( - normalizedIde, - fileSystem, - ); const snippets = new VscodeSnippets(normalizedIde); void snippets.init(); @@ -113,7 +108,7 @@ export async function activate( commandServerApi, talonSpokenForms, snippets, - languageDefinitionsProvider, + fileSystem, }); const commandHistoryStorage = new FileSystemCommandHistoryStorage( diff --git a/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts b/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts deleted file mode 100644 index c76a7bcdf8..0000000000 --- a/packages/file-system-common/src/FileSystemLanguageDefinitionsProvider.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { - getCursorlessRepoRoot, - type Disposable, - type FileSystem, - type IDE, - type RawTreeSitterQueryProvider, - Notifier, -} from "@cursorless/common"; -import path from "pathe"; - -export class FileSystemLanguageDefinitionsProvider - implements RawTreeSitterQueryProvider -{ - private queryDir: string; - private notifier: Notifier = new Notifier(); - private disposables: Disposable[] = []; - - constructor( - ide: IDE, - private fileSystem: FileSystem, - ) { - // Use the repo root as the root for development mode, so that we can - // we can make hot-reloading work for the queries - this.queryDir = - ide.runMode === "development" - ? path.join(getCursorlessRepoRoot(), "queries") - : "queries"; - - if (ide.runMode === "development") { - this.disposables.push( - fileSystem.watchDir(this.queryDir, () => { - this.notifier.notifyListeners(); - }), - ); - } - } - - onChanges = this.notifier.registerListener; - - readQuery(filename: string): Promise { - const queryPath = path.join(this.queryDir, filename); - return this.fileSystem.readBundledFile(queryPath); - } - - dispose() { - this.disposables.forEach((disposable) => disposable.dispose()); - } -} diff --git a/packages/file-system-common/src/index.ts b/packages/file-system-common/src/index.ts index fd4d3b8470..5c217a785d 100644 --- a/packages/file-system-common/src/index.ts +++ b/packages/file-system-common/src/index.ts @@ -1,3 +1,2 @@ export * from "./FileSystemTalonSpokenForms"; export * from "./FileSystemCommandHistoryStorage"; -export * from "./FileSystemLanguageDefinitionsProvider"; From 14650645d845dc96751c50a5c8f27e0646ad6e98 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 21:50:53 +0200 Subject: [PATCH 21/23] clean up --- packages/cursorless-engine/src/cursorlessEngine.ts | 1 + .../src/processTargets/modifiers/surroundingPair/index.ts | 4 ++-- packages/cursorless-engine/src/runIntegrationTests.ts | 2 +- packages/cursorless-vscode/src/extension.ts | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index adb3fa7121..984e0bd3e9 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -83,6 +83,7 @@ export async function createCursorlessEngine({ ); const commandRunnerDecorators: CommandRunnerDecorator[] = []; + let previousCommand: Command | undefined = undefined; const runCommandClosure = (command: Command) => { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts b/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts index 635d9c1ba7..f81ef8ebab 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/surroundingPair/index.ts @@ -68,14 +68,14 @@ function processSurroundingPairCore( scopeType.delimiter as ComplexSurroundingPairName ] ?? [scopeType.delimiter]; - let node: SyntaxNode | undefined; + let node: SyntaxNode | null; try { node = languageDefinitions.getNodeAtLocation(document, range); // Error nodes are unreliable and should be ignored. Fall back to text based // algorithm. - if (node == null || nodeHasError(node)) { + if (nodeHasError(node)) { return findSurroundingPairTextBased( editor, range, diff --git a/packages/cursorless-engine/src/runIntegrationTests.ts b/packages/cursorless-engine/src/runIntegrationTests.ts index b2003e77d7..03aa06a2f0 100644 --- a/packages/cursorless-engine/src/runIntegrationTests.ts +++ b/packages/cursorless-engine/src/runIntegrationTests.ts @@ -26,7 +26,7 @@ async function assertNoScopesBothLegacyAndNew( ) { const errors: string[] = []; for (const languageId of legacyLanguageIds) { - await treeSitter?.loadLanguage(languageId); + await treeSitter.loadLanguage(languageId); await languageDefinitions.loadLanguage(languageId); unsafeKeys(languageMatchers[languageId] ?? {}).map((scopeTypeType) => { diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index ebe29500bd..1dda8d2528 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -106,9 +106,9 @@ export async function activate( treeSitter, hats, commandServerApi, + fileSystem, talonSpokenForms, snippets, - fileSystem, }); const commandHistoryStorage = new FileSystemCommandHistoryStorage( From 3ae67ca75c610c67f1b685f01a792321ddefdf0b Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 21:52:09 +0200 Subject: [PATCH 22/23] Clean up --- .../scopeTypeStages/LegacyContainingSyntaxScopeStage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts index c9d693efb4..9e7dde0ac0 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts @@ -38,7 +38,7 @@ export class LegacyContainingSyntaxScopeStage implements ModifierStage { this.modifier.type === "everyScope", ); - const node = this.languageDefinitions.getNodeAtLocation( + const node: SyntaxNode | null = this.languageDefinitions.getNodeAtLocation( target.editor.document, target.contentRange, ); From 71bf800455e033a9f81e8643923b3b3480d47364 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Thu, 11 Jul 2024 21:52:31 +0200 Subject: [PATCH 23/23] Cleanup --- .../scopeTypeStages/LegacyContainingSyntaxScopeStage.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts index 9e7dde0ac0..b099e10dca 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeTypeStages/LegacyContainingSyntaxScopeStage.ts @@ -43,10 +43,6 @@ export class LegacyContainingSyntaxScopeStage implements ModifierStage { target.contentRange, ); - if (node == null) { - throw new NoContainingScopeError(this.modifier.scopeType.type); - } - const scopeNodes = findNearestContainingAncestorNode(node, nodeMatcher, { editor: target.editor, selection: new Selection(