diff --git a/packages/common/src/ide/PassthroughIDEBase.ts b/packages/common/src/ide/PassthroughIDE.ts similarity index 81% rename from packages/common/src/ide/PassthroughIDEBase.ts rename to packages/common/src/ide/PassthroughIDE.ts index fc968e334e..204ee4050d 100644 --- a/packages/common/src/ide/PassthroughIDEBase.ts +++ b/packages/common/src/ide/PassthroughIDE.ts @@ -22,7 +22,7 @@ import type { KeyValueStore } from "./types/KeyValueStore"; import type { Messages } from "./types/Messages"; import type { QuickPickOptions } from "./types/QuickPickOptions"; -export default class PassthroughIDEBase implements IDE { +export class PassthroughIDE implements IDE { configuration: Configuration; keyValueStore: KeyValueStore; clipboard: Clipboard; @@ -37,6 +37,15 @@ export default class PassthroughIDEBase implements IDE { this.capabilities = original.capabilities; } + setIde(original: IDE) { + this.original = original; + this.configuration = original.configuration; + this.keyValueStore = original.keyValueStore; + this.clipboard = original.clipboard; + this.messages = original.messages; + this.capabilities = original.capabilities; + } + flashRanges(flashDescriptors: FlashDescriptor[]): Promise { return this.original.flashRanges(flashDescriptors); } @@ -112,68 +121,68 @@ export default class PassthroughIDEBase implements IDE { ); } - public get activeTextEditor(): TextEditor | undefined { + get activeTextEditor(): TextEditor | undefined { return this.original.activeTextEditor; } - public get activeEditableTextEditor(): EditableTextEditor | undefined { + get activeEditableTextEditor(): EditableTextEditor | undefined { return this.original.activeEditableTextEditor; } - public get visibleTextEditors(): TextEditor[] { + get visibleTextEditors(): TextEditor[] { return this.original.visibleTextEditors; } - public get visibleNotebookEditors(): NotebookEditor[] { + get visibleNotebookEditors(): NotebookEditor[] { return this.original.visibleNotebookEditors; } - public get cursorlessVersion(): string { + get cursorlessVersion(): string { return this.original.cursorlessVersion; } - public get assetsRoot(): string { + get assetsRoot(): string { return this.original.assetsRoot; } - public get runMode(): RunMode { + get runMode(): RunMode { return this.original.runMode; } - public get workspaceFolders(): readonly WorkspaceFolder[] | undefined { + get workspaceFolders(): readonly WorkspaceFolder[] | undefined { return this.original.workspaceFolders; } - public findInDocument(query: string, editor?: TextEditor): Promise { + findInDocument(query: string, editor?: TextEditor): Promise { return this.original.findInDocument(query, editor); } - public findInWorkspace(query: string): Promise { + findInWorkspace(query: string): Promise { return this.original.findInWorkspace(query); } - public openTextDocument(path: string): Promise { + openTextDocument(path: string): Promise { return this.original.openTextDocument(path); } - public openUntitledTextDocument( + openUntitledTextDocument( options?: OpenUntitledTextDocumentOptions, ): Promise { return this.original.openUntitledTextDocument(options); } - public showQuickPick( + showQuickPick( items: readonly string[], options?: QuickPickOptions, ): Promise { return this.original.showQuickPick(items, options); } - public showInputBox(options?: any): Promise { + showInputBox(options?: any): Promise { return this.original.showInputBox(options); } - public getEditableTextEditor(editor: TextEditor): EditableTextEditor { + getEditableTextEditor(editor: TextEditor): EditableTextEditor { return this.original.getEditableTextEditor(editor); } @@ -181,7 +190,7 @@ export default class PassthroughIDEBase implements IDE { return this.original.executeCommand(command, ...args); } - public onDidChangeTextDocument( + onDidChangeTextDocument( listener: (event: TextDocumentChangeEvent) => void, ): Disposable { return this.original.onDidChangeTextDocument(listener); diff --git a/packages/common/src/ide/normalized/NormalizedIDE.ts b/packages/common/src/ide/normalized/NormalizedIDE.ts index 08e9107eac..7f54a05bb4 100644 --- a/packages/common/src/ide/normalized/NormalizedIDE.ts +++ b/packages/common/src/ide/normalized/NormalizedIDE.ts @@ -3,12 +3,12 @@ import type { TextEditor } from "../../types/TextEditor"; import type FakeConfiguration from "../fake/FakeConfiguration"; import type FakeKeyValueStore from "../fake/FakeKeyValueStore"; import type { FakeIDE } from "../fake/FakeIDE"; -import PassthroughIDEBase from "../PassthroughIDEBase"; +import { PassthroughIDE } from "../PassthroughIDE"; import type { FlashDescriptor } from "../types/FlashDescriptor"; import type { IDE } from "../types/ide.types"; import type { QuickPickOptions } from "../types/QuickPickOptions"; -export class NormalizedIDE extends PassthroughIDEBase { +export class NormalizedIDE extends PassthroughIDE { configuration: FakeConfiguration; keyValueStore: FakeKeyValueStore; diff --git a/packages/common/src/ide/spy/SpyIDE.ts b/packages/common/src/ide/spy/SpyIDE.ts index 88326c1d2d..49013fea69 100644 --- a/packages/common/src/ide/spy/SpyIDE.ts +++ b/packages/common/src/ide/spy/SpyIDE.ts @@ -1,7 +1,7 @@ import { pickBy, values } from "lodash-es"; import type { GeneralizedRange } from "../../types/GeneralizedRange"; import type { TextEditor } from "../../types/TextEditor"; -import PassthroughIDEBase from "../PassthroughIDEBase"; +import { PassthroughIDE } from "../PassthroughIDE"; import type { FlashDescriptor } from "../types/FlashDescriptor"; import type { HighlightId, IDE } from "../types/ide.types"; import type { Message } from "./SpyMessages"; @@ -18,7 +18,7 @@ export interface SpyIDERecordedValues { highlights?: Highlight[]; } -export class SpyIDE extends PassthroughIDEBase { +export class SpyIDE extends PassthroughIDE { messages: SpyMessages; private flashes: FlashDescriptor[] = []; private highlights: Highlight[] = []; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index b4677d3ec1..b6223027a3 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -8,6 +8,7 @@ export * from "./FakeCommandServerApi"; export * from "./ide/fake/FakeIDE"; export * from "./ide/inMemoryTextDocument/InMemoryTextDocument"; export * from "./ide/normalized/NormalizedIDE"; +export * from "./ide/PassthroughIDE"; export * from "./ide/spy/SpyIDE"; export * from "./ide/spy/SpyMessages"; export * from "./ide/types/Capabilities"; diff --git a/packages/common/src/scopeVisualizerUtil/generateDecorationsForLineRange.ts b/packages/common/src/scopeVisualizerUtil/generateDecorationsForLineRange.ts index 930b258693..12c0ba25ed 100644 --- a/packages/common/src/scopeVisualizerUtil/generateDecorationsForLineRange.ts +++ b/packages/common/src/scopeVisualizerUtil/generateDecorationsForLineRange.ts @@ -1,5 +1,6 @@ import { Range } from "../types/Range"; -import { BorderStyle, type StyledRange } from "./decorationStyle.types"; +import type { StyledRange } from "./decorationStyle.types"; +import { BorderStyle } from "./decorationStyle.types"; export function* generateDecorationsForLineRange( startLine: number, diff --git a/packages/cursorless-engine/src/CommandHistoryAnalyzer.ts b/packages/cursorless-engine/src/CommandHistoryAnalyzer.ts index 8db17d2ca1..bec84a1106 100644 --- a/packages/cursorless-engine/src/CommandHistoryAnalyzer.ts +++ b/packages/cursorless-engine/src/CommandHistoryAnalyzer.ts @@ -1,6 +1,7 @@ import type { CommandHistoryEntry, CommandHistoryStorage, + IDE, Modifier, PartialPrimitiveTargetDescriptor, ScopeType, @@ -8,7 +9,6 @@ import type { import { showWarning } from "@cursorless/common"; import { groupBy, map, sum } from "lodash-es"; import { canonicalizeAndValidateCommand } from "./core/commandVersionUpgrades/canonicalizeAndValidateCommand"; -import { ide } from "./singletons/ide.singleton"; import { getPartialTargetDescriptors } from "./util/getPartialTargetDescriptors"; import { getPartialPrimitiveTargets } from "./util/getPrimitiveTargets"; import { getScopeType } from "./util/getScopeType"; @@ -116,6 +116,7 @@ function getMonth(entry: CommandHistoryEntry): string { } export async function analyzeCommandHistory( + ide: IDE, commandHistoryStorage: CommandHistoryStorage, ) { const entries = await commandHistoryStorage.getEntries(); @@ -123,7 +124,7 @@ export async function analyzeCommandHistory( if (entries.length === 0) { const TAKE_ME_THERE = "Show me"; const result = await showWarning( - ide().messages, + ide.messages, "noHistory", "No command history entries found. Please enable the command history in the settings.", TAKE_ME_THERE, @@ -131,7 +132,7 @@ export async function analyzeCommandHistory( if (result === TAKE_ME_THERE) { // FIXME: This is VSCode-specific - await ide().executeCommand( + await ide.executeCommand( "workbench.action.openSettings", "cursorless.commandHistory", ); @@ -148,7 +149,7 @@ export async function analyzeCommandHistory( ), ].join("\n\n\n"); - await ide().openUntitledTextDocument({ content }); + await ide.openUntitledTextDocument({ content }); } function toPercent(value: number) { diff --git a/packages/cursorless-engine/src/KeyboardTargetUpdater.ts b/packages/cursorless-engine/src/KeyboardTargetUpdater.ts index a2518b0086..4d548a57b8 100644 --- a/packages/cursorless-engine/src/KeyboardTargetUpdater.ts +++ b/packages/cursorless-engine/src/KeyboardTargetUpdater.ts @@ -52,7 +52,7 @@ export class KeyboardTargetUpdater { return; } - this.storedTargets.set("keyboard", new CursorStage().run()); + this.storedTargets.set("keyboard", new CursorStage(this.ide).run()); } dispose() { diff --git a/packages/cursorless-engine/src/actions/Actions.ts b/packages/cursorless-engine/src/actions/Actions.ts index 346f9ac0a6..78afdc17a2 100644 --- a/packages/cursorless-engine/src/actions/Actions.ts +++ b/packages/cursorless-engine/src/actions/Actions.ts @@ -1,4 +1,4 @@ -import type { TreeSitter } from "@cursorless/common"; +import type { IDE, TreeSitter } from "@cursorless/common"; import type { Snippets } from "../core/Snippets"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import type { ModifierStageFactory } from "../processTargets/ModifierStageFactory"; @@ -75,99 +75,108 @@ import { Decrement, Increment } from "./incrementDecrement"; */ export class Actions implements ActionRecord { constructor( + ide: IDE, treeSitter: TreeSitter, snippets: Snippets, rangeUpdater: RangeUpdater, modifierStageFactory: ModifierStageFactory, ) { - this.addSelection = new AddSelection(); - this.addSelectionBefore = new AddSelectionBefore(); - this.addSelectionAfter = new AddSelectionAfter(); + this.addSelection = new AddSelection(ide); + this.addSelectionBefore = new AddSelectionBefore(ide); + this.addSelectionAfter = new AddSelectionAfter(ide); this.callAsFunction = new Call(this); - this.clearAndSetSelection = new Clear(this); - this.copyToClipboard = new CopyToClipboard(this, rangeUpdater); - this.cutToClipboard = new CutToClipboard(this); + this.clearAndSetSelection = new Clear(ide, this); + this.copyToClipboard = new CopyToClipboard(ide, this, rangeUpdater); + this.cutToClipboard = new CutToClipboard(ide, this); this.decrement = new Decrement(this); - this.deselect = new Deselect(); - this.editNew = new EditNew(rangeUpdater, this); + this.deselect = new Deselect(ide); + this.editNew = new EditNew(ide, rangeUpdater, this); this.editNewLineAfter = new EditNewAfter(this, modifierStageFactory); this.editNewLineBefore = new EditNewBefore(this, modifierStageFactory); - this.executeCommand = new ExecuteCommand(rangeUpdater); - this.extractVariable = new ExtractVariable(rangeUpdater); - this.findInDocument = new FindInDocument(this); - this.findInWorkspace = new FindInWorkspace(this); - this.flashTargets = new FlashTargets(); - this.foldRegion = new Fold(rangeUpdater); - this.followLink = new FollowLink({ openAside: false }); - this.followLinkAside = new FollowLink({ openAside: true }); - this.generateSnippet = new GenerateSnippet(snippets); - this.getText = new GetText(); - this.gitAccept = new GitAccept(rangeUpdater); - this.gitRevert = new GitRevert(rangeUpdater); - this.gitStage = new GitStage(rangeUpdater); - this.gitUnstage = new GitUnstage(rangeUpdater); - this.highlight = new Highlight(); + this.executeCommand = new ExecuteCommand(ide, rangeUpdater); + this.extractVariable = new ExtractVariable(ide, rangeUpdater); + this.findInDocument = new FindInDocument(ide, this); + this.findInWorkspace = new FindInWorkspace(ide, this); + this.flashTargets = new FlashTargets(ide); + this.foldRegion = new Fold(ide, rangeUpdater); + this.followLink = new FollowLink(ide, { openAside: false }); + this.followLinkAside = new FollowLink(ide, { openAside: true }); + this.generateSnippet = new GenerateSnippet(ide, snippets); + this.getText = new GetText(ide); + this.gitAccept = new GitAccept(ide, rangeUpdater); + this.gitRevert = new GitRevert(ide, rangeUpdater); + this.gitStage = new GitStage(ide, rangeUpdater); + this.gitUnstage = new GitUnstage(ide, rangeUpdater); + this.highlight = new Highlight(ide); this.increment = new Increment(this); - this.indentLine = new IndentLine(rangeUpdater); + this.indentLine = new IndentLine(ide, rangeUpdater); this.insertCopyAfter = new InsertCopyAfter( + ide, rangeUpdater, modifierStageFactory, ); this.insertCopyBefore = new InsertCopyBefore( + ide, rangeUpdater, modifierStageFactory, ); this.insertEmptyLineAfter = new InsertEmptyLineAfter( + ide, rangeUpdater, modifierStageFactory, ); this.insertEmptyLineBefore = new InsertEmptyLineBefore( + ide, rangeUpdater, modifierStageFactory, ); this.insertEmptyLinesAround = new InsertEmptyLinesAround( + ide, rangeUpdater, modifierStageFactory, ); this.insertSnippet = new InsertSnippet( + ide, rangeUpdater, this, modifierStageFactory, ); - this.joinLines = new JoinLines(rangeUpdater, modifierStageFactory); - this.breakLine = new BreakLine(rangeUpdater); - this.moveToTarget = new Move(rangeUpdater); - this.outdentLine = new OutdentLine(rangeUpdater); - this.pasteFromClipboard = new PasteFromClipboard(rangeUpdater, this); - this.randomizeTargets = new Random(this); - this.remove = new Remove(rangeUpdater); - this.rename = new Rename(rangeUpdater); - this.replace = new Replace(rangeUpdater); - this.replaceWithTarget = new Bring(rangeUpdater); - this.revealDefinition = new RevealDefinition(rangeUpdater); - this.revealTypeDefinition = new RevealTypeDefinition(rangeUpdater); - this.reverseTargets = new Reverse(this); + this.joinLines = new JoinLines(ide, rangeUpdater, modifierStageFactory); + this.breakLine = new BreakLine(ide, rangeUpdater); + this.moveToTarget = new Move(ide, rangeUpdater); + this.outdentLine = new OutdentLine(ide, rangeUpdater); + this.pasteFromClipboard = new PasteFromClipboard(ide, rangeUpdater, this); + this.randomizeTargets = new Random(ide, this); + this.remove = new Remove(ide, rangeUpdater); + this.rename = new Rename(ide, rangeUpdater); + this.replace = new Replace(ide, rangeUpdater); + this.replaceWithTarget = new Bring(ide, rangeUpdater); + this.revealDefinition = new RevealDefinition(ide, rangeUpdater); + this.revealTypeDefinition = new RevealTypeDefinition(ide, rangeUpdater); + this.reverseTargets = new Reverse(ide, this); this.rewrapWithPairedDelimiter = new Rewrap( + ide, rangeUpdater, modifierStageFactory, ); - this.scrollToBottom = new ScrollToBottom(); - this.scrollToCenter = new ScrollToCenter(); - this.scrollToTop = new ScrollToTop(); - this.setSelection = new SetSelection(); - this.setSelectionAfter = new SetSelectionAfter(); - this.setSelectionBefore = new SetSelectionBefore(); - this.showDebugHover = new ShowDebugHover(rangeUpdater); - this.showHover = new ShowHover(rangeUpdater); - this.showQuickFix = new ShowQuickFix(rangeUpdater); - this.showReferences = new ShowReferences(rangeUpdater); - this.sortTargets = new Sort(this); - this.swapTargets = new Swap(rangeUpdater); - this.toggleLineBreakpoint = new ToggleBreakpoint(modifierStageFactory); - this.toggleLineComment = new ToggleLineComment(rangeUpdater); - this.unfoldRegion = new Unfold(rangeUpdater); - this.wrapWithPairedDelimiter = new Wrap(rangeUpdater); + this.scrollToBottom = new ScrollToBottom(ide); + this.scrollToCenter = new ScrollToCenter(ide); + this.scrollToTop = new ScrollToTop(ide); + this.setSelection = new SetSelection(ide); + this.setSelectionAfter = new SetSelectionAfter(ide); + this.setSelectionBefore = new SetSelectionBefore(ide); + this.showDebugHover = new ShowDebugHover(ide, rangeUpdater); + this.showHover = new ShowHover(ide, rangeUpdater); + this.showQuickFix = new ShowQuickFix(ide, rangeUpdater); + this.showReferences = new ShowReferences(ide, rangeUpdater); + this.sortTargets = new Sort(ide, this); + this.swapTargets = new Swap(ide, rangeUpdater); + this.toggleLineBreakpoint = new ToggleBreakpoint(ide, modifierStageFactory); + this.toggleLineComment = new ToggleLineComment(ide, rangeUpdater); + this.unfoldRegion = new Unfold(ide, rangeUpdater); + this.wrapWithPairedDelimiter = new Wrap(ide, rangeUpdater); this.wrapWithSnippet = new WrapWithSnippet( + ide, rangeUpdater, modifierStageFactory, ); @@ -176,7 +185,7 @@ export class Actions implements ActionRecord { "instanceReference", ); - this["private.showParseTree"] = new ShowParseTree(treeSitter); + this["private.showParseTree"] = new ShowParseTree(ide, treeSitter); this["private.getTargets"] = new GetTargets(); this["private.setKeyboardTarget"] = new SetSpecialTarget("keyboard"); } diff --git a/packages/cursorless-engine/src/actions/BreakLine.ts b/packages/cursorless-engine/src/actions/BreakLine.ts index 479042fb8a..4cd6611484 100644 --- a/packages/cursorless-engine/src/actions/BreakLine.ts +++ b/packages/cursorless-engine/src/actions/BreakLine.ts @@ -1,26 +1,28 @@ -import type { Edit, TextEditor } from "@cursorless/common"; +import type { Edit, IDE, TextEditor } from "@cursorless/common"; import { FlashStyle, Position, Range } from "@cursorless/common"; import { flatten, zip } from "lodash-es"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { flashTargets, runOnTargetsForEachEditor } from "../util/targetUtils"; import type { ActionReturnValue } from "./actions.types"; export class BreakLine { - constructor(private rangeUpdater: RangeUpdater) { + constructor( + private ide: IDE, + private rangeUpdater: RangeUpdater, + ) { this.run = this.run.bind(this); } async run(targets: Target[]): Promise { - await flashTargets(ide(), targets, FlashStyle.pendingModification0); + await flashTargets(this.ide, targets, FlashStyle.pendingModification0); const thatSelections = flatten( await runOnTargetsForEachEditor(targets, async (editor, targets) => { const contentRanges = targets.map(({ contentRange }) => contentRange); const edits = getEdits(editor, contentRanges); - const editableEditor = ide().getEditableTextEditor(editor); + const editableEditor = this.ide.getEditableTextEditor(editor); const { contentRanges: updatedRanges } = await performEditsAndUpdateSelections({ diff --git a/packages/cursorless-engine/src/actions/BringMoveSwap.ts b/packages/cursorless-engine/src/actions/BringMoveSwap.ts index 5bf7224620..3e55800bc2 100644 --- a/packages/cursorless-engine/src/actions/BringMoveSwap.ts +++ b/packages/cursorless-engine/src/actions/BringMoveSwap.ts @@ -1,5 +1,6 @@ import type { GeneralizedRange, + IDE, Range, Selection, TextEditor, @@ -8,7 +9,6 @@ import { FlashStyle, RangeExpansionBehavior } from "@cursorless/common"; import { flatten } from "lodash-es"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; -import { ide } from "../singletons/ide.singleton"; import type { EditWithRangeUpdater } from "../typings/Types"; import type { Destination, Target } from "../typings/target.types"; import { @@ -43,6 +43,7 @@ abstract class BringMoveSwap { }; constructor( + private ide: IDE, private rangeUpdater: RangeUpdater, private type: ActionType, ) {} @@ -50,12 +51,12 @@ abstract class BringMoveSwap { protected async decorateTargets(sources: Target[], destinations: Target[]) { await Promise.all([ flashTargets( - ide(), + this.ide, sources, this.decoration.sourceStyle, this.decoration.getSourceRangeCallback, ), - flashTargets(ide(), destinations, this.decoration.destinationStyle), + flashTargets(this.ide, destinations, this.decoration.destinationStyle), ]); } @@ -172,7 +173,7 @@ abstract class BringMoveSwap { const destinationEditRanges = destinationEdits.map( ({ edit }) => edit.range, ); - const editableEditor = ide().getEditableTextEditor(editor); + const editableEditor = this.ide.getEditableTextEditor(editor); const { sourceEditRanges: updatedSourceEditRanges, @@ -235,13 +236,13 @@ abstract class BringMoveSwap { }; return Promise.all([ flashTargets( - ide(), + this.ide, thatMark.filter(({ isSource }) => isSource).map(({ target }) => target), this.decoration.sourceStyle, getRange, ), flashTargets( - ide(), + this.ide, thatMark .filter(({ isSource }) => !isSource) .map(({ target }) => target), @@ -274,8 +275,8 @@ export class Bring extends BringMoveSwap { destinationStyle: FlashStyle.pendingModification0, }; - constructor(rangeUpdater: RangeUpdater) { - super(rangeUpdater, "bring"); + constructor(ide: IDE, rangeUpdater: RangeUpdater) { + super(ide, rangeUpdater, "bring"); this.run = this.run.bind(this); } @@ -309,8 +310,8 @@ export class Move extends BringMoveSwap { getSourceRangeCallback: getRemovalHighlightRange, }; - constructor(rangeUpdater: RangeUpdater) { - super(rangeUpdater, "move"); + constructor(ide: IDE, rangeUpdater: RangeUpdater) { + super(ide, rangeUpdater, "move"); this.run = this.run.bind(this); } @@ -343,8 +344,8 @@ export class Swap extends BringMoveSwap { destinationStyle: FlashStyle.pendingModification0, }; - constructor(rangeUpdater: RangeUpdater) { - super(rangeUpdater, "swap"); + constructor(ide: IDE, rangeUpdater: RangeUpdater) { + super(ide, rangeUpdater, "swap"); this.run = this.run.bind(this); } diff --git a/packages/cursorless-engine/src/actions/CallbackAction.ts b/packages/cursorless-engine/src/actions/CallbackAction.ts index f675e52b37..f662e65de9 100644 --- a/packages/cursorless-engine/src/actions/CallbackAction.ts +++ b/packages/cursorless-engine/src/actions/CallbackAction.ts @@ -1,10 +1,9 @@ -import type { EditableTextEditor, TextEditor } from "@cursorless/common"; +import type { EditableTextEditor, IDE, TextEditor } from "@cursorless/common"; import { FlashStyle } from "@cursorless/common"; import { flatten } from "lodash-es"; import { selectionToStoredTarget } from "../core/commandRunner/selectionToStoredTarget"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { ensureSingleEditor, @@ -30,7 +29,10 @@ interface CallbackOptions { * each editor, receiving all the targets that are in the given editor. */ export class CallbackAction { - constructor(private rangeUpdater: RangeUpdater) { + constructor( + private ide: IDE, + private rangeUpdater: RangeUpdater, + ) { this.run = this.run.bind(this); } @@ -39,7 +41,7 @@ export class CallbackAction { options: CallbackOptions, ): Promise { if (options.showDecorations) { - await flashTargets(ide(), targets, FlashStyle.referenced); + await flashTargets(this.ide, targets, FlashStyle.referenced); } if (options.ensureSingleEditor) { @@ -50,7 +52,7 @@ export class CallbackAction { ensureSingleTarget(targets); } - const originalEditor = ide().activeEditableTextEditor; + const originalEditor = this.ide.activeEditableTextEditor; // If we are relying on selections we have to wait for one editor to finish // before moving the selection to the next @@ -85,7 +87,7 @@ export class CallbackAction { editor: TextEditor, targets: Target[], ): Promise { - const editableEditor = ide().getEditableTextEditor(editor); + const editableEditor = this.ide.getEditableTextEditor(editor); const originalSelections = editor.selections; const originalEditorVersion = editor.document.version; const targetSelections = targets.map((target) => target.contentSelection); diff --git a/packages/cursorless-engine/src/actions/Clear.ts b/packages/cursorless-engine/src/actions/Clear.ts index 2de38e8222..f4812a79b7 100644 --- a/packages/cursorless-engine/src/actions/Clear.ts +++ b/packages/cursorless-engine/src/actions/Clear.ts @@ -1,12 +1,15 @@ +import type { IDE } from "@cursorless/common"; import { PlainTarget } from "../processTargets/targets"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { ensureSingleEditor } from "../util/targetUtils"; import type { Actions } from "./Actions"; import type { SimpleAction, ActionReturnValue } from "./actions.types"; export default class Clear implements SimpleAction { - constructor(private actions: Actions) { + constructor( + private ide: IDE, + private actions: Actions, + ) { this.run = this.run.bind(this); } @@ -26,12 +29,10 @@ export default class Clear implements SimpleAction { const { thatTargets } = await this.actions.remove.run(plainTargets); if (thatTargets != null) { - await ide() - .getEditableTextEditor(editor) - .setSelections( - thatTargets.map(({ contentSelection }) => contentSelection), - { focusEditor: true }, - ); + await this.ide.getEditableTextEditor(editor).setSelections( + thatTargets.map(({ contentSelection }) => contentSelection), + { focusEditor: true }, + ); } return { thatTargets }; diff --git a/packages/cursorless-engine/src/actions/CopyToClipboard.ts b/packages/cursorless-engine/src/actions/CopyToClipboard.ts index c16d93236d..efe03b072a 100644 --- a/packages/cursorless-engine/src/actions/CopyToClipboard.ts +++ b/packages/cursorless-engine/src/actions/CopyToClipboard.ts @@ -1,10 +1,10 @@ +import type { IDE } from "@cursorless/common"; import { FlashStyle } from "@cursorless/common"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; -import { ide } from "../singletons/ide.singleton"; +import { CopyToClipboardSimple } from "./SimpleIdeCommandActions"; import type { Target } from "../typings/target.types"; import { flashTargets } from "../util/targetUtils"; import type { Actions } from "./Actions"; -import { CopyToClipboardSimple } from "./SimpleIdeCommandActions"; import type { ActionReturnValue, SimpleAction } from "./actions.types"; interface Options { @@ -13,6 +13,7 @@ interface Options { export class CopyToClipboard implements SimpleAction { constructor( + private ide: IDE, private actions: Actions, private rangeUpdater: RangeUpdater, ) { @@ -23,20 +24,23 @@ export class CopyToClipboard implements SimpleAction { targets: Target[], options: Options = { showDecorations: true }, ): Promise { - if (ide().capabilities.commands.clipboardCopy != null) { - const simpleAction = new CopyToClipboardSimple(this.rangeUpdater); + if (this.ide.capabilities.commands.clipboardCopy != null) { + const simpleAction = new CopyToClipboardSimple( + this.ide, + this.rangeUpdater, + ); return simpleAction.run(targets, options); } if (options.showDecorations) { - await flashTargets(ide(), targets, FlashStyle.referenced); + await flashTargets(this.ide, targets, FlashStyle.referenced); } // FIXME: We should really keep track of the number of targets from the // original copy, as is done in VSCode. const text = targets.map((t) => t.contentText).join("\n"); - await ide().clipboard.writeText(text); + await this.ide.clipboard.writeText(text); return { thatTargets: targets }; } diff --git a/packages/cursorless-engine/src/actions/CutToClipboard.ts b/packages/cursorless-engine/src/actions/CutToClipboard.ts index a9c4f6f7b9..9b5963ba85 100644 --- a/packages/cursorless-engine/src/actions/CutToClipboard.ts +++ b/packages/cursorless-engine/src/actions/CutToClipboard.ts @@ -1,17 +1,19 @@ -import type { CharacterRange, FlashDescriptor } from "@cursorless/common"; +import type { CharacterRange, FlashDescriptor, IDE } from "@cursorless/common"; import { FlashStyle, Range, toCharacterRange } from "@cursorless/common"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import type { Actions } from "./Actions"; import type { ActionReturnValue, SimpleAction } from "./actions.types"; export class CutToClipboard implements SimpleAction { - constructor(private actions: Actions) { + constructor( + private ide: IDE, + private actions: Actions, + ) { this.run = this.run.bind(this); } async run(targets: Target[]): Promise { - await ide().flashRanges(targets.flatMap(getFlashDescriptors)); + await this.ide.flashRanges(targets.flatMap(getFlashDescriptors)); const options = { showDecorations: false }; diff --git a/packages/cursorless-engine/src/actions/Deselect.ts b/packages/cursorless-engine/src/actions/Deselect.ts index 1848f8d5c0..15968d1bb5 100644 --- a/packages/cursorless-engine/src/actions/Deselect.ts +++ b/packages/cursorless-engine/src/actions/Deselect.ts @@ -1,10 +1,10 @@ -import { ide } from "../singletons/ide.singleton"; +import type { IDE } from "@cursorless/common"; import type { Target } from "../typings/target.types"; import { runOnTargetsForEachEditor } from "../util/targetUtils"; import type { SimpleAction, ActionReturnValue } from "./actions.types"; export default class Deselect implements SimpleAction { - constructor() { + constructor(private ide: IDE) { this.run = this.run.bind(this); } @@ -23,7 +23,7 @@ export default class Deselect implements SimpleAction { throw new SelectionRequiredError(); } - await ide().getEditableTextEditor(editor).setSelections(newSelections); + await this.ide.getEditableTextEditor(editor).setSelections(newSelections); }); return { diff --git a/packages/cursorless-engine/src/actions/EditNew/EditNew.ts b/packages/cursorless-engine/src/actions/EditNew/EditNew.ts index bb0d150b17..e9cef44d17 100644 --- a/packages/cursorless-engine/src/actions/EditNew/EditNew.ts +++ b/packages/cursorless-engine/src/actions/EditNew/EditNew.ts @@ -1,5 +1,5 @@ +import type { IDE } from "@cursorless/common"; import type { RangeUpdater } from "../../core/updateSelections/RangeUpdater"; -import { ide } from "../../singletons/ide.singleton"; import type { Destination } from "../../typings/target.types"; import { createThatMark, ensureSingleEditor } from "../../util/targetUtils"; import type { Actions } from "../Actions"; @@ -11,6 +11,7 @@ import { runEditNewNotebookCellTargets } from "./runNotebookCellTargets"; export class EditNew { constructor( + private ide: IDE, private rangeUpdater: RangeUpdater, private actions: Actions, ) { @@ -22,10 +23,14 @@ export class EditNew { // It is not possible to "pour" a notebook cell and something else, // because each notebook cell is its own editor, and you can't have // cursors in multiple editors. - return runEditNewNotebookCellTargets(this.actions, destinations); + return runEditNewNotebookCellTargets( + this.ide, + this.actions, + destinations, + ); } - const editableEditor = ide().getEditableTextEditor( + const editableEditor = this.ide.getEditableTextEditor( ensureSingleEditor(destinations), ); @@ -45,7 +50,7 @@ export class EditNew { }; const insertLineAfterCapability = - ide().capabilities.commands.insertLineAfter; + this.ide.capabilities.commands.insertLineAfter; const useInsertLineAfter = insertLineAfterCapability != null; if (useInsertLineAfter) { diff --git a/packages/cursorless-engine/src/actions/EditNew/runNotebookCellTargets.ts b/packages/cursorless-engine/src/actions/EditNew/runNotebookCellTargets.ts index f1038a888f..9a51a606ec 100644 --- a/packages/cursorless-engine/src/actions/EditNew/runNotebookCellTargets.ts +++ b/packages/cursorless-engine/src/actions/EditNew/runNotebookCellTargets.ts @@ -1,17 +1,18 @@ -import { ide } from "../../singletons/ide.singleton"; +import type { IDE } from "@cursorless/common"; import type { Destination } from "../../typings/target.types"; import { createThatMark, ensureSingleTarget } from "../../util/targetUtils"; import type { Actions } from "../Actions"; import type { ActionReturnValue } from "../actions.types"; export async function runEditNewNotebookCellTargets( + ide: IDE, actions: Actions, destinations: Destination[], ): Promise { // Can only run on one target because otherwise we'd end up with cursors in // multiple cells, which is unsupported in VSCode const destination = ensureSingleTarget(destinations); - const editor = ide().getEditableTextEditor(destination.editor); + const editor = ide.getEditableTextEditor(destination.editor); const isAbove = destination.insertionMode === "before"; if (destination.insertionMode === "to") { diff --git a/packages/cursorless-engine/src/actions/ExecuteCommand.ts b/packages/cursorless-engine/src/actions/ExecuteCommand.ts index 403cf160d1..0435aa6b9a 100644 --- a/packages/cursorless-engine/src/actions/ExecuteCommand.ts +++ b/packages/cursorless-engine/src/actions/ExecuteCommand.ts @@ -1,6 +1,5 @@ -import type { ExecuteCommandOptions } from "@cursorless/common"; +import type { ExecuteCommandOptions, IDE } from "@cursorless/common"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { CallbackAction } from "./CallbackAction"; import type { ActionReturnValue } from "./actions.types"; @@ -15,8 +14,11 @@ import type { ActionReturnValue } from "./actions.types"; export default class ExecuteCommand { private callbackAction: CallbackAction; - constructor(rangeUpdater: RangeUpdater) { - this.callbackAction = new CallbackAction(rangeUpdater); + constructor( + private ide: IDE, + rangeUpdater: RangeUpdater, + ) { + this.callbackAction = new CallbackAction(ide, rangeUpdater); this.run = this.run.bind(this); } @@ -34,7 +36,7 @@ export default class ExecuteCommand { const args = commandArgs ?? []; return this.callbackAction.run(targets, { - callback: () => ide().executeCommand(commandId, ...args), + callback: () => this.ide.executeCommand(commandId, ...args), setSelection: true, ensureSingleEditor: ensureSingleEditor ?? false, ensureSingleTarget: ensureSingleTarget ?? false, diff --git a/packages/cursorless-engine/src/actions/Find.ts b/packages/cursorless-engine/src/actions/Find.ts index c0d0f4e734..79ddea9210 100644 --- a/packages/cursorless-engine/src/actions/Find.ts +++ b/packages/cursorless-engine/src/actions/Find.ts @@ -1,12 +1,15 @@ +import type { IDE } from "@cursorless/common"; import { showWarning } from "@cursorless/common"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { ensureSingleTarget } from "../util/targetUtils"; import type { Actions } from "./Actions"; import type { ActionReturnValue, SimpleAction } from "./actions.types"; abstract class Find implements SimpleAction { - constructor(private actions: Actions) { + constructor( + protected ide: IDE, + private actions: Actions, + ) { this.run = this.run.bind(this); } @@ -21,7 +24,7 @@ abstract class Find implements SimpleAction { if (text.length > 200) { query = text.substring(0, 200); void showWarning( - ide().messages, + this.ide.messages, "truncatedSearchText", "Search text is longer than 200 characters; truncating", ); @@ -39,12 +42,12 @@ abstract class Find implements SimpleAction { export class FindInDocument extends Find { protected find(query: string): Promise { - return ide().findInDocument(query); + return this.ide.findInDocument(query); } } export class FindInWorkspace extends Find { protected find(query: string): Promise { - return ide().findInWorkspace(query); + return this.ide.findInWorkspace(query); } } diff --git a/packages/cursorless-engine/src/actions/FlashTargets.ts b/packages/cursorless-engine/src/actions/FlashTargets.ts index a336dd72c3..8cf5f3f222 100644 --- a/packages/cursorless-engine/src/actions/FlashTargets.ts +++ b/packages/cursorless-engine/src/actions/FlashTargets.ts @@ -1,16 +1,16 @@ +import type { IDE } from "@cursorless/common"; import { FlashStyle } from "@cursorless/common"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { flashTargets } from "../util/targetUtils"; import type { ActionReturnValue, SimpleAction } from "./actions.types"; export class FlashTargets implements SimpleAction { - constructor() { + constructor(private ide: IDE) { this.run = this.run.bind(this); } async run(targets: Target[]): Promise { - await flashTargets(ide(), targets, FlashStyle.referenced); + await flashTargets(this.ide, targets, FlashStyle.referenced); return { thatTargets: targets }; } diff --git a/packages/cursorless-engine/src/actions/FollowLink.ts b/packages/cursorless-engine/src/actions/FollowLink.ts index e4f3ec13e3..47a8ed02ef 100644 --- a/packages/cursorless-engine/src/actions/FollowLink.ts +++ b/packages/cursorless-engine/src/actions/FollowLink.ts @@ -1,6 +1,5 @@ -import type { OpenLinkOptions } from "@cursorless/common"; +import type { IDE, OpenLinkOptions } from "@cursorless/common"; import { FlashStyle } from "@cursorless/common"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { createThatMark, @@ -10,16 +9,19 @@ import { import type { ActionReturnValue, SimpleAction } from "./actions.types"; export default class FollowLink implements SimpleAction { - constructor(private options: OpenLinkOptions) { + constructor( + private ide: IDE, + private options: OpenLinkOptions, + ) { this.run = this.run.bind(this); } async run(targets: Target[]): Promise { const target = ensureSingleTarget(targets); - await flashTargets(ide(), targets, FlashStyle.referenced); + await flashTargets(this.ide, targets, FlashStyle.referenced); - await ide() + await this.ide .getEditableTextEditor(target.editor) .openLink(target.contentRange, this.options); diff --git a/packages/cursorless-engine/src/actions/GenerateSnippet/GenerateSnippet.ts b/packages/cursorless-engine/src/actions/GenerateSnippet/GenerateSnippet.ts index 539544cc25..2ac317af32 100644 --- a/packages/cursorless-engine/src/actions/GenerateSnippet/GenerateSnippet.ts +++ b/packages/cursorless-engine/src/actions/GenerateSnippet/GenerateSnippet.ts @@ -1,21 +1,21 @@ -import { - FlashStyle, - Range, - matchAll, - type EditableTextEditor, - type Selection, - type TextEditor, +import type { + EditableTextEditor, + IDE, + Selection, + TextEditor, } from "@cursorless/common"; +import { FlashStyle, Range, matchAll } from "@cursorless/common"; +import type { + Snippet, + SnippetFile, + SnippetHeader, + SnippetVariable, +} from "@cursorless/talon-tools"; import { parseSnippetFile, serializeSnippetFile, - type Snippet, - type SnippetFile, - type SnippetHeader, - type SnippetVariable, } from "@cursorless/talon-tools"; import type { Snippets } from "../../core/Snippets"; -import { ide } from "../../singletons/ide.singleton"; import type { Target } from "../../typings/target.types"; import { ensureSingleTarget, flashTargets } from "../../util/targetUtils"; import type { ActionReturnValue } from "../actions.types"; @@ -53,7 +53,10 @@ import type { Offsets } from "./Offsets"; * 9. Insert the meta snippet so that the user can construct their snippet. */ export default class GenerateSnippet { - constructor(private snippets: Snippets) { + constructor( + private ide: IDE, + private snippets: Snippets, + ) { this.run = this.run.bind(this); } @@ -75,10 +78,10 @@ export default class GenerateSnippet { // immediately starts saying the name of the snippet (eg command chain // "snippet make funk camel my function"), we're more likely to // win the race and have the input box ready for them - void flashTargets(ide(), targets, FlashStyle.referenced); + void flashTargets(this.ide, targets, FlashStyle.referenced); if (snippetName == null) { - snippetName = await ide().showInputBox({ + snippetName = await this.ide.showInputBox({ prompt: "Name of snippet", placeHolder: "helloWorld", }); @@ -138,12 +141,12 @@ export default class GenerateSnippet { let editableEditor: EditableTextEditor; let snippetFile: SnippetFile = { snippets: [] }; - if (ide().runMode === "test") { + if (this.ide.runMode === "test") { // If we're testing, we just overwrite the current document - editableEditor = ide().getEditableTextEditor(editor); + editableEditor = this.ide.getEditableTextEditor(editor); } else { // Otherwise, we create and open a new document for the snippet - editableEditor = ide().getEditableTextEditor( + editableEditor = this.ide.getEditableTextEditor( await this.snippets.openNewSnippetFile(snippetName, directory), ); snippetFile = parseSnippetFile(editableEditor.document.getText()); diff --git a/packages/cursorless-engine/src/actions/GetText.ts b/packages/cursorless-engine/src/actions/GetText.ts index 1f7bac354b..5e0856e88a 100644 --- a/packages/cursorless-engine/src/actions/GetText.ts +++ b/packages/cursorless-engine/src/actions/GetText.ts @@ -1,12 +1,11 @@ -import type { GetTextActionOptions } from "@cursorless/common"; +import type { GetTextActionOptions, IDE } from "@cursorless/common"; import { FlashStyle } from "@cursorless/common"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { ensureSingleTarget, flashTargets } from "../util/targetUtils"; import type { ActionReturnValue } from "./actions.types"; export default class GetText { - constructor() { + constructor(private ide: IDE) { this.run = this.run.bind(this); } @@ -18,7 +17,7 @@ export default class GetText { }: GetTextActionOptions = {}, ): Promise { if (showDecorations) { - await flashTargets(ide(), targets, FlashStyle.referenced); + await flashTargets(this.ide, targets, FlashStyle.referenced); } if (doEnsureSingleTarget) { diff --git a/packages/cursorless-engine/src/actions/Highlight.ts b/packages/cursorless-engine/src/actions/Highlight.ts index 4b0c341441..65c8e52144 100644 --- a/packages/cursorless-engine/src/actions/Highlight.ts +++ b/packages/cursorless-engine/src/actions/Highlight.ts @@ -1,5 +1,4 @@ -import type { HighlightId } from "@cursorless/common"; -import { ide } from "../singletons/ide.singleton"; +import type { HighlightId, IDE } from "@cursorless/common"; import type { Target } from "../typings/target.types"; import { runOnTargetsForEachEditor, @@ -8,7 +7,7 @@ import { import type { ActionReturnValue } from "./actions.types"; export default class Highlight { - constructor() { + constructor(private ide: IDE) { this.run = this.run.bind(this); } @@ -16,7 +15,7 @@ export default class Highlight { targets: Target[], highlightId?: HighlightId, ): Promise { - if (ide().capabilities.commands["highlight"] == null) { + if (this.ide.capabilities.commands["highlight"] == null) { throw Error(`The highlight action is not supported by your ide`); } @@ -24,13 +23,13 @@ export default class Highlight { // Special case to clear highlights for all editors when user says // "highlight nothing" await Promise.all( - ide().visibleTextEditors.map((editor) => - ide().setHighlightRanges(highlightId, editor, []), + this.ide.visibleTextEditors.map((editor) => + this.ide.setHighlightRanges(highlightId, editor, []), ), ); } else { await runOnTargetsForEachEditor(targets, (editor, targets) => - ide().setHighlightRanges( + this.ide.setHighlightRanges( highlightId, editor, targets.map((target) => diff --git a/packages/cursorless-engine/src/actions/IndentLine.ts b/packages/cursorless-engine/src/actions/IndentLine.ts index ae4eb84967..8d0a7d146c 100644 --- a/packages/cursorless-engine/src/actions/IndentLine.ts +++ b/packages/cursorless-engine/src/actions/IndentLine.ts @@ -1,8 +1,8 @@ -import { FlashStyle, Range, type TextEditor } from "@cursorless/common"; +import type { IDE, TextEditor } from "@cursorless/common"; +import { FlashStyle, Range } from "@cursorless/common"; import { flatten, zip } from "lodash-es"; import { selectionToStoredTarget } from "../core/commandRunner/selectionToStoredTarget"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { flashTargets, runOnTargetsForEachEditor } from "../util/targetUtils"; import { @@ -14,6 +14,7 @@ import { performEditsAndUpdateSelections } from "../core/updateSelections/update abstract class IndentLineBase { constructor( + private ide: IDE, private rangeUpdater: RangeUpdater, private isIndent: boolean, ) { @@ -26,7 +27,7 @@ abstract class IndentLineBase { return this.runSimpleCommandAction(targets); } - await flashTargets(ide(), targets, FlashStyle.pendingModification0); + await flashTargets(this.ide, targets, FlashStyle.pendingModification0); const thatTargets = flatten( await runOnTargetsForEachEditor(targets, this.runForEditor), @@ -37,16 +38,16 @@ abstract class IndentLineBase { private hasCapability() { return this.isIndent - ? ide().capabilities.commands.indentLine != null - : ide().capabilities.commands.outdentLine != null; + ? this.ide.capabilities.commands.indentLine != null + : this.ide.capabilities.commands.outdentLine != null; } private runSimpleCommandAction( targets: Target[], ): Promise { const action = this.isIndent - ? new IndentLineSimpleAction(this.rangeUpdater) - : new OutdentLineSimpleAction(this.rangeUpdater); + ? new IndentLineSimpleAction(this.ide, this.rangeUpdater) + : new OutdentLineSimpleAction(this.ide, this.rangeUpdater); return action.run(targets); } @@ -58,7 +59,7 @@ abstract class IndentLineBase { const { targetSelections: updatedTargetSelections } = await performEditsAndUpdateSelections({ rangeUpdater: this.rangeUpdater, - editor: ide().getEditableTextEditor(editor), + editor: this.ide.getEditableTextEditor(editor), edits, selections: { targetSelections: targets.map( @@ -77,14 +78,14 @@ abstract class IndentLineBase { } export class IndentLine extends IndentLineBase { - constructor(rangeUpdater: RangeUpdater) { - super(rangeUpdater, true); + constructor(ide: IDE, rangeUpdater: RangeUpdater) { + super(ide, rangeUpdater, true); } } export class OutdentLine extends IndentLineBase { - constructor(rangeUpdater: RangeUpdater) { - super(rangeUpdater, false); + constructor(ide: IDE, rangeUpdater: RangeUpdater) { + super(ide, rangeUpdater, false); } } diff --git a/packages/cursorless-engine/src/actions/InsertCopy.ts b/packages/cursorless-engine/src/actions/InsertCopy.ts index 71c5f6eba9..b23781ad57 100644 --- a/packages/cursorless-engine/src/actions/InsertCopy.ts +++ b/packages/cursorless-engine/src/actions/InsertCopy.ts @@ -1,4 +1,4 @@ -import type { TextEditor } from "@cursorless/common"; +import type { IDE, TextEditor } from "@cursorless/common"; import { FlashStyle, RangeExpansionBehavior, @@ -9,7 +9,6 @@ import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; import type { ModifierStageFactory } from "../processTargets/ModifierStageFactory"; import { containingLineIfUntypedModifier } from "../processTargets/modifiers/commonContainingScopeIfUntypedModifiers"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { createThatMark, runOnTargetsForEachEditor } from "../util/targetUtils"; import type { ActionReturnValue, SimpleAction } from "./actions.types"; @@ -20,6 +19,7 @@ class InsertCopy implements SimpleAction { ]; constructor( + private ide: IDE, private rangeUpdater: RangeUpdater, private modifierStageFactory: ModifierStageFactory, private isBefore: boolean, @@ -33,7 +33,7 @@ class InsertCopy implements SimpleAction { await runOnTargetsForEachEditor(targets, this.runForEditor), ); - await ide().flashRanges( + await this.ide.flashRanges( results.flatMap((result) => result.thatMark.map((that) => ({ editor: that.editor, @@ -59,7 +59,7 @@ class InsertCopy implements SimpleAction { ({ contentSelection }) => contentSelection, ); const editRanges = edits.map(({ range }) => range); - const editableEditor = ide().getEditableTextEditor(editor); + const editableEditor = this.ide.getEditableTextEditor(editor); const { contentSelections: updatedContentSelections, @@ -101,18 +101,20 @@ class InsertCopy implements SimpleAction { export class CopyContentBefore extends InsertCopy { constructor( + ide: IDE, rangeUpdater: RangeUpdater, modifierStageFactory: ModifierStageFactory, ) { - super(rangeUpdater, modifierStageFactory, true); + super(ide, rangeUpdater, modifierStageFactory, true); } } export class CopyContentAfter extends InsertCopy { constructor( + ide: IDE, rangeUpdater: RangeUpdater, modifierStageFactory: ModifierStageFactory, ) { - super(rangeUpdater, modifierStageFactory, false); + super(ide, rangeUpdater, modifierStageFactory, false); } } diff --git a/packages/cursorless-engine/src/actions/InsertEmptyLines.ts b/packages/cursorless-engine/src/actions/InsertEmptyLines.ts index 94b1824d46..e1deecaf60 100644 --- a/packages/cursorless-engine/src/actions/InsertEmptyLines.ts +++ b/packages/cursorless-engine/src/actions/InsertEmptyLines.ts @@ -1,17 +1,16 @@ +import type { IDE, InsertionMode } from "@cursorless/common"; import { FlashStyle, RangeExpansionBehavior, toCharacterRange, toLineRange, zipStrict, - type InsertionMode, } from "@cursorless/common"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; import { containingLineIfUntypedModifier } from "../processTargets/modifiers/commonContainingScopeIfUntypedModifiers"; import type { ModifierStageFactory } from "../processTargets/ModifierStageFactory"; import type { ModifierStage } from "../processTargets/PipelineStages.types"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import type { EditWithRangeUpdater as EditWithRangeType } from "../typings/Types"; import { runOnTargetsForEachEditor } from "../util/targetUtils"; @@ -28,6 +27,7 @@ abstract class InsertEmptyLines implements SimpleAction { } constructor( + private ide: IDE, private rangeUpdater: RangeUpdater, private modifierStageFactory: ModifierStageFactory, ) { @@ -48,7 +48,7 @@ abstract class InsertEmptyLines implements SimpleAction { editRanges: updatedEditRanges, } = await performEditsAndUpdateSelections({ rangeUpdater: this.rangeUpdater, - editor: ide().getEditableTextEditor(editor), + editor: this.ide.getEditableTextEditor(editor), edits, selections: { contentSelections, @@ -76,7 +76,7 @@ abstract class InsertEmptyLines implements SimpleAction { }, ); - await ide().flashRanges( + await this.ide.flashRanges( results.flatMap((result) => result.flashRanges.map(({ editor, range, isLine }) => ({ editor, @@ -96,10 +96,11 @@ abstract class InsertEmptyLines implements SimpleAction { export class InsertEmptyLinesAround extends InsertEmptyLines { constructor( + ide: IDE, rangeUpdater: RangeUpdater, modifierStageFactory: ModifierStageFactory, ) { - super(rangeUpdater, modifierStageFactory); + super(ide, rangeUpdater, modifierStageFactory); } protected getEdits(targets: Target[]) { @@ -112,10 +113,11 @@ export class InsertEmptyLinesAround extends InsertEmptyLines { export class InsertEmptyLineAbove extends InsertEmptyLines { constructor( + ide: IDE, rangeUpdater: RangeUpdater, modifierStageFactory: ModifierStageFactory, ) { - super(rangeUpdater, modifierStageFactory); + super(ide, rangeUpdater, modifierStageFactory); } protected getEdits(targets: Target[]) { @@ -125,10 +127,11 @@ export class InsertEmptyLineAbove extends InsertEmptyLines { export class InsertEmptyLineBelow extends InsertEmptyLines { constructor( + ide: IDE, rangeUpdater: RangeUpdater, modifierStageFactory: ModifierStageFactory, ) { - super(rangeUpdater, modifierStageFactory); + super(ide, rangeUpdater, modifierStageFactory); } protected getEdits(targets: Target[]) { diff --git a/packages/cursorless-engine/src/actions/InsertSnippet.ts b/packages/cursorless-engine/src/actions/InsertSnippet.ts index 1d25c1dd6a..96dc227086 100644 --- a/packages/cursorless-engine/src/actions/InsertSnippet.ts +++ b/packages/cursorless-engine/src/actions/InsertSnippet.ts @@ -1,4 +1,4 @@ -import type { InsertSnippetArg } from "@cursorless/common"; +import type { IDE, InsertSnippetArg } from "@cursorless/common"; import { NamedSnippetsDeprecationError, RangeExpansionBehavior, @@ -9,7 +9,6 @@ import { performEditsAndUpdateSelections } from "../core/updateSelections/update import type { ModifierStageFactory } from "../processTargets/ModifierStageFactory"; import type { ModifierStage } from "../processTargets/PipelineStages.types"; import { ModifyIfUntypedExplicitStage } from "../processTargets/modifiers/ConditionalModifierStages"; -import { ide } from "../singletons/ide.singleton"; import { transformSnippetVariables } from "../snippets/transformSnippetVariables"; import { SnippetParser } from "../snippets/vendor/vscodeSnippet/snippetParser"; import type { Destination } from "../typings/target.types"; @@ -21,6 +20,7 @@ export default class InsertSnippet { private snippetParser = new SnippetParser(); constructor( + private ide: IDE, private rangeUpdater: RangeUpdater, private actions: Actions, private modifierStageFactory: ModifierStageFactory, @@ -59,7 +59,7 @@ export default class InsertSnippet { throw new NamedSnippetsDeprecationError(); } - const editor = ide().getEditableTextEditor( + const editor = this.ide.getEditableTextEditor( ensureSingleEditor(destinations), ); const snippet = getPreferredSnippet( diff --git a/packages/cursorless-engine/src/actions/JoinLines.ts b/packages/cursorless-engine/src/actions/JoinLines.ts index f5b20936a8..90620104f6 100644 --- a/packages/cursorless-engine/src/actions/JoinLines.ts +++ b/packages/cursorless-engine/src/actions/JoinLines.ts @@ -1,4 +1,4 @@ -import type { Edit, TextEditor } from "@cursorless/common"; +import type { Edit, IDE, TextEditor } from "@cursorless/common"; import { FlashStyle, Range, zipStrict } from "@cursorless/common"; import { range as iterRange, map, pairwise } from "itertools"; import { flatten } from "lodash-es"; @@ -7,7 +7,6 @@ import { performEditsAndUpdateSelections } from "../core/updateSelections/update import { containingLineIfUntypedModifier } from "../processTargets/modifiers/commonContainingScopeIfUntypedModifiers"; import type { ModifierStageFactory } from "../processTargets/ModifierStageFactory"; import type { ModifierStage } from "../processTargets/PipelineStages.types"; -import { ide } from "../singletons/ide.singleton"; import { getMatcher } from "../tokenizer"; import type { Target } from "../typings/target.types"; import { generateMatchesInRange } from "../util/getMatchesInRange"; @@ -20,6 +19,7 @@ export default class JoinLines { } constructor( + private ide: IDE, private rangeUpdater: RangeUpdater, private modifierStageFactory: ModifierStageFactory, ) { @@ -28,7 +28,7 @@ export default class JoinLines { async run(targets: Target[]): Promise { await flashTargets( - ide(), + this.ide, targets.map(({ thatTarget }) => thatTarget), FlashStyle.pendingModification0, ); @@ -38,8 +38,8 @@ export default class JoinLines { const { thatRanges: updatedThatRanges } = await performEditsAndUpdateSelections({ rangeUpdater: this.rangeUpdater, - editor: ide().getEditableTextEditor(editor), - edits: getEdits(editor, targets), + editor: this.ide.getEditableTextEditor(editor), + edits: getEdits(this.ide, editor, targets), selections: { thatRanges: targets.map(({ contentRange }) => contentRange), }, @@ -56,13 +56,13 @@ export default class JoinLines { } } -function getEdits(editor: TextEditor, targets: Target[]): Edit[] { +function getEdits(ide: IDE, editor: TextEditor, targets: Target[]): Edit[] { const edits: Edit[] = []; for (const target of targets) { const targetsEdits = target.textualType === "token" - ? getTokenTargetEdits(target) + ? getTokenTargetEdits(ide, target) : getLineTargetEdits(target); edits.push(...targetsEdits); @@ -71,9 +71,9 @@ function getEdits(editor: TextEditor, targets: Target[]): Edit[] { return edits; } -function getTokenTargetEdits(target: Target): Edit[] { +function getTokenTargetEdits(ide: IDE, target: Target): Edit[] { const { editor, contentRange } = target; - const regex = getMatcher(editor.document.languageId).tokenMatcher; + const regex = getMatcher(ide, editor.document.languageId).tokenMatcher; const matches = generateMatchesInRange( regex, editor, diff --git a/packages/cursorless-engine/src/actions/PasteFromClipboard.ts b/packages/cursorless-engine/src/actions/PasteFromClipboard.ts index 71d477262d..0943eb373e 100644 --- a/packages/cursorless-engine/src/actions/PasteFromClipboard.ts +++ b/packages/cursorless-engine/src/actions/PasteFromClipboard.ts @@ -1,10 +1,10 @@ +import type { IDE } from "@cursorless/common"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; -import { ide } from "../singletons/ide.singleton"; import type { Destination } from "../typings/target.types"; import type { Actions } from "./Actions"; import type { ActionReturnValue } from "./actions.types"; -import { PasteFromClipboardUsingCommand } from "./PasteFromClipboardUsingCommand"; import { PasteFromClipboardDirectly } from "./PasteFromClipboardDirectly"; +import { PasteFromClipboardUsingCommand } from "./PasteFromClipboardUsingCommand"; export interface DestinationWithText { destination: Destination; @@ -14,12 +14,12 @@ export interface DestinationWithText { export class PasteFromClipboard { private runner: PasteFromClipboardDirectly | PasteFromClipboardUsingCommand; - constructor(rangeUpdater: RangeUpdater, actions: Actions) { + constructor(ide: IDE, rangeUpdater: RangeUpdater, actions: Actions) { this.run = this.run.bind(this); this.runner = - ide().capabilities.commands.clipboardPaste != null - ? new PasteFromClipboardUsingCommand(rangeUpdater, actions) - : new PasteFromClipboardDirectly(rangeUpdater); + ide.capabilities.commands.clipboardPaste != null + ? new PasteFromClipboardUsingCommand(ide, rangeUpdater, actions) + : new PasteFromClipboardDirectly(ide, rangeUpdater); } run(destinations: Destination[]): Promise { diff --git a/packages/cursorless-engine/src/actions/PasteFromClipboardDirectly.ts b/packages/cursorless-engine/src/actions/PasteFromClipboardDirectly.ts index 6a5b07dc1f..e01e5ae23f 100644 --- a/packages/cursorless-engine/src/actions/PasteFromClipboardDirectly.ts +++ b/packages/cursorless-engine/src/actions/PasteFromClipboardDirectly.ts @@ -1,14 +1,13 @@ +import type { IDE, TextEditor } from "@cursorless/common"; import { FlashStyle, RangeExpansionBehavior, toCharacterRange, zipStrict, - type TextEditor, } from "@cursorless/common"; import { flatten } from "lodash-es"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; -import { ide } from "../singletons/ide.singleton"; import type { Destination } from "../typings/target.types"; import { runForEachEditor } from "../util/targetUtils"; import type { ActionReturnValue } from "./actions.types"; @@ -19,12 +18,15 @@ import type { DestinationWithText } from "./PasteFromClipboard"; * by reading the clipboard and inserting the text directly into the editor. */ export class PasteFromClipboardDirectly { - constructor(private rangeUpdater: RangeUpdater) { + constructor( + private ide: IDE, + private rangeUpdater: RangeUpdater, + ) { this.runForEditor = this.runForEditor.bind(this); } async run(destinations: Destination[]): Promise { - const text = await ide().clipboard.readText(); + const text = await this.ide.clipboard.readText(); const textLines = text.split(/\r?\n/g); // FIXME: We should really use the number of targets from the original copy @@ -59,7 +61,7 @@ export class PasteFromClipboardDirectly { const { editSelections: updatedEditSelections } = await performEditsAndUpdateSelections({ rangeUpdater: this.rangeUpdater, - editor: ide().getEditableTextEditor(editor), + editor: this.ide.getEditableTextEditor(editor), edits, selections: { editSelections: { @@ -74,7 +76,7 @@ export class PasteFromClipboardDirectly { edit.updateRange(selection).toSelection(selection.isReversed), ); - await ide().flashRanges( + await this.ide.flashRanges( thatTargetSelections.map((selection) => ({ editor, range: toCharacterRange(selection), diff --git a/packages/cursorless-engine/src/actions/PasteFromClipboardUsingCommand.ts b/packages/cursorless-engine/src/actions/PasteFromClipboardUsingCommand.ts index 42e52a550a..b242860195 100644 --- a/packages/cursorless-engine/src/actions/PasteFromClipboardUsingCommand.ts +++ b/packages/cursorless-engine/src/actions/PasteFromClipboardUsingCommand.ts @@ -2,10 +2,10 @@ import { FlashStyle, RangeExpansionBehavior, toCharacterRange, + type IDE, } from "@cursorless/common"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; -import { ide } from "../singletons/ide.singleton"; import type { Destination } from "../typings/target.types"; import { ensureSingleEditor } from "../util/targetUtils"; import type { Actions } from "./Actions"; @@ -17,6 +17,7 @@ import type { ActionReturnValue } from "./actions.types"; */ export class PasteFromClipboardUsingCommand { constructor( + private ide: IDE, private rangeUpdater: RangeUpdater, private actions: Actions, ) { @@ -24,10 +25,10 @@ export class PasteFromClipboardUsingCommand { } async run(destinations: Destination[]): Promise { - const editor = ide().getEditableTextEditor( + const editor = this.ide.getEditableTextEditor( ensureSingleEditor(destinations), ); - const originalEditor = ide().activeEditableTextEditor; + const originalEditor = this.ide.activeEditableTextEditor; // First call editNew in order to insert delimiters if necessary and leave // the cursor in the right position. Note that this action will focus the @@ -78,7 +79,7 @@ export class PasteFromClipboardUsingCommand { await originalEditor.focus(); } - await ide().flashRanges( + await this.ide.flashRanges( updatedTargetSelections.map((selection) => ({ editor, range: toCharacterRange(selection), diff --git a/packages/cursorless-engine/src/actions/Remove.ts b/packages/cursorless-engine/src/actions/Remove.ts index dd7ea565ae..ad1230924e 100644 --- a/packages/cursorless-engine/src/actions/Remove.ts +++ b/packages/cursorless-engine/src/actions/Remove.ts @@ -1,17 +1,19 @@ -import type { TextEditor } from "@cursorless/common"; +import type { IDE, TextEditor } from "@cursorless/common"; import { FlashStyle } from "@cursorless/common"; import { flatten, zip } from "lodash-es"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; import { RawSelectionTarget } from "../processTargets/targets"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { flashTargets, runOnTargetsForEachEditor } from "../util/targetUtils"; import { unifyRemovalTargets } from "../util/unifyRanges"; import type { ActionReturnValue, SimpleAction } from "./actions.types"; export default class Delete implements SimpleAction { - constructor(private rangeUpdater: RangeUpdater) { + constructor( + private ide: IDE, + private rangeUpdater: RangeUpdater, + ) { this.run = this.run.bind(this); this.runForEditor = this.runForEditor.bind(this); } @@ -24,8 +26,11 @@ export default class Delete implements SimpleAction { targets = unifyRemovalTargets(targets); if (showDecorations) { - await flashTargets(ide(), targets, FlashStyle.pendingDelete, (target) => - target.getRemovalHighlightRange(), + await flashTargets( + this.ide, + targets, + FlashStyle.pendingDelete, + (target) => target.getRemovalHighlightRange(), ); } @@ -38,7 +43,7 @@ export default class Delete implements SimpleAction { private async runForEditor(editor: TextEditor, targets: Target[]) { const edits = targets.map((target) => target.constructRemovalEdit()); - const editableEditor = ide().getEditableTextEditor(editor); + const editableEditor = this.ide.getEditableTextEditor(editor); const { editRanges: updatedEditRanges } = await performEditsAndUpdateSelections({ diff --git a/packages/cursorless-engine/src/actions/Replace.ts b/packages/cursorless-engine/src/actions/Replace.ts index 4d1efa061a..72e905a035 100644 --- a/packages/cursorless-engine/src/actions/Replace.ts +++ b/packages/cursorless-engine/src/actions/Replace.ts @@ -1,16 +1,18 @@ -import type { ReplaceWith } from "@cursorless/common"; +import type { IDE, ReplaceWith } from "@cursorless/common"; import { FlashStyle, RangeExpansionBehavior } from "@cursorless/common"; import { zip } from "lodash-es"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; -import { ide } from "../singletons/ide.singleton"; import type { SelectionWithEditor } from "../typings/Types"; import type { Destination, Target } from "../typings/target.types"; import { flashTargets, runForEachEditor } from "../util/targetUtils"; import type { ActionReturnValue } from "./actions.types"; export default class Replace { - constructor(private rangeUpdater: RangeUpdater) { + constructor( + private ide: IDE, + private rangeUpdater: RangeUpdater, + ) { this.run = this.run.bind(this); } @@ -37,7 +39,7 @@ export default class Replace { replaceWith: ReplaceWith, ): Promise { await flashTargets( - ide(), + this.ide, destinations.map((d) => d.target), FlashStyle.pendingModification0, ); @@ -68,7 +70,7 @@ export default class Replace { editRanges: updatedEditRanges, } = await performEditsAndUpdateSelections({ rangeUpdater: this.rangeUpdater, - editor: ide().getEditableTextEditor(editor), + editor: this.ide.getEditableTextEditor(editor), edits, selections: { contentSelections: editWrappers.map( diff --git a/packages/cursorless-engine/src/actions/Rewrap.ts b/packages/cursorless-engine/src/actions/Rewrap.ts index 0fad5350e3..7b33a35244 100644 --- a/packages/cursorless-engine/src/actions/Rewrap.ts +++ b/packages/cursorless-engine/src/actions/Rewrap.ts @@ -1,9 +1,9 @@ +import type { IDE } from "@cursorless/common"; import { FlashStyle } from "@cursorless/common"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; import { getContainingSurroundingPairIfNoBoundaryStage } from "../processTargets/modifiers/BoundaryStage"; import type { ModifierStageFactory } from "../processTargets/ModifierStageFactory"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { createThatMark, @@ -18,6 +18,7 @@ export default class Rewrap { ]; constructor( + private ide: IDE, private rangeUpdater: RangeUpdater, private modifierStageFactory: ModifierStageFactory, ) { @@ -39,7 +40,11 @@ export default class Rewrap { return boundary; }); - await flashTargets(ide(), boundaryTargets, FlashStyle.pendingModification0); + await flashTargets( + this.ide, + boundaryTargets, + FlashStyle.pendingModification0, + ); const results = await runOnTargetsForEachEditor( boundaryTargets, @@ -55,7 +60,7 @@ export default class Rewrap { thatRanges: updatedThatRanges, } = await performEditsAndUpdateSelections({ rangeUpdater: this.rangeUpdater, - editor: ide().getEditableTextEditor(editor), + editor: this.ide.getEditableTextEditor(editor), edits, selections: { sourceRanges: targets.map( diff --git a/packages/cursorless-engine/src/actions/Scroll.ts b/packages/cursorless-engine/src/actions/Scroll.ts index d079e224d7..ad60f4faf4 100644 --- a/packages/cursorless-engine/src/actions/Scroll.ts +++ b/packages/cursorless-engine/src/actions/Scroll.ts @@ -1,15 +1,18 @@ +import type { IDE } from "@cursorless/common"; import { FlashStyle, groupBy, RevealLineAt, toLineRange, } from "@cursorless/common"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import type { SimpleAction, ActionReturnValue } from "./actions.types"; class Scroll implements SimpleAction { - constructor(private at: RevealLineAt) { + constructor( + private ide: IDE, + private at: RevealLineAt, + ) { this.run = this.run.bind(this); } @@ -20,10 +23,10 @@ class Scroll implements SimpleAction { return { lineNumber: getLineNumber(targets, this.at), editor }; }); - const originalEditor = ide().activeEditableTextEditor; + const originalEditor = this.ide.activeEditableTextEditor; for (const lineWithEditor of lines) { - await ide() + await this.ide .getEditableTextEditor(lineWithEditor.editor) .revealLine(lineWithEditor.lineNumber, this.at); } @@ -46,7 +49,7 @@ class Scroll implements SimpleAction { ); }); - await ide().flashRanges( + await this.ide.flashRanges( decorationTargets.map((target) => ({ editor: target.editor, range: toLineRange(target.contentRange), @@ -61,20 +64,20 @@ class Scroll implements SimpleAction { } export class ScrollToTop extends Scroll { - constructor() { - super(RevealLineAt.top); + constructor(ide: IDE) { + super(ide, RevealLineAt.top); } } export class ScrollToCenter extends Scroll { - constructor() { - super(RevealLineAt.center); + constructor(ide: IDE) { + super(ide, RevealLineAt.center); } } export class ScrollToBottom extends Scroll { - constructor() { - super(RevealLineAt.bottom); + constructor(ide: IDE) { + super(ide, RevealLineAt.bottom); } } diff --git a/packages/cursorless-engine/src/actions/SetSelection.ts b/packages/cursorless-engine/src/actions/SetSelection.ts index a221b1cc9f..dbfc9807d0 100644 --- a/packages/cursorless-engine/src/actions/SetSelection.ts +++ b/packages/cursorless-engine/src/actions/SetSelection.ts @@ -1,11 +1,12 @@ +import type { IDE } from "@cursorless/common"; import { Selection } from "@cursorless/common"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { ensureSingleEditor } from "../util/targetUtils"; import type { SimpleAction, ActionReturnValue } from "./actions.types"; abstract class SetSelectionBase implements SimpleAction { constructor( + private ide: IDE, private selectionMode: "set" | "add", private rangeMode: "content" | "before" | "after", ) { @@ -26,7 +27,7 @@ abstract class SetSelectionBase implements SimpleAction { selections.length === 1 && selections[0].isEmpty; - await ide() + await this.ide .getEditableTextEditor(editor) .setSelections(selections, { focusEditor: true, highlightWord }); @@ -54,37 +55,37 @@ abstract class SetSelectionBase implements SimpleAction { } export class SetSelection extends SetSelectionBase { - constructor() { - super("set", "content"); + constructor(ide: IDE) { + super(ide, "set", "content"); } } export class SetSelectionBefore extends SetSelectionBase { - constructor() { - super("set", "before"); + constructor(ide: IDE) { + super(ide, "set", "before"); } } export class SetSelectionAfter extends SetSelectionBase { - constructor() { - super("set", "after"); + constructor(ide: IDE) { + super(ide, "set", "after"); } } export class AddSelection extends SetSelectionBase { - constructor() { - super("add", "content"); + constructor(ide: IDE) { + super(ide, "add", "content"); } } export class AddSelectionBefore extends SetSelectionBase { - constructor() { - super("add", "before"); + constructor(ide: IDE) { + super(ide, "add", "before"); } } export class AddSelectionAfter extends SetSelectionBase { - constructor() { - super("add", "after"); + constructor(ide: IDE) { + super(ide, "add", "after"); } } diff --git a/packages/cursorless-engine/src/actions/ShowParseTree.ts b/packages/cursorless-engine/src/actions/ShowParseTree.ts index e93617500e..7e93d1f365 100644 --- a/packages/cursorless-engine/src/actions/ShowParseTree.ts +++ b/packages/cursorless-engine/src/actions/ShowParseTree.ts @@ -1,18 +1,20 @@ -import type { TreeSitter, TextDocument } from "@cursorless/common"; +import type { IDE, TextDocument, TreeSitter } from "@cursorless/common"; import { FlashStyle, Range } from "@cursorless/common"; import type { Tree, TreeCursor } from "web-tree-sitter"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { flashTargets } from "../util/targetUtils"; import type { ActionReturnValue } from "./actions.types"; -export default class ShowParseTree { - constructor(private treeSitter: TreeSitter) { +export default class InjectedShowParseTree { + constructor( + private ide: IDE, + private treeSitter: TreeSitter, + ) { this.run = this.run.bind(this); } async run(targets: Target[]): Promise { - await flashTargets(ide(), targets, FlashStyle.referenced); + await flashTargets(this.ide, targets, FlashStyle.referenced); const results: string[] = ["# Cursorless parse tree"]; @@ -22,8 +24,7 @@ export default class ShowParseTree { results.push(parseTree(editor.document, tree, contentRange)); } - // FIXME: If we await this our test break. We should probably find out when this actually resolves. - void ide().openUntitledTextDocument({ + void this.ide.openUntitledTextDocument({ language: "markdown", content: results.join("\n\n"), }); diff --git a/packages/cursorless-engine/src/actions/SimpleIdeCommandActions.ts b/packages/cursorless-engine/src/actions/SimpleIdeCommandActions.ts index a6d9668413..149ab83fe2 100644 --- a/packages/cursorless-engine/src/actions/SimpleIdeCommandActions.ts +++ b/packages/cursorless-engine/src/actions/SimpleIdeCommandActions.ts @@ -1,6 +1,10 @@ -import type { CommandId, EditableTextEditor, Range } from "@cursorless/common"; +import type { + CommandId, + EditableTextEditor, + IDE, + Range, +} from "@cursorless/common"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { CallbackAction } from "./CallbackAction"; import type { ActionReturnValue } from "./actions.types"; @@ -25,8 +29,11 @@ abstract class SimpleIdeCommandAction { restoreSelection: boolean = true; showDecorations: boolean = true; - constructor(rangeUpdater: RangeUpdater) { - this.callbackAction = new CallbackAction(rangeUpdater); + constructor( + protected ide: IDE, + rangeUpdater: RangeUpdater, + ) { + this.callbackAction = new CallbackAction(ide, rangeUpdater); this.run = this.run.bind(this); } @@ -34,7 +41,7 @@ abstract class SimpleIdeCommandAction { targets: Target[], { showDecorations }: Options = {}, ): Promise { - const capabilities = ide().capabilities.commands[this.command]; + const capabilities = this.ide.capabilities.commands[this.command]; if (capabilities == null) { throw Error(`Action ${this.command} is not supported by your ide`); diff --git a/packages/cursorless-engine/src/actions/Sort.ts b/packages/cursorless-engine/src/actions/Sort.ts index a3cccdbed3..59cc16aa70 100644 --- a/packages/cursorless-engine/src/actions/Sort.ts +++ b/packages/cursorless-engine/src/actions/Sort.ts @@ -1,12 +1,14 @@ -import { showWarning } from "@cursorless/common"; +import { showWarning, type IDE } from "@cursorless/common"; import { shuffle } from "lodash-es"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import type { Actions } from "./Actions"; import type { ActionReturnValue, SimpleAction } from "./actions.types"; abstract class SortBase implements SimpleAction { - constructor(private actions: Actions) { + constructor( + private ide: IDE, + private actions: Actions, + ) { this.run = this.run.bind(this); } @@ -15,7 +17,7 @@ abstract class SortBase implements SimpleAction { async run(targets: Target[]): Promise { if (targets.length < 2) { void showWarning( - ide().messages, + this.ide.messages, "tooFewTargets", 'This action works on multiple targets, e.g. "sort every line block" instead of "sort block".', ); diff --git a/packages/cursorless-engine/src/actions/ToggleBreakpoint.ts b/packages/cursorless-engine/src/actions/ToggleBreakpoint.ts index 4bbe0d4cfb..17e843cdfa 100644 --- a/packages/cursorless-engine/src/actions/ToggleBreakpoint.ts +++ b/packages/cursorless-engine/src/actions/ToggleBreakpoint.ts @@ -1,7 +1,6 @@ -import { FlashStyle } from "@cursorless/common"; +import { FlashStyle, type IDE } from "@cursorless/common"; import type { ModifierStageFactory } from "../processTargets/ModifierStageFactory"; import { containingLineIfUntypedModifier } from "../processTargets/modifiers/commonContainingScopeIfUntypedModifiers"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { flashTargets, @@ -15,21 +14,24 @@ export default class ToggleBreakpoint implements SimpleAction { this.modifierStageFactory.create(containingLineIfUntypedModifier), ]; - constructor(private modifierStageFactory: ModifierStageFactory) { + constructor( + private ide: IDE, + private modifierStageFactory: ModifierStageFactory, + ) { this.run = this.run.bind(this); } async run(targets: Target[]): Promise { const thatTargets = targets.map(({ thatTarget }) => thatTarget); - await flashTargets(ide(), thatTargets, FlashStyle.referenced); + await flashTargets(this.ide, thatTargets, FlashStyle.referenced); await runOnTargetsForEachEditor(targets, async (editor, targets) => { const generalizedRanges = targets.map((target) => toGeneralizedRange(target, target.contentRange), ); - await ide() + await this.ide .getEditableTextEditor(editor) .toggleBreakpoint(generalizedRanges); }); diff --git a/packages/cursorless-engine/src/actions/Wrap.ts b/packages/cursorless-engine/src/actions/Wrap.ts index 8fe65a5bc5..e72fca4202 100644 --- a/packages/cursorless-engine/src/actions/Wrap.ts +++ b/packages/cursorless-engine/src/actions/Wrap.ts @@ -1,4 +1,4 @@ -import type { Edit } from "@cursorless/common"; +import type { Edit, IDE } from "@cursorless/common"; import { FlashStyle, RangeExpansionBehavior, @@ -7,13 +7,15 @@ import { } from "@cursorless/common"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; import { performEditsAndUpdateSelections } from "../core/updateSelections/updateSelections"; -import { ide } from "../singletons/ide.singleton"; import type { Target } from "../typings/target.types"; import { runOnTargetsForEachEditor } from "../util/targetUtils"; import type { ActionReturnValue } from "./actions.types"; export default class Wrap { - constructor(private rangeUpdater: RangeUpdater) { + constructor( + private ide: IDE, + private rangeUpdater: RangeUpdater, + ) { this.run = this.run.bind(this); } @@ -56,7 +58,7 @@ export default class Wrap { thatSelections: thatMarkSelections, } = await performEditsAndUpdateSelections({ rangeUpdater: this.rangeUpdater, - editor: ide().getEditableTextEditor(editor), + editor: this.ide.getEditableTextEditor(editor), edits, selections: { boundariesStartSelections: { @@ -83,7 +85,7 @@ export default class Wrap { ...delimiterEndSelections, ]; - await ide().flashRanges( + await this.ide.flashRanges( delimiterSelections.map((selection) => ({ editor, range: toCharacterRange(selection), diff --git a/packages/cursorless-engine/src/actions/WrapWithSnippet.ts b/packages/cursorless-engine/src/actions/WrapWithSnippet.ts index c546de61ee..158bd1a7dc 100644 --- a/packages/cursorless-engine/src/actions/WrapWithSnippet.ts +++ b/packages/cursorless-engine/src/actions/WrapWithSnippet.ts @@ -1,4 +1,4 @@ -import type { WrapWithSnippetArg } from "@cursorless/common"; +import type { IDE, WrapWithSnippetArg } from "@cursorless/common"; import { FlashStyle } from "@cursorless/common"; import { getPreferredSnippet } from "../core/getPreferredSnippet"; import type { RangeUpdater } from "../core/updateSelections/RangeUpdater"; @@ -6,7 +6,6 @@ import { performEditsAndUpdateSelections } from "../core/updateSelections/update import type { ModifierStageFactory } from "../processTargets/ModifierStageFactory"; import type { ModifierStage } from "../processTargets/PipelineStages.types"; import { ModifyIfUntypedStage } from "../processTargets/modifiers/ConditionalModifierStages"; -import { ide } from "../singletons/ide.singleton"; import { transformSnippetVariables } from "../snippets/transformSnippetVariables"; import { SnippetParser } from "../snippets/vendor/vscodeSnippet/snippetParser"; import type { Target } from "../typings/target.types"; @@ -17,6 +16,7 @@ export default class WrapWithSnippet { private snippetParser = new SnippetParser(); constructor( + private ide: IDE, private rangeUpdater: RangeUpdater, private modifierStageFactory: ModifierStageFactory, ) { @@ -52,7 +52,7 @@ export default class WrapWithSnippet { targets: Target[], snippetDescription: WrapWithSnippetArg, ): Promise { - const editor = ide().getEditableTextEditor(ensureSingleEditor(targets)); + const editor = this.ide.getEditableTextEditor(ensureSingleEditor(targets)); const snippet = getPreferredSnippet( snippetDescription, editor.document.languageId, @@ -64,7 +64,7 @@ export default class WrapWithSnippet { const snippetString = parsedSnippet.toTextmateString(); - await flashTargets(ide(), targets, FlashStyle.pendingModification0); + await flashTargets(this.ide, targets, FlashStyle.pendingModification0); const targetSelections = targets.map((target) => target.contentSelection); diff --git a/packages/cursorless-engine/src/api/CursorlessEngineApi.ts b/packages/cursorless-engine/src/api/CursorlessEngineApi.ts index aab3b72b5d..6d70b8e3c7 100644 --- a/packages/cursorless-engine/src/api/CursorlessEngineApi.ts +++ b/packages/cursorless-engine/src/api/CursorlessEngineApi.ts @@ -22,7 +22,7 @@ export interface CursorlessEngine { customSpokenFormGenerator: CustomSpokenFormGenerator; storedTargets: StoredTargetMap; hatTokenMap: HatTokenMap; - injectIde: (ide: IDE | undefined) => void; + injectIde: (ide: IDE) => void; addCommandRunnerDecorator: ( commandRunnerDecorator: CommandRunnerDecorator, ) => void; diff --git a/packages/cursorless-engine/src/core/HatAllocator.ts b/packages/cursorless-engine/src/core/HatAllocator.ts index ec480ea22b..b3263f164a 100644 --- a/packages/cursorless-engine/src/core/HatAllocator.ts +++ b/packages/cursorless-engine/src/core/HatAllocator.ts @@ -1,6 +1,5 @@ -import type { Disposable, Hats, TokenHat } from "@cursorless/common"; -import { ide } from "../singletons/ide.singleton"; -import tokenGraphemeSplitter from "../singletons/tokenGraphemeSplitter.singleton"; +import type { Disposable, Hats, IDE, TokenHat } from "@cursorless/common"; +import type { TokenGraphemeSplitter } from "../tokenGraphemeSplitter"; import { allocateHats } from "../util/allocateHats"; import type { IndividualHatMap } from "./IndividualHatMap"; import { DecorationDebouncer } from "../util/DecorationDebouncer"; @@ -13,12 +12,14 @@ export class HatAllocator { private disposables: Disposable[] = []; constructor( + private ide: IDE, + private tokenGraphemeSplitter: TokenGraphemeSplitter, private hats: Hats, private context: Context, ) { - ide().disposeOnExit(this); + ide.disposeOnExit(this); - const debouncer = new DecorationDebouncer(ide().configuration, () => + const debouncer = new DecorationDebouncer(ide.configuration, () => this.allocateHats(), ); @@ -27,22 +28,22 @@ export class HatAllocator { this.hats.onDidChangeIsEnabled(debouncer.run), // An event that fires when a text document opens - ide().onDidOpenTextDocument(debouncer.run), + ide.onDidOpenTextDocument(debouncer.run), // An event that fires when a text document closes - ide().onDidCloseTextDocument(debouncer.run), + ide.onDidCloseTextDocument(debouncer.run), // An Event which fires when the active editor has changed. Note that the event also fires when the active editor changes to undefined. - ide().onDidChangeActiveTextEditor(debouncer.run), + ide.onDidChangeActiveTextEditor(debouncer.run), // An Event which fires when the array of visible editors has changed. - ide().onDidChangeVisibleTextEditors(debouncer.run), + ide.onDidChangeVisibleTextEditors(debouncer.run), // An event that is emitted when a text document is changed. This usually happens when the contents changes but also when other things like the dirty-state changes. - ide().onDidChangeTextDocument(debouncer.run), + ide.onDidChangeTextDocument(debouncer.run), // An Event which fires when the selection in an editor has changed. - ide().onDidChangeTextEditorSelection(debouncer.run), + ide.onDidChangeTextEditorSelection(debouncer.run), // An Event which fires when the visible ranges of an editor has changed. - ide().onDidChangeTextEditorVisibleRanges(debouncer.run), + ide.onDidChangeTextEditorVisibleRanges(debouncer.run), // Re-draw hats on grapheme splitting algorithm change in case they // changed their token hat splitting setting. - tokenGraphemeSplitter().registerAlgorithmChangeListener(debouncer.run), + tokenGraphemeSplitter.registerAlgorithmChangeListener(debouncer.run), debouncer, ); @@ -60,20 +61,21 @@ export class HatAllocator { // Forced graphemes won't have been normalized forceTokenHats = forceTokenHats?.map((tokenHat) => ({ ...tokenHat, - grapheme: tokenGraphemeSplitter().normalizeGrapheme(tokenHat.grapheme), + grapheme: this.tokenGraphemeSplitter.normalizeGrapheme(tokenHat.grapheme), })); const tokenHats = this.hats.isEnabled ? allocateHats({ - tokenGraphemeSplitter: tokenGraphemeSplitter(), + ide: this.ide, + tokenGraphemeSplitter: this.tokenGraphemeSplitter, enabledHatStyles: this.hats.enabledHatStyles, forceTokenHats, oldTokenHats: activeMap.tokenHats, - hatStability: ide().configuration.getOwnConfiguration( + hatStability: this.ide.configuration.getOwnConfiguration( "experimental.hatStability", ), - activeTextEditor: ide().activeTextEditor, - visibleTextEditors: ide().visibleTextEditors, + activeTextEditor: this.ide.activeTextEditor, + visibleTextEditors: this.ide.visibleTextEditors, }) : []; diff --git a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts index e571a553bb..103f4e6314 100644 --- a/packages/cursorless-engine/src/core/HatTokenMapImpl.ts +++ b/packages/cursorless-engine/src/core/HatTokenMapImpl.ts @@ -2,10 +2,11 @@ import type { CommandServerApi, HatTokenMap, Hats, + IDE, ReadOnlyHatMap, TokenHat, } from "@cursorless/common"; -import { ide } from "../singletons/ide.singleton"; +import type { TokenGraphemeSplitter } from "../tokenGraphemeSplitter"; import type { Debug } from "./Debug"; import { HatAllocator } from "./HatAllocator"; import { IndividualHatMap } from "./IndividualHatMap"; @@ -38,18 +39,24 @@ export class HatTokenMapImpl implements HatTokenMap { private hatAllocator: HatAllocator; constructor( + private ide: IDE, + tokenGraphemeSplitter: TokenGraphemeSplitter, rangeUpdater: RangeUpdater, private debug: Debug, hats: Hats, private commandServerApi: CommandServerApi, ) { - ide().disposeOnExit(this); - this.activeMap = new IndividualHatMap(rangeUpdater); + ide.disposeOnExit(this); + this.activeMap = new IndividualHatMap( + ide, + tokenGraphemeSplitter, + rangeUpdater, + ); this.getActiveMap = this.getActiveMap.bind(this); this.allocateHats = this.allocateHats.bind(this); - this.hatAllocator = new HatAllocator(hats, { + this.hatAllocator = new HatAllocator(ide, tokenGraphemeSplitter, hats, { getActiveMap: this.getActiveMap, }); } diff --git a/packages/cursorless-engine/src/core/IndividualHatMap.ts b/packages/cursorless-engine/src/core/IndividualHatMap.ts index 2269c082a2..a11f32f1d1 100644 --- a/packages/cursorless-engine/src/core/IndividualHatMap.ts +++ b/packages/cursorless-engine/src/core/IndividualHatMap.ts @@ -1,12 +1,13 @@ import type { HatStyleName, + IDE, ReadOnlyHatMap, TextDocument, Token, TokenHat, } from "@cursorless/common"; import { getKey } from "@cursorless/common"; -import tokenGraphemeSplitter from "../singletons/tokenGraphemeSplitter.singleton"; +import type { TokenGraphemeSplitter } from "../tokenGraphemeSplitter"; import { getMatcher } from "../tokenizer"; import type { FullRangeInfo } from "../typings/updateSelections"; import type { RangeUpdater } from "./updateSelections/RangeUpdater"; @@ -32,7 +33,11 @@ export class IndividualHatMap implements ReadOnlyHatMap { return this._tokenHats; } - constructor(private rangeUpdater: RangeUpdater) {} + constructor( + private ide: IDE, + private tokenGraphemeSplitter: TokenGraphemeSplitter, + private rangeUpdater: RangeUpdater, + ) {} private getDocumentTokenList(document: TextDocument) { const key = document.uri.toString(); @@ -50,7 +55,11 @@ export class IndividualHatMap implements ReadOnlyHatMap { } clone() { - const ret = new IndividualHatMap(this.rangeUpdater); + const ret = new IndividualHatMap( + this.ide, + this.tokenGraphemeSplitter, + this.rangeUpdater, + ); ret.setTokenHats(this._tokenHats); @@ -83,7 +92,10 @@ export class IndividualHatMap implements ReadOnlyHatMap { } private makeTokenLive(token: Token): LiveToken { - const { tokenMatcher } = getMatcher(token.editor.document.languageId); + const { tokenMatcher } = getMatcher( + this.ide, + token.editor.document.languageId, + ); const liveToken: LiveToken = { ...token, @@ -112,7 +124,7 @@ export class IndividualHatMap implements ReadOnlyHatMap { getToken(hatStyle: HatStyleName, character: string): Token | undefined { this.checkExpired(); return this.map[ - getKey(hatStyle, tokenGraphemeSplitter().normalizeGrapheme(character)) + getKey(hatStyle, this.tokenGraphemeSplitter.normalizeGrapheme(character)) ]; } diff --git a/packages/cursorless-engine/src/core/updateSelections/RangeUpdater.ts b/packages/cursorless-engine/src/core/updateSelections/RangeUpdater.ts index 053e0bb77f..fe78cb4927 100644 --- a/packages/cursorless-engine/src/core/updateSelections/RangeUpdater.ts +++ b/packages/cursorless-engine/src/core/updateSelections/RangeUpdater.ts @@ -1,12 +1,12 @@ import type { Disposable, Edit, + IDE, TextDocument, TextDocumentChangeEvent, TextDocumentContentChangeEvent, } from "@cursorless/common"; import { pull } from "lodash-es"; -import { ide } from "../../singletons/ide.singleton"; import type { ExtendedTextDocumentChangeEvent, FullRangeInfo, @@ -23,7 +23,7 @@ export class RangeUpdater { private replaceEditLists: Map = new Map(); private disposables: Disposable[] = []; - constructor() { + constructor(private ide: IDE) { this.listenForDocumentChanges(); } @@ -116,7 +116,7 @@ export class RangeUpdater { private listenForDocumentChanges() { this.disposables.push( - ide().onDidChangeTextDocument((event: TextDocumentChangeEvent) => { + this.ide.onDidChangeTextDocument((event: TextDocumentChangeEvent) => { const documentReplaceEditLists = this.replaceEditLists.get(event.document.uri.toString()) ?? []; @@ -138,7 +138,7 @@ export class RangeUpdater { ); }), - ide().onDidCloseTextDocument((document) => { + this.ide.onDidCloseTextDocument((document) => { const key = document.uri.toString(); this.rangeInfoLists.delete(key); this.replaceEditLists.delete(key); diff --git a/packages/cursorless-engine/src/cursorlessEngine.ts b/packages/cursorless-engine/src/cursorlessEngine.ts index b60a57f364..9f63c477f3 100644 --- a/packages/cursorless-engine/src/cursorlessEngine.ts +++ b/packages/cursorless-engine/src/cursorlessEngine.ts @@ -8,7 +8,7 @@ import type { TalonSpokenForms, TreeSitter, } from "@cursorless/common"; -import { ensureCommandShape } from "@cursorless/common"; +import { ensureCommandShape, PassthroughIDE } from "@cursorless/common"; import { KeyboardTargetUpdater } from "./KeyboardTargetUpdater"; import type { CommandRunnerDecorator, @@ -26,10 +26,8 @@ import { DisabledSnippets } from "./disabledComponents/DisabledSnippets"; import { DisabledTalonSpokenForms } from "./disabledComponents/DisabledTalonSpokenForms"; import { DisabledTreeSitter } from "./disabledComponents/DisabledTreeSitter"; import { CustomSpokenFormGeneratorImpl } from "./generateSpokenForm/CustomSpokenFormGeneratorImpl"; -import { - LanguageDefinitionsImpl, - type LanguageDefinitions, -} from "./languages/LanguageDefinitions"; +import type { LanguageDefinitions } from "./languages/LanguageDefinitions"; +import { LanguageDefinitionsImpl } from "./languages/LanguageDefinitions"; import { ModifierStageFactoryImpl } from "./processTargets/ModifierStageFactoryImpl"; import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandlers"; import { runCommand } from "./runCommand"; @@ -38,7 +36,7 @@ import { ScopeRangeProvider } from "./scopeProviders/ScopeRangeProvider"; import { ScopeRangeWatcher } from "./scopeProviders/ScopeRangeWatcher"; import { ScopeSupportChecker } from "./scopeProviders/ScopeSupportChecker"; import { ScopeSupportWatcher } from "./scopeProviders/ScopeSupportWatcher"; -import { injectIde } from "./singletons/ide.singleton"; +import { TokenGraphemeSplitter } from "./tokenGraphemeSplitter/tokenGraphemeSplitter"; export interface EngineProps { ide: IDE; @@ -59,33 +57,46 @@ export async function createCursorlessEngine({ talonSpokenForms = new DisabledTalonSpokenForms(), snippets = new DisabledSnippets(), }: EngineProps): Promise { - injectIde(ide); + const injectedIde = new PassthroughIDE(ide); - const debug = new Debug(ide); - const rangeUpdater = new RangeUpdater(); + const debug = new Debug(injectedIde); + const rangeUpdater = new RangeUpdater(injectedIde); + const tokenGraphemeSplitter = new TokenGraphemeSplitter(injectedIde); const storedTargets = new StoredTargetMap(); - const keyboardTargetUpdater = new KeyboardTargetUpdater(ide, storedTargets); + const keyboardTargetUpdater = new KeyboardTargetUpdater( + injectedIde, + storedTargets, + ); const customSpokenFormGenerator = new CustomSpokenFormGeneratorImpl( + injectedIde, talonSpokenForms, ); const hatTokenMap = hats != null - ? new HatTokenMapImpl(rangeUpdater, debug, hats, commandServerApi) + ? new HatTokenMapImpl( + injectedIde, + tokenGraphemeSplitter, + rangeUpdater, + debug, + hats, + commandServerApi, + ) : new DisabledHatTokenMap(); void hatTokenMap.allocateHats(); const languageDefinitions = treeSitterQueryProvider ? await LanguageDefinitionsImpl.create( - ide, + injectedIde, treeSitter, treeSitterQueryProvider, ) : new DisabledLanguageDefinitions(); - ide.disposeOnExit( + injectedIde.disposeOnExit( rangeUpdater, + tokenGraphemeSplitter, languageDefinitions, hatTokenMap, debug, @@ -100,6 +111,7 @@ export async function createCursorlessEngine({ const runCommandClosure = (command: Command) => { previousCommand = command; return runCommand( + injectedIde, treeSitter, commandServerApi, debug, @@ -133,7 +145,7 @@ export async function createCursorlessEngine({ }, }, scopeProvider: createScopeProvider( - ide, + injectedIde, languageDefinitions, storedTargets, customSpokenFormGenerator, @@ -141,7 +153,7 @@ export async function createCursorlessEngine({ customSpokenFormGenerator, storedTargets, hatTokenMap, - injectIde, + injectIde: (ide) => injectedIde.setIde(ide), addCommandRunnerDecorator: (decorator: CommandRunnerDecorator) => { commandRunnerDecorators.push(decorator); }, @@ -154,7 +166,10 @@ function createScopeProvider( storedTargets: StoredTargetMap, customSpokenFormGenerator: CustomSpokenFormGeneratorImpl, ): ScopeProvider { - const scopeHandlerFactory = new ScopeHandlerFactoryImpl(languageDefinitions); + const scopeHandlerFactory = new ScopeHandlerFactoryImpl( + ide, + languageDefinitions, + ); const rangeProvider = new ScopeRangeProvider( scopeHandlerFactory, @@ -166,12 +181,14 @@ function createScopeProvider( ); const rangeWatcher = new ScopeRangeWatcher( + ide, languageDefinitions, rangeProvider, ); const supportChecker = new ScopeSupportChecker(scopeHandlerFactory); const infoProvider = new ScopeInfoProvider(customSpokenFormGenerator); const supportWatcher = new ScopeSupportWatcher( + ide, languageDefinitions, supportChecker, infoProvider, diff --git a/packages/cursorless-engine/src/customCommandGrammar/lexer.test.ts b/packages/cursorless-engine/src/customCommandGrammar/lexer.test.ts index 307eebc680..f2d5ebdab0 100644 --- a/packages/cursorless-engine/src/customCommandGrammar/lexer.test.ts +++ b/packages/cursorless-engine/src/customCommandGrammar/lexer.test.ts @@ -1,5 +1,4 @@ import * as assert from "assert"; -import { unitTestSetup } from "../testUtil/unitTestSetup"; import type { NearleyLexer, NearleyToken } from "./CommandLexer"; import { lexer } from "./lexer"; @@ -87,8 +86,6 @@ const fixtures: Fixture[] = [ ]; suite("custom grammar: lexer", () => { - unitTestSetup(); - fixtures.forEach(({ input, expectedOutput }) => { test(input, () => { assert.deepStrictEqual( diff --git a/packages/cursorless-engine/src/disabledComponents/DisabledTalonSpokenForms.ts b/packages/cursorless-engine/src/disabledComponents/DisabledTalonSpokenForms.ts index c188a8ddf0..35f3400bd4 100644 --- a/packages/cursorless-engine/src/disabledComponents/DisabledTalonSpokenForms.ts +++ b/packages/cursorless-engine/src/disabledComponents/DisabledTalonSpokenForms.ts @@ -1,8 +1,5 @@ -import { - DisabledCustomSpokenFormsError, - type SpokenFormEntry, - type TalonSpokenForms, -} from "@cursorless/common"; +import type { SpokenFormEntry, TalonSpokenForms } from "@cursorless/common"; +import { DisabledCustomSpokenFormsError } from "@cursorless/common"; export class DisabledTalonSpokenForms implements TalonSpokenForms { getSpokenFormEntries(): Promise { diff --git a/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.test.ts b/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.test.ts index 4cbbf8e5fc..b2baf21344 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.test.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.test.ts @@ -1,12 +1,12 @@ import assert from "node:assert"; import { CustomSpokenFormGeneratorImpl } from "./CustomSpokenFormGeneratorImpl"; -import { LATEST_VERSION, asyncSafety } from "@cursorless/common"; +import { FakeIDE, LATEST_VERSION, asyncSafety } from "@cursorless/common"; suite("CustomSpokenFormGeneratorImpl", async function () { test( "basic", asyncSafety(async () => { - const generator = new CustomSpokenFormGeneratorImpl({ + const generator = new CustomSpokenFormGeneratorImpl(new FakeIDE(), { async getSpokenFormEntries() { return [ { diff --git a/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.ts b/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.ts index 2f9dad87cd..a3ddaea5cd 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/CustomSpokenFormGeneratorImpl.ts @@ -2,6 +2,7 @@ import type { ActionType, CommandComplete, Disposable, + IDE, Listener, ScopeType, TalonSpokenForms, @@ -25,8 +26,8 @@ export class CustomSpokenFormGeneratorImpl implements CustomSpokenFormGenerator */ public readonly customSpokenFormsInitialized: Promise; - constructor(talonSpokenForms: TalonSpokenForms) { - this.customSpokenForms = new CustomSpokenForms(talonSpokenForms); + constructor(ide: IDE, talonSpokenForms: TalonSpokenForms) { + this.customSpokenForms = new CustomSpokenForms(ide, talonSpokenForms); this.customSpokenFormsInitialized = this.customSpokenForms.customSpokenFormsInitialized; this.spokenFormGenerator = new SpokenFormGenerator( diff --git a/packages/cursorless-engine/src/index.ts b/packages/cursorless-engine/src/index.ts index d8bde764d8..b4bcc4cdc1 100644 --- a/packages/cursorless-engine/src/index.ts +++ b/packages/cursorless-engine/src/index.ts @@ -11,7 +11,6 @@ export * from "./generateSpokenForm/defaultSpokenForms/surroundingPairsDelimiter export * from "./generateSpokenForm/generateSpokenForm"; export * from "./languages/TreeSitterQuery/TreeSitterQueryCache"; export * from "./processTargets/modifiers/scopeHandlers/ScopeHandlerCache"; -export * from "./singletons/ide.singleton"; export * from "./spokenForms/defaultSpokenFormMap"; export * from "./testUtil/extractTargetKeys"; export * from "./testUtil/plainObjectToTarget"; diff --git a/packages/cursorless-engine/src/languages/LanguageDefinition.ts b/packages/cursorless-engine/src/languages/LanguageDefinition.ts index 99da7defb7..09949b8202 100644 --- a/packages/cursorless-engine/src/languages/LanguageDefinition.ts +++ b/packages/cursorless-engine/src/languages/LanguageDefinition.ts @@ -19,6 +19,8 @@ import { validateQueryCaptures } from "./TreeSitterQuery/validateQueryCaptures"; */ export class LanguageDefinition { private constructor( + private ide: IDE, + /** * The tree-sitter query used to extract scopes for the given language. * Note that this query contains patterns for all scope types that the @@ -64,9 +66,9 @@ export class LanguageDefinition { ); } - const query = TreeSitterQuery.create(languageId, treeSitter, rawQuery); + const query = TreeSitterQuery.create(ide, languageId, treeSitter, rawQuery); - return new LanguageDefinition(query); + return new LanguageDefinition(ide, query); } /** @@ -79,7 +81,11 @@ export class LanguageDefinition { return undefined; } - return new TreeSitterScopeHandler(this.query, scopeType as SimpleScopeType); + return new TreeSitterScopeHandler( + this.ide, + this.query, + scopeType as SimpleScopeType, + ); } /** @@ -163,7 +169,7 @@ async function readQueryFileAndImports( } if (doValidation) { - validateQueryCaptures(queryName, rawQuery); + validateQueryCaptures(ide, queryName, rawQuery); } rawQueryStrings[queryName] = rawQuery; diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts index 9fc4dbf45f..8cffcfad0a 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/TreeSitterQuery.ts @@ -1,15 +1,15 @@ import type { + IDE, Mutable, Position, TextDocument, TreeSitter, } from "@cursorless/common"; import type * as treeSitter from "web-tree-sitter"; -import { ide } from "../../singletons/ide.singleton"; +import type { ScopeCaptureName } from "./captureNames"; import { getNormalizedCaptureIndex, getNormalizedCaptureName, - type ScopeCaptureName, } from "./captureNames"; import { checkCaptureStartEnd } from "./checkCaptureStartEnd"; import { getNodeRange } from "./getNodeRange"; @@ -38,6 +38,7 @@ export class TreeSitterQuery { private shouldCheckCaptures: boolean; private constructor( + private ide: IDE, private treeSitter: TreeSitter, /** @@ -52,17 +53,18 @@ export class TreeSitterQuery { */ private patternPredicates: PatternPredicate[][], ) { - this.shouldCheckCaptures = ide().runMode !== "production"; + this.shouldCheckCaptures = ide.runMode !== "production"; } static create( + ide: IDE, languageId: string, treeSitter: TreeSitter, query: treeSitter.Query, ) { - const predicates = parsePredicatesWithErrorHandling(languageId, query); + const predicates = parsePredicatesWithErrorHandling(ide, languageId, query); - return new TreeSitterQuery(treeSitter, query, predicates); + return new TreeSitterQuery(ide, treeSitter, query, predicates); } hasCapture(name: string): boolean { @@ -239,7 +241,7 @@ export class TreeSitterQuery { } if (this.shouldCheckCaptures) { - checkCaptures(Array.from(map.values())); + checkCaptures(this.ide, Array.from(map.values())); } return result; @@ -289,6 +291,7 @@ export class TreeSitterQuery { if (this.shouldCheckCaptures) { checkCaptures( + this.ide, Array.from(map.values(), (v) => ({ captures: v.captures.map((c) => createTestQueryCapture(c.name, getNodeRange(c.node)), @@ -301,13 +304,13 @@ export class TreeSitterQuery { } } -function checkCaptures(matches: { captures: QueryCapture[] }[]) { +function checkCaptures(ide: IDE, matches: { captures: QueryCapture[] }[]) { for (const match of matches) { const capturesAreValid = checkCaptureStartEnd( rewriteStartOfEndOf(match.captures), - ide().messages, + ide.messages, ); - if (!capturesAreValid && ide().runMode === "test") { + if (!capturesAreValid && ide.runMode === "test") { throw new Error("Invalid captures"); } } diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/parsePredicatesWithErrorHandling.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/parsePredicatesWithErrorHandling.ts index e231b2e3d2..a390154896 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/parsePredicatesWithErrorHandling.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/parsePredicatesWithErrorHandling.ts @@ -1,11 +1,12 @@ +import type { IDE } from "@cursorless/common"; import { showError } from "@cursorless/common"; import type { Query } from "web-tree-sitter"; -import { ide } from "../../singletons/ide.singleton"; import { parsePredicates } from "./parsePredicates"; import { predicateToString } from "./predicateToString"; import type { PatternPredicate } from "./QueryCapture"; export function parsePredicatesWithErrorHandling( + ide: IDE, languageId: string, query: Query, ): PatternPredicate[][] { @@ -22,7 +23,7 @@ export function parsePredicatesWithErrorHandling( ].join(", "); void showError( - ide().messages, + ide.messages, "TreeSitterQuery.parsePredicates", `Error parsing predicate for ${context}: ${error.error}`, ); @@ -30,7 +31,7 @@ export function parsePredicatesWithErrorHandling( // We show errors to the user, but we don't want to crash the extension // unless we're in test mode - if (ide().runMode === "test") { + if (ide.runMode === "test") { throw new Error("Invalid predicates"); } } diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/validateQueryCaptures.test.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/validateQueryCaptures.test.ts index 08c54a7e92..c48b1054e2 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/validateQueryCaptures.test.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/validateQueryCaptures.test.ts @@ -1,6 +1,5 @@ import { FakeIDE } from "@cursorless/common"; import assert from "assert"; -import { injectIde } from "../../singletons/ide.singleton"; import { validateQueryCaptures } from "./validateQueryCaptures"; const testCases: { name: string; isOk: boolean; content: string }[] = [ @@ -89,16 +88,14 @@ const testCases: { name: string; isOk: boolean; content: string }[] = [ ]; suite("validateQueryCaptures", function () { - suiteSetup(() => { - injectIde(new FakeIDE()); - }); + const ide = new FakeIDE(); for (const testCase of testCases) { const name = [testCase.isOk ? "OK" : "Error", testCase.name].join(": "); test(name, () => { const runTest = () => - validateQueryCaptures(testCase.name, testCase.content); + validateQueryCaptures(ide, testCase.name, testCase.content); if (testCase.isOk) { assert.doesNotThrow(runTest); diff --git a/packages/cursorless-engine/src/languages/TreeSitterQuery/validateQueryCaptures.ts b/packages/cursorless-engine/src/languages/TreeSitterQuery/validateQueryCaptures.ts index 46b86337b1..97f6e742f1 100644 --- a/packages/cursorless-engine/src/languages/TreeSitterQuery/validateQueryCaptures.ts +++ b/packages/cursorless-engine/src/languages/TreeSitterQuery/validateQueryCaptures.ts @@ -1,5 +1,4 @@ -import { showError } from "@cursorless/common"; -import { ide } from "../../singletons/ide.singleton"; +import { showError, type IDE } from "@cursorless/common"; import { isCaptureAllowed } from "./captureNames"; // Not a comment. ie line is not starting with `;;` @@ -7,7 +6,11 @@ import { isCaptureAllowed } from "./captureNames"; // Capture starts with `@` and is followed by words and/or dots const capturePattern = /^(?!;;).*(? new UntypedTarget({ editor: selection.editor, diff --git a/packages/cursorless-engine/src/processTargets/marks/ExplicitMarkStage.ts b/packages/cursorless-engine/src/processTargets/marks/ExplicitMarkStage.ts index e3c1b2f5ea..4dffc7b492 100644 --- a/packages/cursorless-engine/src/processTargets/marks/ExplicitMarkStage.ts +++ b/packages/cursorless-engine/src/processTargets/marks/ExplicitMarkStage.ts @@ -1,12 +1,14 @@ -import type { ExplicitMark } from "@cursorless/common"; +import type { ExplicitMark, IDE } from "@cursorless/common"; import { Range } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { MarkStage } from "../PipelineStages.types"; import { UntypedTarget } from "../targets"; -import { ide } from "../../singletons/ide.singleton"; export class ExplicitMarkStage implements MarkStage { - constructor(private mark: ExplicitMark) {} + constructor( + private ide: IDE, + private mark: ExplicitMark, + ) {} run(): Target[] { const { @@ -14,7 +16,7 @@ export class ExplicitMarkStage implements MarkStage { range: { start, end }, } = this.mark; - const editor = ide().visibleTextEditors.find((e) => e.id === editorId); + const editor = this.ide.visibleTextEditors.find((e) => e.id === editorId); if (editor == null) { throw new Error(`Couldn't find editor '${editorId}'`); diff --git a/packages/cursorless-engine/src/processTargets/marks/ImplicitStage.ts b/packages/cursorless-engine/src/processTargets/marks/ImplicitStage.ts index 232b8c506c..1a2175aab4 100644 --- a/packages/cursorless-engine/src/processTargets/marks/ImplicitStage.ts +++ b/packages/cursorless-engine/src/processTargets/marks/ImplicitStage.ts @@ -1,12 +1,14 @@ -import { ide } from "../../singletons/ide.singleton"; +import type { IDE } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { MarkStage } from "../PipelineStages.types"; import { ImplicitTarget } from "../targets"; import { getActiveSelections } from "./getActiveSelections"; export class ImplicitStage implements MarkStage { + constructor(private ide: IDE) {} + run(): Target[] { - return getActiveSelections(ide()).map( + return getActiveSelections(this.ide).map( (selection) => new ImplicitTarget({ editor: selection.editor, diff --git a/packages/cursorless-engine/src/processTargets/marks/LineNumberStage.ts b/packages/cursorless-engine/src/processTargets/marks/LineNumberStage.ts index 9803e67430..3b7a3b92d6 100644 --- a/packages/cursorless-engine/src/processTargets/marks/LineNumberStage.ts +++ b/packages/cursorless-engine/src/processTargets/marks/LineNumberStage.ts @@ -1,18 +1,21 @@ import type { + IDE, LineNumberMark, LineNumberType, TextEditor, } from "@cursorless/common"; -import { ide } from "../../singletons/ide.singleton"; import type { MarkStage } from "../PipelineStages.types"; import type { LineTarget } from "../targets"; import { createLineTarget } from "../targets"; export class LineNumberStage implements MarkStage { - constructor(private mark: LineNumberMark) {} + constructor( + private ide: IDE, + private mark: LineNumberMark, + ) {} run(): LineTarget[] { - const editor = ide().activeTextEditor; + const editor = this.ide.activeTextEditor; if (editor == null) { return []; } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/InteriorStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/InteriorStage.ts index 796f2beab4..33bbc4d026 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/InteriorStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/InteriorStage.ts @@ -1,8 +1,7 @@ +import type { InteriorOnlyModifier, ScopeType } from "@cursorless/common"; import { NoContainingScopeError, UnsupportedScopeError, - type InteriorOnlyModifier, - type ScopeType, } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts index 03e9fb7ca3..488c87c471 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts @@ -1,8 +1,5 @@ -import { - NoContainingScopeError, - type Position, - type PreferredScopeModifier, -} from "@cursorless/common"; +import type { Position, PreferredScopeModifier } from "@cursorless/common"; +import { NoContainingScopeError } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; import type { ModifierStage } from "../PipelineStages.types"; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CharacterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CharacterScopeHandler.ts index de20d3b4e2..23a9fbda8d 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CharacterScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CharacterScopeHandler.ts @@ -1,11 +1,12 @@ -import type { Direction, ScopeType } from "@cursorless/common"; +import type { Direction, IDE, ScopeType } from "@cursorless/common"; import { imap } from "itertools"; -import { NestedScopeHandler } from "./NestedScopeHandler"; import { getMatcher } from "../../../tokenizer"; import { generateMatchesInRange } from "../../../util/getMatchesInRange"; import { PlainTarget } from "../../targets"; import { isPreferredOverHelper } from "./isPreferredOverHelper"; +import { NestedScopeHandler } from "./NestedScopeHandler"; import type { TargetScope } from "./scope.types"; +import type { ScopeHandlerFactory } from "./ScopeHandlerFactory"; /** * The regex used to split a range into characters. Combines letters with their @@ -21,6 +22,15 @@ export class CharacterScopeHandler extends NestedScopeHandler { public readonly scopeType = { type: "character" } as const; public readonly iterationScopeType = { type: "token" } as const; + constructor( + private ide: IDE, + scopeHandlerFactory: ScopeHandlerFactory, + scopeType: ScopeType, + languageId: string, + ) { + super(scopeHandlerFactory, scopeType, languageId); + } + protected get searchScopeType(): ScopeType { return { type: "line" }; } @@ -50,7 +60,7 @@ export class CharacterScopeHandler extends NestedScopeHandler { scopeA: TargetScope, scopeB: TargetScope, ): boolean | undefined { - const { identifierMatcher } = getMatcher(this.languageId); + const { identifierMatcher } = getMatcher(this.ide, this.languageId); // Regexes indicating preferences. We prefer identifiers, preferred // symbols, then nonwhitespace. return isPreferredOverHelper(scopeA, scopeB, [ diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/createTargetScope.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/createTargetScope.ts index 8597228a28..f6e4a550c8 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/createTargetScope.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/createTargetScope.ts @@ -1,4 +1,5 @@ -import { type TextEditor, Range } from "@cursorless/common"; +import type { TextEditor } from "@cursorless/common"; +import { Range } from "@cursorless/common"; import { ScopeTypeTarget } from "../../../targets"; import type { TargetScope } from "../scope.types"; import { getCollectionItemRemovalRange } from "../util/getCollectionItemRemovalRange"; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/getSeparatorOccurrences.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/getSeparatorOccurrences.ts index 5d78f70d59..7ce907aeff 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/getSeparatorOccurrences.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/getSeparatorOccurrences.ts @@ -1,4 +1,5 @@ -import { matchAll, Range, type TextDocument } from "@cursorless/common"; +import type { TextDocument } from "@cursorless/common"; +import { matchAll, Range } from "@cursorless/common"; const separator = ","; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/IdentifierScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/IdentifierScopeHandler.ts index 87d9a0c572..6152984a0e 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/IdentifierScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/IdentifierScopeHandler.ts @@ -1,16 +1,26 @@ +import type { Direction, IDE, ScopeType } from "@cursorless/common"; import { imap } from "itertools"; -import { NestedScopeHandler } from "./NestedScopeHandler"; import { getMatcher } from "../../../tokenizer"; -import type { Direction } from "@cursorless/common"; import { generateMatchesInRange } from "../../../util/getMatchesInRange"; import { TokenTarget } from "../../targets"; +import { NestedScopeHandler } from "./NestedScopeHandler"; import type { TargetScope } from "./scope.types"; +import type { ScopeHandlerFactory } from "./ScopeHandlerFactory"; export class IdentifierScopeHandler extends NestedScopeHandler { public readonly scopeType = { type: "identifier" } as const; public readonly iterationScopeType = { type: "line" } as const; + private regex: RegExp; - private regex: RegExp = getMatcher(this.languageId).identifierMatcher; + constructor( + private ide: IDE, + scopeHandlerFactory: ScopeHandlerFactory, + scopeType: ScopeType, + languageId: string, + ) { + super(scopeHandlerFactory, scopeType, languageId); + this.regex = getMatcher(ide, this.languageId).identifierMatcher; + } protected generateScopesInSearchScope( direction: Direction, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellApiScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellApiScopeHandler.ts index 3260ee6c7b..f6be678b23 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellApiScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellApiScopeHandler.ts @@ -1,11 +1,11 @@ -import { - Range, - type Direction, - type NotebookCell, - type Position, - type TextEditor, +import type { + IDE, + Direction, + NotebookCell, + Position, + TextEditor, } from "@cursorless/common"; -import { ide } from "../../../singletons/ide.singleton"; +import { Range } from "@cursorless/common"; import { NotebookCellTarget } from "../../targets"; import { BaseScopeHandler } from "./BaseScopeHandler"; import type { TargetScope } from "./scope.types"; @@ -19,7 +19,7 @@ export class NotebookCellApiScopeHandler extends BaseScopeHandler { public readonly iterationScopeType = { type: "document" } as const; protected isHierarchical = false; - constructor() { + constructor(private ide: IDE) { super(); } @@ -29,21 +29,28 @@ export class NotebookCellApiScopeHandler extends BaseScopeHandler { direction: Direction, hints: ScopeIteratorRequirements, ): Iterable { - const cells = getNotebookCells(editor, position, direction, hints); + const cells = getNotebookCells( + this.ide, + editor, + position, + direction, + hints, + ); for (const cell of cells) { - yield createTargetScope(cell); + yield createTargetScope(this.ide, cell); } } } function getNotebookCells( + ide: IDE, editor: TextEditor, position: Position, direction: Direction, hints: ScopeIteratorRequirements, ) { - const nb = getNotebook(editor); + const nb = getNotebook(ide, editor); if (nb == null) { return []; @@ -77,9 +84,9 @@ function getNotebookCells( : notebook.cells.slice(0, cell.index + 1).reverse(); } -function getNotebook(editor: TextEditor) { +function getNotebook(ide: IDE, editor: TextEditor) { const uri = editor.document.uri.toString(); - for (const notebook of ide().visibleNotebookEditors) { + for (const notebook of ide.visibleNotebookEditors) { for (const cell of notebook.cells) { if (cell.document.uri.toString() === uri) { return { notebook, cell }; @@ -89,8 +96,8 @@ function getNotebook(editor: TextEditor) { return undefined; } -function createTargetScope(cell: NotebookCell): TargetScope { - const editor = getEditor(cell); +function createTargetScope(ide: IDE, cell: NotebookCell): TargetScope { + const editor = getEditor(ide, cell); const contentRange = editor.document.range; return { editor, @@ -105,9 +112,9 @@ function createTargetScope(cell: NotebookCell): TargetScope { }; } -function getEditor(cell: NotebookCell) { +function getEditor(ide: IDE, cell: NotebookCell) { const uri = cell.document.uri.toString(); - for (const editor of ide().visibleTextEditors) { + for (const editor of ide.visibleTextEditors) { if (editor.document.uri.toString() === uri) { return editor; } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts index 07d4043f4d..b8b3b75aa4 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts @@ -1,5 +1,6 @@ import { type Direction, + type IDE, type Position, type ScopeType, type TextEditor, @@ -7,7 +8,6 @@ import { import type { LanguageDefinitions } from "../../../languages/LanguageDefinitions"; import { BaseScopeHandler } from "./BaseScopeHandler"; import { NotebookCellApiScopeHandler } from "./NotebookCellApiScopeHandler"; -import { SortedScopeHandler } from "./SortedScopeHandler"; import type { TargetScope } from "./scope.types"; import type { ComplexScopeType, @@ -15,6 +15,7 @@ import type { ScopeIteratorRequirements, } from "./scopeHandler.types"; import type { ScopeHandlerFactory } from "./ScopeHandlerFactory"; +import { SortedScopeHandler } from "./SortedScopeHandler"; export class NotebookCellScopeHandler extends BaseScopeHandler { public readonly scopeType = { type: "notebookCell" } as const; @@ -26,6 +27,7 @@ export class NotebookCellScopeHandler extends BaseScopeHandler { } constructor( + ide: IDE, scopeHandlerFactory: ScopeHandlerFactory, languageDefinitions: LanguageDefinitions, _scopeType: ScopeType, @@ -34,7 +36,7 @@ export class NotebookCellScopeHandler extends BaseScopeHandler { super(); this.scopeHandler = (() => { - const apiScopeHandler = new NotebookCellApiScopeHandler(); + const apiScopeHandler = new NotebookCellApiScopeHandler(ide); const languageScopeHandler = languageDefinitions .get(languageId) diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts index a78e17bc4d..38e07c09e0 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts @@ -1,6 +1,7 @@ import { pseudoScopes, UnsupportedScopeError, + type IDE, type ScopeType, } from "@cursorless/common"; import type { LanguageDefinitions } from "../../../languages/LanguageDefinitions"; @@ -46,7 +47,10 @@ import { WordScopeHandler } from "./WordScopeHandler/WordScopeHandler"; * undefined if the given scope type / language id combination is not supported. */ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { - constructor(private languageDefinitions: LanguageDefinitions) { + constructor( + private ide: IDE, + private languageDefinitions: LanguageDefinitions, + ) { this.maybeCreate = this.maybeCreate.bind(this); this.create = this.create.bind(this); } @@ -57,13 +61,18 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { ): ScopeHandler | undefined { switch (scopeType.type) { case "character": - return new CharacterScopeHandler(this, scopeType, languageId); + return new CharacterScopeHandler(this.ide, this, scopeType, languageId); case "word": - return new WordScopeHandler(this, scopeType, languageId); + return new WordScopeHandler(this.ide, this, scopeType, languageId); case "token": - return new TokenScopeHandler(this, scopeType, languageId); + return new TokenScopeHandler(this.ide, this, scopeType, languageId); case "identifier": - return new IdentifierScopeHandler(this, scopeType, languageId); + return new IdentifierScopeHandler( + this.ide, + this, + scopeType, + languageId, + ); case "line": case "fullLine": return new LineScopeHandler({ type: scopeType.type }, languageId); @@ -113,6 +122,7 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { ); case "notebookCell": return new NotebookCellScopeHandler( + this.ide, this, this.languageDefinitions, scopeType, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getDelimiterOccurrences.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getDelimiterOccurrences.ts index 6da8b7c254..1f8c3ef30c 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getDelimiterOccurrences.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getDelimiterOccurrences.ts @@ -1,4 +1,5 @@ -import { matchAllIterator, Range, type TextDocument } from "@cursorless/common"; +import type { TextDocument } from "@cursorless/common"; +import { matchAllIterator, Range } from "@cursorless/common"; import type { LanguageDefinition } from "../../../../languages/LanguageDefinition"; import type { QueryCapture } from "../../../../languages/TreeSitterQuery/QueryCapture"; import { OneWayNestedRangeFinder } from "../util/OneWayNestedRangeFinder"; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getIndividualDelimiters.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getIndividualDelimiters.ts index a2cd5f818a..3d08fc890e 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getIndividualDelimiters.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/getIndividualDelimiters.ts @@ -6,7 +6,8 @@ import type { import { isString } from "@cursorless/common"; import { concat, uniq } from "lodash-es"; import { complexDelimiterMap, getSimpleDelimiterMap } from "./delimiterMaps"; -import { DelimiterSide, type IndividualDelimiter } from "./types"; +import type { IndividualDelimiter } from "./types"; +import { DelimiterSide } from "./types"; /** * Given a list of delimiters, returns a list where each element corresponds to diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TokenScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TokenScopeHandler.ts index d714640a9b..b9b2ef2ee1 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TokenScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TokenScopeHandler.ts @@ -1,19 +1,29 @@ -import type { Direction } from "@cursorless/common"; +import type { Direction, IDE, ScopeType } from "@cursorless/common"; import { imap } from "itertools"; -import { NestedScopeHandler } from "./NestedScopeHandler"; import { getMatcher } from "../../../tokenizer"; import { generateMatchesInRange } from "../../../util/getMatchesInRange"; import { TokenTarget } from "../../targets"; import { isPreferredOverHelper } from "./isPreferredOverHelper"; +import { NestedScopeHandler } from "./NestedScopeHandler"; import type { TargetScope } from "./scope.types"; +import type { ScopeHandlerFactory } from "./ScopeHandlerFactory"; const PREFERRED_SYMBOLS_REGEX = /[$]/g; export class TokenScopeHandler extends NestedScopeHandler { public readonly scopeType = { type: "token" } as const; public readonly iterationScopeType = { type: "line" } as const; + private regex: RegExp; - private regex: RegExp = getMatcher(this.languageId).tokenMatcher; + constructor( + private ide: IDE, + scopeHandlerFactory: ScopeHandlerFactory, + scopeType: ScopeType, + languageId: string, + ) { + super(scopeHandlerFactory, scopeType, languageId); + this.regex = getMatcher(ide, this.languageId).tokenMatcher; + } protected generateScopesInSearchScope( direction: Direction, @@ -39,7 +49,7 @@ export class TokenScopeHandler extends NestedScopeHandler { scopeA: TargetScope, scopeB: TargetScope, ): boolean | undefined { - const { identifierMatcher } = getMatcher(this.languageId); + const { identifierMatcher } = getMatcher(this.ide, this.languageId); // Regexes indicating preferences. We prefer identifiers then preferred // symbols. return isPreferredOverHelper(scopeA, scopeB, [ diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/BaseTreeSitterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/BaseTreeSitterScopeHandler.ts index cbe8912d4a..c3339f6716 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/BaseTreeSitterScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/BaseTreeSitterScopeHandler.ts @@ -1,9 +1,8 @@ -import type { Direction, Position, TextEditor } from "@cursorless/common"; +import type { Direction, IDE, Position, TextEditor } from "@cursorless/common"; import { showError } from "@cursorless/common"; import { uniqWith } from "lodash-es"; import type { TreeSitterQuery } from "../../../../languages/TreeSitterQuery"; import type { QueryMatch } from "../../../../languages/TreeSitterQuery/QueryCapture"; -import { ide } from "../../../../singletons/ide.singleton"; import { BaseScopeHandler } from "../BaseScopeHandler"; import { compareTargetScopes } from "../compareTargetScopes"; import type { TargetScope } from "../scope.types"; @@ -14,7 +13,10 @@ import { mergeAdjacentBy } from "./mergeAdjacentBy"; /** Base scope handler to use for both tree-sitter scopes and their iteration scopes */ export abstract class BaseTreeSitterScopeHandler extends BaseScopeHandler { - constructor(protected query: TreeSitterQuery) { + constructor( + protected ide: IDE, + protected query: TreeSitterQuery, + ) { super(); } @@ -51,6 +53,8 @@ export abstract class BaseTreeSitterScopeHandler extends BaseScopeHandler { return equivalentScopes[0]; } + const ide = this.ide; + return { ...equivalentScopes[0], @@ -68,12 +72,12 @@ export abstract class BaseTreeSitterScopeHandler extends BaseScopeHandler { "Please use #allow-multiple! predicate in your query to allow multiple matches for this scope type"; void showError( - ide().messages, + ide.messages, "BaseTreeSitterScopeHandler.allow-multiple", message, ); - if (ide().runMode === "test") { + if (ide.runMode === "test") { throw Error(message); } } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterIterationScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterIterationScopeHandler.ts index dd6efba28d..990fe99160 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterIterationScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterIterationScopeHandler.ts @@ -1,4 +1,5 @@ import type { + IDE, ScopeType, SimpleScopeType, TextEditor, @@ -24,11 +25,12 @@ export class TreeSitterIterationScopeHandler extends BaseTreeSitterScopeHandler } constructor( + ide: IDE, query: TreeSitterQuery, /** The scope type for which we are the iteration scope */ private iterateeScopeType: SimpleScopeType, ) { - super(query); + super(ide, query); } protected matchToScope( diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts index 1fead109cd..6d7d157268 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/TreeSitterScopeHandler/TreeSitterScopeHandler.ts @@ -1,4 +1,4 @@ -import type { SimpleScopeType, TextEditor } from "@cursorless/common"; +import type { IDE, SimpleScopeType, TextEditor } from "@cursorless/common"; import type { TreeSitterQuery } from "../../../../languages/TreeSitterQuery"; import type { QueryMatch } from "../../../../languages/TreeSitterQuery/QueryCapture"; import { ScopeTypeTarget } from "../../../targets/ScopeTypeTarget"; @@ -16,10 +16,11 @@ export class TreeSitterScopeHandler extends BaseTreeSitterScopeHandler { protected isHierarchical = true; constructor( + ide: IDE, query: TreeSitterQuery, public scopeType: SimpleScopeType, ) { - super(query); + super(ide, query); } // We just create a custom scope handler that doesn't necessarily correspond @@ -28,6 +29,7 @@ export class TreeSitterScopeHandler extends BaseTreeSitterScopeHandler { return { type: "custom", scopeHandler: new TreeSitterIterationScopeHandler( + this.ide, this.query, this.scopeType, ), diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/WordScopeHandler/WordScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/WordScopeHandler/WordScopeHandler.ts index 092ff63d27..65a2ffbbb8 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/WordScopeHandler/WordScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/WordScopeHandler/WordScopeHandler.ts @@ -1,15 +1,26 @@ -import type { Direction, TextEditor } from "@cursorless/common"; +import type { Direction, IDE, ScopeType, TextEditor } from "@cursorless/common"; import { Range } from "@cursorless/common"; import { SubTokenWordTarget } from "../../../targets"; import { NestedScopeHandler } from "../NestedScopeHandler"; import type { TargetScope } from "../scope.types"; import { WordTokenizer } from "./WordTokenizer"; +import type { ScopeHandlerFactory } from "../ScopeHandlerFactory"; export class WordScopeHandler extends NestedScopeHandler { public readonly scopeType = { type: "word" } as const; public readonly iterationScopeType = { type: "identifier" } as const; - private wordTokenizer = new WordTokenizer(this.languageId); + private wordTokenizer: WordTokenizer; + + constructor( + ide: IDE, + scopeHandlerFactory: ScopeHandlerFactory, + scopeType: ScopeType, + languageId: string, + ) { + super(scopeHandlerFactory, scopeType, languageId); + this.wordTokenizer = new WordTokenizer(ide, this.languageId); + } private getScopesInSearchScope({ editor, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/WordScopeHandler/WordTokenizer.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/WordScopeHandler/WordTokenizer.ts index 35b5feeda6..174da051ad 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/WordScopeHandler/WordTokenizer.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/WordScopeHandler/WordTokenizer.ts @@ -1,3 +1,4 @@ +import type { IDE } from "@cursorless/common"; import { matchText } from "@cursorless/common"; import { getMatcher } from "../../../../tokenizer"; @@ -10,8 +11,8 @@ const CAMEL_REGEX = /\p{Lu}?\p{Ll}+|\p{Lu}+(?!\p{Ll})|\p{N}+/gu; export class WordTokenizer { private wordRegex: RegExp; - constructor(languageId: string) { - this.wordRegex = getMatcher(languageId).wordMatcher; + constructor(ide: IDE, languageId: string) { + this.wordRegex = getMatcher(ide, languageId).wordMatcher; } public splitIdentifier(text: string) { diff --git a/packages/cursorless-engine/src/runCommand.ts b/packages/cursorless-engine/src/runCommand.ts index cf7828bc40..8f3660e18d 100644 --- a/packages/cursorless-engine/src/runCommand.ts +++ b/packages/cursorless-engine/src/runCommand.ts @@ -3,6 +3,7 @@ import type { CommandResponse, CommandServerApi, HatTokenMap, + IDE, ReadOnlyHatMap, TreeSitter, } from "@cursorless/common"; @@ -35,6 +36,7 @@ import { ScopeHandlerFactoryImpl } from "./processTargets/modifiers/scopeHandler * 5. Call {@link CommandRunnerImpl.run} to run the actual command. */ export async function runCommand( + ide: IDE, treeSitter: TreeSitter, commandServerApi: CommandServerApi, debug: Debug, @@ -58,6 +60,7 @@ export async function runCommand( ); let commandRunner = createCommandRunner( + ide, treeSitter, commandServerApi, languageDefinitions, @@ -91,6 +94,7 @@ async function unwrapLegacyCommandResponse( } function createCommandRunner( + ide: IDE, treeSitter: TreeSitter, commandServerApi: CommandServerApi, languageDefinitions: LanguageDefinitions, @@ -103,14 +107,16 @@ function createCommandRunner( const modifierStageFactory = new ModifierStageFactoryImpl( languageDefinitions, storedTargets, - new ScopeHandlerFactoryImpl(languageDefinitions), + new ScopeHandlerFactoryImpl(ide, languageDefinitions), ); const markStageFactory = new MarkStageFactoryImpl( + ide, readableHatMap, storedTargets, ); const targetPipelineRunner = new TargetPipelineRunner( + ide, modifierStageFactory, markStageFactory, ); @@ -120,6 +126,6 @@ function createCommandRunner( debug, storedTargets, targetPipelineRunner, - new Actions(treeSitter, snippets, rangeUpdater, modifierStageFactory), + new Actions(ide, treeSitter, snippets, rangeUpdater, modifierStageFactory), ); } diff --git a/packages/cursorless-engine/src/scopeProviders/ScopeRangeWatcher.ts b/packages/cursorless-engine/src/scopeProviders/ScopeRangeWatcher.ts index 3ee4a095a5..530f6e2408 100644 --- a/packages/cursorless-engine/src/scopeProviders/ScopeRangeWatcher.ts +++ b/packages/cursorless-engine/src/scopeProviders/ScopeRangeWatcher.ts @@ -1,5 +1,6 @@ import type { Disposable, + IDE, IterationScopeChangeEventCallback, IterationScopeRangeConfig, ScopeChangeEventCallback, @@ -10,9 +11,8 @@ import { showError } from "@cursorless/common"; import { pull } from "lodash-es"; import type { LanguageDefinitions } from "../languages/LanguageDefinitions"; -import { ide } from "../singletons/ide.singleton"; -import type { ScopeRangeProvider } from "./ScopeRangeProvider"; import { DecorationDebouncer } from "../util/DecorationDebouncer"; +import type { ScopeRangeProvider } from "./ScopeRangeProvider"; /** * Watches for changes to the scope ranges of visible editors and notifies @@ -23,6 +23,7 @@ export class ScopeRangeWatcher { private listeners: (() => void)[] = []; constructor( + private ide: IDE, languageDefinitions: LanguageDefinitions, private scopeRangeProvider: ScopeRangeProvider, ) { @@ -31,22 +32,22 @@ export class ScopeRangeWatcher { this.onDidChangeIterationScopeRanges = this.onDidChangeIterationScopeRanges.bind(this); - const debouncer = new DecorationDebouncer(ide().configuration, () => + const debouncer = new DecorationDebouncer(ide.configuration, () => this.onChange(), ); this.disposables.push( // An Event which fires when the array of visible editors has changed. - ide().onDidChangeVisibleTextEditors(debouncer.run), + ide.onDidChangeVisibleTextEditors(debouncer.run), // An event that fires when a text document opens - ide().onDidOpenTextDocument(debouncer.run), + ide.onDidOpenTextDocument(debouncer.run), // An Event that fires when a text document closes - ide().onDidCloseTextDocument(debouncer.run), + ide.onDidCloseTextDocument(debouncer.run), // An event that is emitted when a text document is changed. This usually // happens when the contents changes but also when other things like the // dirty-state changes. - ide().onDidChangeTextDocument(debouncer.run), - ide().onDidChangeTextEditorVisibleRanges(debouncer.run), + ide.onDidChangeTextDocument(debouncer.run), + ide.onDidChangeTextEditorVisibleRanges(debouncer.run), languageDefinitions.onDidChangeDefinition(this.onChange), debouncer, ); @@ -65,7 +66,7 @@ export class ScopeRangeWatcher { config: ScopeRangeConfig, ): Disposable { const fn = () => { - ide().visibleTextEditors.forEach((editor) => { + this.ide.visibleTextEditors.forEach((editor) => { let scopeRanges: ScopeRanges[]; try { scopeRanges = this.scopeRangeProvider.provideScopeRanges( @@ -74,7 +75,7 @@ export class ScopeRangeWatcher { ); } catch (err) { void showError( - ide().messages, + this.ide.messages, "ScopeRangeWatcher.provide", (err as Error).message, ); @@ -84,7 +85,7 @@ export class ScopeRangeWatcher { // robust thing to do generally. scopeRanges = []; - if (ide().runMode === "test") { + if (this.ide.runMode === "test") { // Fail hard if we're in test mode; otherwise recover throw err; } @@ -118,7 +119,7 @@ export class ScopeRangeWatcher { config: IterationScopeRangeConfig, ): Disposable { const fn = () => { - ide().visibleTextEditors.forEach((editor) => { + this.ide.visibleTextEditors.forEach((editor) => { callback( editor, this.scopeRangeProvider.provideIterationScopeRanges(editor, config), diff --git a/packages/cursorless-engine/src/scopeProviders/ScopeSupportWatcher.ts b/packages/cursorless-engine/src/scopeProviders/ScopeSupportWatcher.ts index 37293b6dc7..f094ade0cb 100644 --- a/packages/cursorless-engine/src/scopeProviders/ScopeSupportWatcher.ts +++ b/packages/cursorless-engine/src/scopeProviders/ScopeSupportWatcher.ts @@ -1,14 +1,13 @@ import type { Disposable, + IDE, ScopeSupportEventCallback, ScopeSupportInfo, ScopeType, } from "@cursorless/common"; import { ScopeSupport, disposableFrom } from "@cursorless/common"; import { pull } from "lodash-es"; - import type { LanguageDefinitions } from "../languages/LanguageDefinitions"; -import { ide } from "../singletons/ide.singleton"; import { DecorationDebouncer } from "../util/DecorationDebouncer"; import type { ScopeInfoProvider } from "./ScopeInfoProvider"; import type { ScopeSupportChecker } from "./ScopeSupportChecker"; @@ -22,6 +21,7 @@ export class ScopeSupportWatcher { private listeners: ScopeSupportEventCallback[] = []; constructor( + private ide: IDE, languageDefinitions: LanguageDefinitions, private scopeSupportChecker: ScopeSupportChecker, private scopeInfoProvider: ScopeInfoProvider, @@ -29,21 +29,21 @@ export class ScopeSupportWatcher { this.onChange = this.onChange.bind(this); this.onDidChangeScopeSupport = this.onDidChangeScopeSupport.bind(this); - const debouncer = new DecorationDebouncer(ide().configuration, () => + const debouncer = new DecorationDebouncer(ide.configuration, () => this.onChange(), ); this.disposable = disposableFrom( // An event that fires when a text document opens - ide().onDidOpenTextDocument(debouncer.run), + ide.onDidOpenTextDocument(debouncer.run), // An Event that fires when a text document closes - ide().onDidCloseTextDocument(debouncer.run), + ide.onDidCloseTextDocument(debouncer.run), // An Event which fires when the active editor has changed. Note that the event also fires when the active editor changes to undefined. - ide().onDidChangeActiveTextEditor(debouncer.run), + ide.onDidChangeActiveTextEditor(debouncer.run), // An event that is emitted when a text document is changed. This usually // happens when the contents changes but also when other things like the // dirty-state changes. - ide().onDidChangeTextDocument(debouncer.run), + ide.onDidChangeTextDocument(debouncer.run), languageDefinitions.onDidChangeDefinition(debouncer.run), this.scopeInfoProvider.onDidChangeScopeInfo(this.onChange), debouncer, @@ -86,7 +86,7 @@ export class ScopeSupportWatcher { } private getSupportLevels(): ScopeSupportInfo[] { - const activeTextEditor = ide().activeTextEditor; + const activeTextEditor = this.ide.activeTextEditor; const getScopeTypeSupport = activeTextEditor == null diff --git a/packages/cursorless-engine/src/scripts/transformRecordedTests/checkMarks.ts b/packages/cursorless-engine/src/scripts/transformRecordedTests/checkMarks.ts index 3f55fd04d7..a04594f963 100644 --- a/packages/cursorless-engine/src/scripts/transformRecordedTests/checkMarks.ts +++ b/packages/cursorless-engine/src/scripts/transformRecordedTests/checkMarks.ts @@ -1,18 +1,16 @@ import type { TestCaseFixtureLegacy } from "@cursorless/common"; import { FakeIDE } from "@cursorless/common"; +import assert from "assert"; import { uniq } from "lodash-es"; -import { injectIde } from "../../singletons/ide.singleton"; -import tokenGraphemeSplitter from "../../singletons/tokenGraphemeSplitter.singleton"; import { extractTargetKeys } from "../../testUtil/extractTargetKeys"; +import { TokenGraphemeSplitter } from "../../tokenGraphemeSplitter/tokenGraphemeSplitter"; import { getPartialTargetDescriptors } from "../../util/getPartialTargetDescriptors"; -import assert from "assert"; import { canonicalize } from "./transformations/canonicalize"; export function checkMarks(originalFixture: TestCaseFixtureLegacy): undefined { const command = canonicalize(originalFixture).command; - injectIde(new FakeIDE()); - const graphemeSplitter = tokenGraphemeSplitter(); + const graphemeSplitter = new TokenGraphemeSplitter(new FakeIDE()); const targetedMarks = getPartialTargetDescriptors(command.action) .map(extractTargetKeys) diff --git a/packages/cursorless-engine/src/singletons/ide.singleton.ts b/packages/cursorless-engine/src/singletons/ide.singleton.ts deleted file mode 100644 index acfe529f65..0000000000 --- a/packages/cursorless-engine/src/singletons/ide.singleton.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { IDE } from "@cursorless/common"; - -/** - * This is the `ide` singleton - */ -let ide_: IDE | undefined; - -/** - * Injects an {@link IDE} object that can be used to interact with the IDE. - * This function should only be called from a select few places, eg extension - * activation or when mocking a test. - * @param ide The ide to inject - */ -export function injectIde(ide: IDE | undefined) { - ide_ = ide; -} - -/** - * Gets the singleton used to interact with the IDE. - * @throws Error if the IDE hasn't been injected yet. Can avoid this by - * constructing your objects lazily - * @returns The IDE object - */ -export function ide(): IDE { - if (ide_ == null) { - throw Error("Tried to access ide before it was injected"); - } - - return ide_; -} diff --git a/packages/cursorless-engine/src/singletons/tokenGraphemeSplitter.singleton.ts b/packages/cursorless-engine/src/singletons/tokenGraphemeSplitter.singleton.ts deleted file mode 100644 index b0ffc0f5a3..0000000000 --- a/packages/cursorless-engine/src/singletons/tokenGraphemeSplitter.singleton.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { TokenGraphemeSplitter } from "../tokenGraphemeSplitter/tokenGraphemeSplitter"; - -/** - * Returns the token grapheme splitter singleton, constructing it if it doesn't exist. - * @returns The token grapheme splitter singleton - */ - -export default function tokenGraphemeSplitter(): TokenGraphemeSplitter { - if (tokenGraphemeSplitter_ == null) { - tokenGraphemeSplitter_ = new TokenGraphemeSplitter(); - } - - return tokenGraphemeSplitter_; -} -/** - * This is the token grapheme splitter singleton - */ -let tokenGraphemeSplitter_: TokenGraphemeSplitter | undefined; diff --git a/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts b/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts index 28ef9e13b3..56d57d0d77 100644 --- a/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts +++ b/packages/cursorless-engine/src/spokenForms/CustomSpokenForms.ts @@ -1,6 +1,7 @@ import type { CustomRegexScopeType, Disposable, + IDE, SpokenFormEntry, SpokenFormMapKeyTypes, SpokenFormType, @@ -14,7 +15,6 @@ import { showError, } from "@cursorless/common"; import { isEqual } from "lodash-es"; -import { ide } from "../singletons/ide.singleton"; import type { SpokenFormMap, SpokenFormMapEntry } from "./SpokenFormMap"; import { defaultSpokenFormInfoMap, @@ -57,7 +57,10 @@ export class CustomSpokenForms { return this.needsInitialTalonUpdate_; } - constructor(private talonSpokenForms: TalonSpokenForms) { + constructor( + private ide: IDE, + private talonSpokenForms: TalonSpokenForms, + ) { this.disposable = talonSpokenForms.onDidChange(() => this.updateSpokenFormMaps(), ); @@ -94,7 +97,7 @@ export class CustomSpokenForms { console.error("Error loading custom spoken forms", err); const msg = (err as Error).message.replace(/\.$/, ""); void showError( - ide().messages, + this.ide.messages, "CustomSpokenForms.updateSpokenFormMaps", `Error loading custom spoken forms: ${msg}. Falling back to default spoken forms.`, ); diff --git a/packages/cursorless-engine/src/test/scopes.test.ts b/packages/cursorless-engine/src/test/scopes.test.ts index 3259e78e56..b6dbc72eaa 100644 --- a/packages/cursorless-engine/src/test/scopes.test.ts +++ b/packages/cursorless-engine/src/test/scopes.test.ts @@ -16,10 +16,8 @@ import { getScopeTestPathsRecursively } from "@cursorless/node-common"; import { assert } from "chai"; import { groupBy, uniq } from "lodash-es"; import { promises as fsp } from "node:fs"; -import { - createTestEnvironment, - type TestEnvironment, -} from "../testUtil/createTestEnvironment"; +import type { TestEnvironment } from "../testUtil/createTestEnvironment"; +import { createTestEnvironment } from "../testUtil/createTestEnvironment"; import { serializeIterationScopeFixture, serializeScopeFixture, diff --git a/packages/cursorless-engine/src/test/sentenceSegmenter.test.ts b/packages/cursorless-engine/src/test/sentenceSegmenter.test.ts index cb2fb0fd80..586b83d348 100644 --- a/packages/cursorless-engine/src/test/sentenceSegmenter.test.ts +++ b/packages/cursorless-engine/src/test/sentenceSegmenter.test.ts @@ -1,11 +1,8 @@ import * as assert from "assert"; import { SentenceSegmenter } from "../processTargets/modifiers/scopeHandlers/SentenceScopeHandler/SentenceSegmenter"; import { sentenceSegmenterFixture } from "./fixtures/sentenceSegmeter.fixture"; -import { unitTestSetup } from "../testUtil/unitTestSetup"; suite("Sentence segmenter", () => { - unitTestSetup(); - sentenceSegmenterFixture.forEach(({ input, expectedOutput }) => { test(input, () => { assert.deepStrictEqual( diff --git a/packages/cursorless-engine/src/test/subtoken.test.ts b/packages/cursorless-engine/src/test/subtoken.test.ts index 77409d0641..8cf7035bca 100644 --- a/packages/cursorless-engine/src/test/subtoken.test.ts +++ b/packages/cursorless-engine/src/test/subtoken.test.ts @@ -1,15 +1,15 @@ +import { FakeIDE } from "@cursorless/common"; import * as assert from "assert"; import { WordTokenizer } from "../processTargets/modifiers/scopeHandlers/WordScopeHandler/WordTokenizer"; import { subtokenFixture } from "./fixtures/subtoken.fixture"; -import { unitTestSetup } from "../testUtil/unitTestSetup"; suite("subtoken regex matcher", () => { - unitTestSetup(); + const ide = new FakeIDE(); subtokenFixture.forEach(({ input, expectedOutput }) => { test(input, () => { assert.deepStrictEqual( - new WordTokenizer("anyLang") + new WordTokenizer(ide, "anyLang") .splitIdentifier(input) .map(({ text }) => text), expectedOutput, diff --git a/packages/cursorless-engine/src/testUtil/createTestEnvironment.ts b/packages/cursorless-engine/src/testUtil/createTestEnvironment.ts index dadd0e7ef2..07f46ff94c 100644 --- a/packages/cursorless-engine/src/testUtil/createTestEnvironment.ts +++ b/packages/cursorless-engine/src/testUtil/createTestEnvironment.ts @@ -1,13 +1,11 @@ -import { - FakeIDE, - InMemoryTextDocument, - Selection, - type EditableTextEditor, - type MessageId, - type Messages, - type MessageType, - type ScopeProvider, +import type { + EditableTextEditor, + MessageId, + Messages, + MessageType, + ScopeProvider, } from "@cursorless/common"; +import { FakeIDE, InMemoryTextDocument, Selection } from "@cursorless/common"; import { FileSystemRawTreeSitterQueryProvider } from "@cursorless/node-common"; import { URI } from "vscode-uri"; import { createCursorlessEngine } from ".."; diff --git a/packages/cursorless-engine/src/testUtil/unitTestSetup.ts b/packages/cursorless-engine/src/testUtil/unitTestSetup.ts deleted file mode 100644 index 64e3954bab..0000000000 --- a/packages/cursorless-engine/src/testUtil/unitTestSetup.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { FakeIDE, SpyIDE } from "@cursorless/common"; -import type { Context } from "mocha"; -import * as sinon from "sinon"; -import { injectIde } from "../singletons/ide.singleton"; - -export function unitTestSetup(setupFake?: (fake: FakeIDE) => void) { - let spy: SpyIDE | undefined; - let fake: FakeIDE | undefined; - - setup(async function (this: Context) { - fake = new FakeIDE(); - setupFake?.(fake); - spy = new SpyIDE(fake); - injectIde(spy); - }); - - teardown(() => { - sinon.restore(); - injectIde(undefined); - }); - - return { - getSpy() { - return spy; - }, - }; -} diff --git a/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.test.ts b/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.test.ts index 24e6d365e4..f8cee2de30 100644 --- a/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.test.ts +++ b/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.test.ts @@ -1,6 +1,6 @@ import type { TokenHatSplittingMode } from "@cursorless/common"; +import { FakeIDE } from "@cursorless/common"; import * as assert from "assert"; -import { unitTestSetup } from "../testUtil/unitTestSetup"; import { TokenGraphemeSplitter, UNKNOWN } from "./tokenGraphemeSplitter"; /** @@ -281,11 +281,11 @@ const tokenHatSplittingDefaults: TokenHatSplittingMode = { tests.forEach(({ tokenHatSplittingMode, extraTestCases }) => { suite(`getTokenGraphemes(${JSON.stringify(tokenHatSplittingMode)})`, () => { - unitTestSetup(({ configuration }) => { - configuration.mockConfiguration("tokenHatSplittingMode", { - ...tokenHatSplittingDefaults, - ...tokenHatSplittingMode, - }); + const ide = new FakeIDE(); + + ide.configuration.mockConfiguration("tokenHatSplittingMode", { + ...tokenHatSplittingDefaults, + ...tokenHatSplittingMode, }); const testCases = [...commonTestCases, ...extraTestCases]; @@ -302,7 +302,7 @@ tests.forEach(({ tokenHatSplittingMode, extraTestCases }) => { const displayOutput = expectedOutput.map(({ text }) => text).join(", "); test(`${input} -> ${displayOutput}`, () => { - const actualOutput = new TokenGraphemeSplitter().getTokenGraphemes( + const actualOutput = new TokenGraphemeSplitter(ide).getTokenGraphemes( input, ); assert.deepStrictEqual(actualOutput, expectedOutput); diff --git a/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.ts b/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.ts index 6b8f9270ed..9f92a52e49 100644 --- a/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.ts +++ b/packages/cursorless-engine/src/tokenGraphemeSplitter/tokenGraphemeSplitter.ts @@ -1,7 +1,10 @@ -import type { Disposable, TokenHatSplittingMode } from "@cursorless/common"; +import type { + Disposable, + IDE, + TokenHatSplittingMode, +} from "@cursorless/common"; import { Notifier, matchAll } from "@cursorless/common"; import { deburr, escapeRegExp } from "lodash-es"; -import { ide } from "../singletons/ide.singleton"; /** * A list of all symbols that are speakable by default in community. @@ -74,8 +77,8 @@ export class TokenGraphemeSplitter { private algorithmChangeNotifier = new Notifier(); private tokenHatSplittingMode!: TokenHatSplittingMode; - constructor() { - ide().disposeOnExit(this); + constructor(private ide: IDE) { + ide.disposeOnExit(this); this.updateTokenHatSplittingMode = this.updateTokenHatSplittingMode.bind(this); @@ -86,7 +89,7 @@ export class TokenGraphemeSplitter { this.disposables.push( // Notify listeners in case the user changed their token hat splitting // setting. - ide().configuration.onDidChangeConfiguration( + ide.configuration.onDidChangeConfiguration( this.updateTokenHatSplittingMode, ), ); @@ -94,7 +97,7 @@ export class TokenGraphemeSplitter { private updateTokenHatSplittingMode() { const { lettersToPreserve, symbolsToPreserve, ...rest } = - ide().configuration.getOwnConfiguration("tokenHatSplittingMode"); + this.ide.configuration.getOwnConfiguration("tokenHatSplittingMode"); this.tokenHatSplittingMode = { lettersToPreserve: lettersToPreserve.map((grapheme) => diff --git a/packages/cursorless-engine/src/tokenizer/tokenizer.test.ts b/packages/cursorless-engine/src/tokenizer/tokenizer.test.ts index cbd64c551a..2dd07022b5 100644 --- a/packages/cursorless-engine/src/tokenizer/tokenizer.test.ts +++ b/packages/cursorless-engine/src/tokenizer/tokenizer.test.ts @@ -1,7 +1,7 @@ import * as assert from "assert"; import { flatten, range } from "lodash-es"; import { tokenize } from "."; -import { unitTestSetup } from "../testUtil/unitTestSetup"; +import { FakeIDE } from "@cursorless/common"; type TestCase = [string, string[]]; /** @@ -116,24 +116,24 @@ const languageTokenizerTests: Record = { }; suite("tokenizer", () => { - unitTestSetup((fake) => { - Object.entries(languageTokenizerTests).forEach( - ([languageId, { wordSeparators }]) => { - fake.configuration.mockConfigurationScope( - { - languageId, - }, - { - wordSeparators, - }, - ); - }, - ); - }); + const ide = new FakeIDE(); + + Object.entries(languageTokenizerTests).forEach( + ([languageId, { wordSeparators }]) => { + ide.configuration.mockConfigurationScope( + { + languageId, + }, + { + wordSeparators, + }, + ); + }, + ); globalTests.forEach(([input, expectedOutput]) => { test(`tokenizer test, input: "${input}"`, () => { - const output = tokenize(input, "anyLang", (match) => match[0]); + const output = tokenize(ide, input, "anyLang", (match) => match[0]); assert.deepStrictEqual(output, expectedOutput); }); }); @@ -155,7 +155,7 @@ suite("tokenizer", () => { tests.forEach(([input, expectedOutput]) => { test(`${language} custom tokenizer, input: "${input}"`, () => { - const output = tokenize(input, language, (match) => match[0]); + const output = tokenize(ide, input, language, (match) => match[0]); assert.deepStrictEqual(output, expectedOutput); }); }); diff --git a/packages/cursorless-engine/src/tokenizer/tokenizer.ts b/packages/cursorless-engine/src/tokenizer/tokenizer.ts index 05f921900b..6800f27b46 100644 --- a/packages/cursorless-engine/src/tokenizer/tokenizer.ts +++ b/packages/cursorless-engine/src/tokenizer/tokenizer.ts @@ -1,6 +1,5 @@ -import { matchAll } from "@cursorless/common"; +import { matchAll, type IDE } from "@cursorless/common"; import { escapeRegExp } from "lodash-es"; -import { ide } from "../singletons/ide.singleton"; import type { LanguageTokenizerComponents } from "./tokenizer.types"; const REPEATABLE_SYMBOLS = [ @@ -96,10 +95,10 @@ function generateMatcher( const matchers = new Map(); -export function getMatcher(languageId: string): Matcher { +export function getMatcher(ide: IDE, languageId: string): Matcher { // FIXME: The reason this code will auto-reload on settings change is that we don't use fine-grained settings listener in `Decorations`: // https://github.com/cursorless-dev/cursorless/blob/c914d477c9624c498a47c964088b34e484eac494/src/core/Decorations.ts#L58 - const wordSeparators = ide().configuration.getOwnConfiguration( + const wordSeparators = ide.configuration.getOwnConfiguration( "wordSeparators", { languageId, @@ -124,9 +123,11 @@ export function getMatcher(languageId: string): Matcher { } export function tokenize( + ide: IDE, text: string, languageId: string, mapfn: (v: RegExpMatchArray, k: number) => T, ) { - return matchAll(text, getMatcher(languageId).tokenMatcher, mapfn); + const matcher = getMatcher(ide, languageId); + return matchAll(text, matcher.tokenMatcher, mapfn); } diff --git a/packages/cursorless-engine/src/util/allocateHats/allocateHats.ts b/packages/cursorless-engine/src/util/allocateHats/allocateHats.ts index 2e9586b908..364b913c29 100644 --- a/packages/cursorless-engine/src/util/allocateHats/allocateHats.ts +++ b/packages/cursorless-engine/src/util/allocateHats/allocateHats.ts @@ -2,6 +2,7 @@ import type { HatStability, HatStyleMap, HatStyleName, + IDE, TextEditor, Token, TokenHat, @@ -24,6 +25,7 @@ export interface HatCandidate { } interface AllocateHatsOptions { + ide: IDE; tokenGraphemeSplitter: TokenGraphemeSplitter; enabledHatStyles: HatStyleMap; forceTokenHats: readonly TokenHat[] | undefined; @@ -53,6 +55,7 @@ interface AllocateHatsOptions { * and the hat that it will wear */ export function allocateHats({ + ide, tokenGraphemeSplitter, enabledHatStyles, forceTokenHats, @@ -77,6 +80,7 @@ export function allocateHats({ * be used. */ const rankedTokens = getRankedTokens( + ide, activeTextEditor, visibleTextEditors, forcedHatMap, @@ -116,6 +120,7 @@ export function allocateHats({ * higher ranked token */ const tokenRemainingHatCandidates = getTokenRemainingHatCandidates( + ide, tokenGraphemeSplitter, token, graphemeRemainingHatCandidates, @@ -166,6 +171,7 @@ function getTokenOldHatMap(oldTokenHats: readonly TokenHat[]) { } function getTokenRemainingHatCandidates( + ide: IDE, tokenGraphemeSplitter: TokenGraphemeSplitter, token: Token, graphemeRemainingHatCandidates: DefaultMap, @@ -174,7 +180,7 @@ function getTokenRemainingHatCandidates( const candidates: HatCandidate[] = []; const graphemes = tokenGraphemeSplitter.getTokenGraphemes(token.text); const firstLetterOffsets = new Set( - new WordTokenizer(token.editor.document.languageId) + new WordTokenizer(ide, token.editor.document.languageId) .splitIdentifier(token.text) .map((word) => word.index), ); diff --git a/packages/cursorless-engine/src/util/allocateHats/getRankedTokens.ts b/packages/cursorless-engine/src/util/allocateHats/getRankedTokens.ts index 33165235e1..ff59ef5a5c 100644 --- a/packages/cursorless-engine/src/util/allocateHats/getRankedTokens.ts +++ b/packages/cursorless-engine/src/util/allocateHats/getRankedTokens.ts @@ -1,5 +1,6 @@ import type { CompositeKeyMap, + IDE, TextEditor, Token, TokenHat, @@ -15,6 +16,7 @@ import { getTokensInRange } from "./getTokensInRange"; * @returns A list of tokens along with their ranks, sorted by decreasing rank */ export function getRankedTokens( + ide: IDE, activeTextEditor: TextEditor | undefined, visibleTextEditors: readonly TextEditor[], forcedHatMap: CompositeKeyMap | undefined, @@ -35,7 +37,7 @@ export function getRankedTokens( const displayLineMap = getDisplayLineMap(editor, [referencePosition.line]); const tokens = flatten( editor.visibleRanges.map((range) => - getTokensInRange(editor, range).map((partialToken) => ({ + getTokensInRange(ide, editor, range).map((partialToken) => ({ ...partialToken, displayLine: displayLineMap.get(partialToken.range.start.line)!, })), diff --git a/packages/cursorless-engine/src/util/allocateHats/getTokensInRange.ts b/packages/cursorless-engine/src/util/allocateHats/getTokensInRange.ts index 8ae4b8c97f..cb7fbba729 100644 --- a/packages/cursorless-engine/src/util/allocateHats/getTokensInRange.ts +++ b/packages/cursorless-engine/src/util/allocateHats/getTokensInRange.ts @@ -1,13 +1,17 @@ -import type { TextEditor, Token } from "@cursorless/common"; +import type { IDE, TextEditor, Token } from "@cursorless/common"; import { Range } from "@cursorless/common"; import { tokenize } from "../../tokenizer"; -export function getTokensInRange(editor: TextEditor, range: Range): Token[] { +export function getTokensInRange( + ide: IDE, + editor: TextEditor, + range: Range, +): Token[] { const languageId = editor.document.languageId; const text = editor.document.getText(range); const rangeOffset = editor.document.offsetAt(range.start); - return tokenize(text, languageId, (match) => { + return tokenize(ide, text, languageId, (match) => { const startOffset = rangeOffset + match.index!; const endOffset = rangeOffset + match.index! + match[0].length; const range = new Range( diff --git a/packages/cursorless-everywhere-talon-core/src/constructTestHelpers.ts b/packages/cursorless-everywhere-talon-core/src/constructTestHelpers.ts index 9c3f91d740..f6399600e6 100644 --- a/packages/cursorless-everywhere-talon-core/src/constructTestHelpers.ts +++ b/packages/cursorless-everywhere-talon-core/src/constructTestHelpers.ts @@ -9,11 +9,11 @@ import type { TargetPlainObject, TextEditor, } from "@cursorless/common"; -import { - plainObjectToTarget, - type CommandApi, - type StoredTargetMap, +import type { + CommandApi, + StoredTargetMap, } from "@cursorless/cursorless-engine"; +import { plainObjectToTarget } from "@cursorless/cursorless-engine"; import type { TalonJsIDE } from "./ide/TalonJsIDE"; import type { TalonJsTestHelpers } from "./types/types"; diff --git a/packages/cursorless-everywhere-talon-core/src/extension.ts b/packages/cursorless-everywhere-talon-core/src/extension.ts index c59bded006..cba24b63bf 100644 --- a/packages/cursorless-everywhere-talon-core/src/extension.ts +++ b/packages/cursorless-everywhere-talon-core/src/extension.ts @@ -1,10 +1,10 @@ import "./polyfill"; +import type { RunMode } from "@cursorless/common"; import { FakeCommandServerApi, FakeIDE, NormalizedIDE, - type RunMode, } from "@cursorless/common"; import { createCursorlessEngine } from "@cursorless/cursorless-engine"; import { constructTestHelpers } from "./constructTestHelpers"; diff --git a/packages/cursorless-everywhere-talon-core/src/ide/TalonJsConfiguration.ts b/packages/cursorless-everywhere-talon-core/src/ide/TalonJsConfiguration.ts index 47f530068a..949528c907 100644 --- a/packages/cursorless-everywhere-talon-core/src/ide/TalonJsConfiguration.ts +++ b/packages/cursorless-everywhere-talon-core/src/ide/TalonJsConfiguration.ts @@ -1,14 +1,14 @@ import { get } from "lodash-es"; -import { - HatStability, - type Configuration, - type ConfigurationScope, - type CursorlessConfiguration, - type Disposable, - type GetFieldType, - type Listener, - type Paths, +import type { + Configuration, + ConfigurationScope, + CursorlessConfiguration, + Disposable, + GetFieldType, + Listener, + Paths, } from "@cursorless/common"; +import { HatStability } from "@cursorless/common"; const CONFIGURATION_DEFAULTS: CursorlessConfiguration = { tokenHatSplittingMode: { diff --git a/packages/cursorless-everywhere-talon-core/src/ide/TalonJsEditor.ts b/packages/cursorless-everywhere-talon-core/src/ide/TalonJsEditor.ts index 6ab7bc3ac0..9dcac1e9c2 100644 --- a/packages/cursorless-everywhere-talon-core/src/ide/TalonJsEditor.ts +++ b/packages/cursorless-everywhere-talon-core/src/ide/TalonJsEditor.ts @@ -1,17 +1,17 @@ -import { - selectionsEqual, - type Edit, - type EditableTextEditor, - type GeneralizedRange, - type InMemoryTextDocument, - type OpenLinkOptions, - type Range, - type RevealLineAt, - type Selection, - type SetSelectionsOpts, - type TextEditor, - type TextEditorOptions, +import type { + Edit, + EditableTextEditor, + GeneralizedRange, + InMemoryTextDocument, + OpenLinkOptions, + Range, + RevealLineAt, + Selection, + SetSelectionsOpts, + TextEditor, + TextEditorOptions, } from "@cursorless/common"; +import { selectionsEqual } from "@cursorless/common"; import type { Talon } from "../types/talon.types"; import { setSelections } from "./setSelections"; import type { TalonJsIDE } from "./TalonJsIDE"; diff --git a/packages/cursorless-everywhere-talon-core/src/ide/TalonJsMessages.ts b/packages/cursorless-everywhere-talon-core/src/ide/TalonJsMessages.ts index 090f265e80..0d1d7a2d66 100644 --- a/packages/cursorless-everywhere-talon-core/src/ide/TalonJsMessages.ts +++ b/packages/cursorless-everywhere-talon-core/src/ide/TalonJsMessages.ts @@ -1,4 +1,5 @@ -import { MessageType, type Messages } from "@cursorless/common"; +import type { Messages } from "@cursorless/common"; +import { MessageType } from "@cursorless/common"; import type { Talon } from "../types/talon.types"; export class TalonJsMessages implements Messages { diff --git a/packages/cursorless-everywhere-talon-core/src/ide/createTextEditor.ts b/packages/cursorless-everywhere-talon-core/src/ide/createTextEditor.ts index 0e57553172..7eb632ad7b 100644 --- a/packages/cursorless-everywhere-talon-core/src/ide/createTextEditor.ts +++ b/packages/cursorless-everywhere-talon-core/src/ide/createTextEditor.ts @@ -1,8 +1,5 @@ -import { - InMemoryTextDocument, - Selection, - type TextDocument, -} from "@cursorless/common"; +import type { TextDocument } from "@cursorless/common"; +import { InMemoryTextDocument, Selection } from "@cursorless/common"; import { URI } from "vscode-uri"; import type { Talon } from "../types/talon.types"; import type { EditorState, SelectionOffsets } from "../types/types"; diff --git a/packages/cursorless-neovim/src/extension.ts b/packages/cursorless-neovim/src/extension.ts index 2bfb8e2c91..e51c4da9e6 100644 --- a/packages/cursorless-neovim/src/extension.ts +++ b/packages/cursorless-neovim/src/extension.ts @@ -23,21 +23,18 @@ export async function activate(plugin: NvimPlugin) { const neovimIDE = new NeovimIDE(client); await neovimIDE.init(); + const isTesting = neovimIDE.runMode === "test"; + const normalizedIde = neovimIDE.runMode === "production" ? neovimIDE - : new NormalizedIDE( - neovimIDE, - new FakeIDE(), - neovimIDE.runMode === "test", - ); + : new NormalizedIDE(neovimIDE, new FakeIDE(), isTesting); const fakeCommandServerApi = new FakeCommandServerApi(); const neovimCommandServerApi = new NeovimCommandServerApi(client); - const commandServerApi = - neovimIDE.runMode === "test" - ? fakeCommandServerApi - : neovimCommandServerApi; + const commandServerApi = isTesting + ? fakeCommandServerApi + : neovimCommandServerApi; const { commandApi, storedTargets, hatTokenMap, scopeProvider, injectIde } = await createCursorlessEngine({ ide: normalizedIde, commandServerApi }); @@ -45,18 +42,17 @@ export async function activate(plugin: NvimPlugin) { await registerCommands(client, neovimIDE, commandApi, commandServerApi); const cursorlessApi = { - testHelpers: - neovimIDE.runMode === "test" - ? constructTestHelpers( - fakeCommandServerApi, - storedTargets, - hatTokenMap, - neovimIDE, - normalizedIde as NormalizedIDE, - scopeProvider, - injectIde, - ) - : undefined, + testHelpers: isTesting + ? constructTestHelpers( + fakeCommandServerApi, + storedTargets, + hatTokenMap, + neovimIDE, + normalizedIde as NormalizedIDE, + scopeProvider, + injectIde, + ) + : undefined, }; getNeovimRegistry().registerExtensionApi(EXTENSION_ID, cursorlessApi); diff --git a/packages/cursorless-org-docs/src/docs/components/Code.tsx b/packages/cursorless-org-docs/src/docs/components/Code.tsx index f70ab465b2..00b2a764c0 100644 --- a/packages/cursorless-org-docs/src/docs/components/Code.tsx +++ b/packages/cursorless-org-docs/src/docs/components/Code.tsx @@ -1,5 +1,6 @@ import React, { useEffect, useState } from "react"; -import { codeToHtml, type DecorationItem } from "shiki"; +import type { DecorationItem } from "shiki"; +import { codeToHtml } from "shiki"; import "./Code.css"; interface Props { diff --git a/packages/cursorless-org-docs/src/docs/components/ScopeVisualizer.tsx b/packages/cursorless-org-docs/src/docs/components/ScopeVisualizer.tsx index a60f4e93e0..e3f195993e 100644 --- a/packages/cursorless-org-docs/src/docs/components/ScopeVisualizer.tsx +++ b/packages/cursorless-org-docs/src/docs/components/ScopeVisualizer.tsx @@ -1,9 +1,8 @@ +import type { ScopeSupportFacetInfo, ScopeTypeType } from "@cursorless/common"; import { prettifyLanguageName, prettifyScopeType, serializeScopeType, - type ScopeSupportFacetInfo, - type ScopeTypeType, } from "@cursorless/common"; import { usePluginData } from "@docusaurus/useGlobalData"; import React, { useState } from "react"; diff --git a/packages/cursorless-org-docs/src/docs/components/calculateHighlights.ts b/packages/cursorless-org-docs/src/docs/components/calculateHighlights.ts index 1395deb674..1df18e3ac3 100644 --- a/packages/cursorless-org-docs/src/docs/components/calculateHighlights.ts +++ b/packages/cursorless-org-docs/src/docs/components/calculateHighlights.ts @@ -1,8 +1,8 @@ +import type { DecorationStyle } from "@cursorless/common"; import { generateDecorationsForCharacterRange, Range, useSingleCornerBorderRadius, - type DecorationStyle, } from "@cursorless/common"; import type { DecorationItem } from "shiki"; import { flattenHighlights } from "./flattenHighlights"; diff --git a/packages/cursorless-org-docs/src/docs/components/flattenHighlights.ts b/packages/cursorless-org-docs/src/docs/components/flattenHighlights.ts index 454cab8928..3fa393e470 100644 --- a/packages/cursorless-org-docs/src/docs/components/flattenHighlights.ts +++ b/packages/cursorless-org-docs/src/docs/components/flattenHighlights.ts @@ -1,10 +1,5 @@ -import { - type DecorationStyle, - type Position, - blendMultipleColors, - BorderStyle, - Range, -} from "@cursorless/common"; +import type { DecorationStyle, Position } from "@cursorless/common"; +import { blendMultipleColors, BorderStyle, Range } from "@cursorless/common"; import type { BorderRadius, Highlight, Style } from "./types"; export function flattenHighlights(highlights: Highlight[]): Highlight[] { diff --git a/packages/cursorless-org-docs/src/docs/components/util.ts b/packages/cursorless-org-docs/src/docs/components/util.ts index 09b802cd4a..3b59b9e541 100644 --- a/packages/cursorless-org-docs/src/docs/components/util.ts +++ b/packages/cursorless-org-docs/src/docs/components/util.ts @@ -1,12 +1,14 @@ +import type { + PlaintextScopeSupportFacet, + ScopeSupportFacet, + ScopeSupportFacetInfo, + ScopeTypeType, +} from "@cursorless/common"; import { camelCaseToAllDown, capitalize, plaintextScopeSupportFacetInfos, scopeSupportFacetInfos, - type PlaintextScopeSupportFacet, - type ScopeSupportFacet, - type ScopeSupportFacetInfo, - type ScopeTypeType, } from "@cursorless/common"; export function prettifyFacet( diff --git a/packages/cursorless-org-docs/src/docs/contributing/MissingLanguageScopes.tsx b/packages/cursorless-org-docs/src/docs/contributing/MissingLanguageScopes.tsx index 440c1f0ec7..bd4c6abf4d 100644 --- a/packages/cursorless-org-docs/src/docs/contributing/MissingLanguageScopes.tsx +++ b/packages/cursorless-org-docs/src/docs/contributing/MissingLanguageScopes.tsx @@ -1,10 +1,10 @@ +import type { ScopeSupportFacet } from "@cursorless/common"; import { languageScopeSupport, scopeSupportFacetInfos, ScopeSupportFacetLevel, scopeSupportFacets, serializeScopeType, - type ScopeSupportFacet, } from "@cursorless/common"; import React, { useState } from "react"; diff --git a/packages/cursorless-org-docs/src/plugins/scope-tests-plugin.ts b/packages/cursorless-org-docs/src/plugins/scope-tests-plugin.ts index 3f4964d9cc..f331713bcf 100644 --- a/packages/cursorless-org-docs/src/plugins/scope-tests-plugin.ts +++ b/packages/cursorless-org-docs/src/plugins/scope-tests-plugin.ts @@ -1,7 +1,7 @@ +import type { ScopeTestPath } from "@cursorless/node-common"; import { getScopeTestLanguagesRecursively, getScopeTestPaths, - type ScopeTestPath, } from "@cursorless/node-common"; import type { LoadContext, Plugin, PluginOptions } from "@docusaurus/types"; import * as fs from "node:fs"; diff --git a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts index 3c214e3dae..b1c56b7d69 100644 --- a/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts +++ b/packages/cursorless-vscode-e2e/src/endToEndTestSetup.ts @@ -29,32 +29,33 @@ export function endToEndTestSetup( suite.timeout(timeout); suite.retries(retries); - let ide: IDE; + let originalIde: IDE; let injectIde: (ide: IDE) => void; - let spy: SpyIDE | undefined; + let spyIde: SpyIDE | undefined; setup(async function (this: Context) { const title = this.test!.fullTitle(); retryCount = title === previousTestTitle ? retryCount + 1 : 0; previousTestTitle = title; const testHelpers = (await getCursorlessApi()).testHelpers!; - ({ ide, injectIde } = testHelpers); + originalIde = testHelpers.ide; + injectIde = testHelpers.injectIde; testHelpers.commandServerApi.setFocusedElementType(undefined); - spy = new SpyIDE(ide); - injectIde(spy); + spyIde = new SpyIDE(originalIde); + injectIde(spyIde); }); teardown(() => { sinon.restore(); - injectIde(ide); + injectIde(originalIde); }); return { getSpy() { - if (spy == null) { - throw Error("spy is undefined"); + if (spyIde == null) { + throw Error("Spy is undefined"); } - return spy; + return spyIde; }, }; } diff --git a/packages/cursorless-vscode-e2e/src/suite/performance.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/performance.vscode.test.ts index 5048e59571..1aedf6f762 100644 --- a/packages/cursorless-vscode-e2e/src/suite/performance.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/performance.vscode.test.ts @@ -1,10 +1,10 @@ -import { - asyncSafety, - type ActionDescriptor, - type Modifier, - type ScopeType, - type SimpleScopeTypeType, +import type { + ActionDescriptor, + Modifier, + ScopeType, + SimpleScopeTypeType, } from "@cursorless/common"; +import { asyncSafety } from "@cursorless/common"; import { openNewEditor, runCursorlessAction } from "@cursorless/vscode-common"; import assert from "assert"; import * as vscode from "vscode"; diff --git a/packages/cursorless-vscode-tutorial-webview/src/App.tsx b/packages/cursorless-vscode-tutorial-webview/src/App.tsx index cbadb82faa..65a219d954 100644 --- a/packages/cursorless-vscode-tutorial-webview/src/App.tsx +++ b/packages/cursorless-vscode-tutorial-webview/src/App.tsx @@ -1,5 +1,6 @@ import type { TutorialState } from "@cursorless/common"; -import { useEffect, useState, type FunctionComponent } from "react"; +import type { FunctionComponent } from "react"; +import { useEffect, useState } from "react"; import type { WebviewApi } from "vscode-webview"; import { TutorialStep } from "./TutorialStep"; import { Command } from "./Command"; diff --git a/packages/cursorless-vscode/src/ScopeTreeProvider.ts b/packages/cursorless-vscode/src/ScopeTreeProvider.ts index df38f06cba..50d050fa50 100644 --- a/packages/cursorless-vscode/src/ScopeTreeProvider.ts +++ b/packages/cursorless-vscode/src/ScopeTreeProvider.ts @@ -1,5 +1,6 @@ import type { CursorlessCommandId, + IDE, ScopeProvider, ScopeSupportInfo, ScopeType, @@ -15,10 +16,7 @@ import { serializeScopeType, uriEncodeHashId, } from "@cursorless/common"; -import { - ide, - type CustomSpokenFormGenerator, -} from "@cursorless/cursorless-engine"; +import { type CustomSpokenFormGenerator } from "@cursorless/cursorless-engine"; import { type VscodeApi } from "@cursorless/vscode-common"; import { isEqual } from "lodash-es"; import type { @@ -60,6 +58,7 @@ export class ScopeTreeProvider implements TreeDataProvider { this._onDidChangeTreeData.event; constructor( + private ide: IDE, private vscodeApi: VscodeApi, private context: ExtensionContext, private scopeProvider: ScopeProvider, @@ -173,7 +172,7 @@ export class ScopeTreeProvider implements TreeDataProvider { if (scopeSupport !== ScopeSupport.supportedAndPresentInEditor) { return null; } - const editor = ide().activeTextEditor; + const editor = this.ide.activeTextEditor; if (editor == null || editor.selections.length !== 1) { return null; } diff --git a/packages/cursorless-vscode/src/constructTestHelpers.ts b/packages/cursorless-vscode/src/constructTestHelpers.ts index dd3cdd7631..34bdef326e 100644 --- a/packages/cursorless-vscode/src/constructTestHelpers.ts +++ b/packages/cursorless-vscode/src/constructTestHelpers.ts @@ -35,8 +35,8 @@ export function constructTestHelpers( normalizedIde: NormalizedIDE, fileSystem: VscodeFileSystem, scopeProvider: ScopeProvider, - injectIde: (ide: IDE) => void, vscodeTutorial: VscodeTutorial, + injectIde: (ide: IDE) => void, ): VscodeTestHelpers | undefined { return { commandServerApi: commandServerApi!, diff --git a/packages/cursorless-vscode/src/extension.ts b/packages/cursorless-vscode/src/extension.ts index 57dbcd02dd..08ceac262b 100644 --- a/packages/cursorless-vscode/src/extension.ts +++ b/packages/cursorless-vscode/src/extension.ts @@ -77,21 +77,17 @@ export async function activate( const parseTreeApi = await getParseTreeApi(); const { vscodeIDE, hats, fileSystem } = await createVscodeIde(context); + const isTesting = vscodeIDE.runMode === "test"; const normalizedIde = vscodeIDE.runMode === "production" ? vscodeIDE - : new NormalizedIDE( - vscodeIDE, - new FakeIDE(), - vscodeIDE.runMode === "test", - ); + : new NormalizedIDE(vscodeIDE, new FakeIDE(), isTesting); const fakeCommandServerApi = new FakeCommandServerApi(); - const commandServerApi = - normalizedIde.runMode === "test" - ? fakeCommandServerApi - : await getCommandServerApi(); + const commandServerApi = isTesting + ? fakeCommandServerApi + : await getCommandServerApi(); const treeSitter = createTreeSitter(parseTreeApi); const talonSpokenForms = new FileSystemTalonSpokenForms(fileSystem); @@ -133,9 +129,11 @@ export async function activate( ); const testCaseRecorder = new TestCaseRecorder( + normalizedIde, commandServerApi, hatTokenMap, storedTargets, + injectIde, ); addCommandRunnerDecorator(testCaseRecorder); @@ -155,6 +153,7 @@ export async function activate( ); new ScopeTreeProvider( + normalizedIde, vscodeApi, context, scopeProvider, @@ -198,20 +197,19 @@ export async function activate( installationDependencies.maybeShow(); return { - testHelpers: - normalizedIde.runMode === "test" - ? constructTestHelpers( - fakeCommandServerApi, - storedTargets, - hatTokenMap, - vscodeIDE, - normalizedIde as NormalizedIDE, - fileSystem, - scopeProvider, - injectIde, - vscodeTutorial, - ) - : undefined, + testHelpers: isTesting + ? constructTestHelpers( + fakeCommandServerApi, + storedTargets, + hatTokenMap, + vscodeIDE, + normalizedIde as NormalizedIDE, + fileSystem, + scopeProvider, + vscodeTutorial, + injectIde, + ) + : undefined, }; } diff --git a/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeFancyRangeHighlighter/VscodeFancyRangeHighlighterRenderer.ts b/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeFancyRangeHighlighter/VscodeFancyRangeHighlighterRenderer.ts index 883d263822..8d36fee6d6 100644 --- a/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeFancyRangeHighlighter/VscodeFancyRangeHighlighterRenderer.ts +++ b/packages/cursorless-vscode/src/ide/vscode/VSCodeScopeVisualizer/VscodeFancyRangeHighlighter/VscodeFancyRangeHighlighterRenderer.ts @@ -1,10 +1,10 @@ +import type { DecorationStyle } from "@cursorless/common"; import { BORDER_WIDTH, CompositeKeyDefaultMap, getBorderColor, getBorderRadius, getBorderStyle, - type DecorationStyle, } from "@cursorless/common"; import { toVscodeRange } from "@cursorless/vscode-common"; import type { DecorationRenderOptions, TextEditorDecorationType } from "vscode"; diff --git a/packages/cursorless-vscode/src/registerCommands.ts b/packages/cursorless-vscode/src/registerCommands.ts index ba436f32a5..ebd514f9a1 100644 --- a/packages/cursorless-vscode/src/registerCommands.ts +++ b/packages/cursorless-vscode/src/registerCommands.ts @@ -116,7 +116,7 @@ export function registerCommands( // Command history ["cursorless.analyzeCommandHistory"]: () => - analyzeCommandHistory(commandHistoryStorage), + analyzeCommandHistory(vscodeIde, commandHistoryStorage), // General keyboard commands ["cursorless.keyboard.escape"]: diff --git a/packages/meta-updater/src/updateScopeMdx.ts b/packages/meta-updater/src/updateScopeMdx.ts index 31f7b14361..edae78b493 100644 --- a/packages/meta-updater/src/updateScopeMdx.ts +++ b/packages/meta-updater/src/updateScopeMdx.ts @@ -1,9 +1,9 @@ +import type { ScopeTypeType } from "@cursorless/common"; import { plaintextScopeSupportFacetInfos, prettifyScopeType, scopeSupportFacetInfos, serializeScopeType, - type ScopeTypeType, } from "@cursorless/common"; import type { FormatPluginFnOptions } from "@pnpm/meta-updater"; diff --git a/packages/meta-updater/src/updatesScopeSupportFacetInfos.ts b/packages/meta-updater/src/updatesScopeSupportFacetInfos.ts index cf27003b72..262008776b 100644 --- a/packages/meta-updater/src/updatesScopeSupportFacetInfos.ts +++ b/packages/meta-updater/src/updatesScopeSupportFacetInfos.ts @@ -1,9 +1,11 @@ +import type { + ScopeSupportFacet, + PlaintextScopeSupportFacet, +} from "@cursorless/common"; import { scopeSupportFacetInfos, serializeScopeType, plaintextScopeSupportFacetInfos, - type ScopeSupportFacet, - type PlaintextScopeSupportFacet, } from "@cursorless/common"; import type { FormatPluginFnOptions } from "@pnpm/meta-updater"; diff --git a/packages/node-common/src/getScopeTestPathsRecursively.ts b/packages/node-common/src/getScopeTestPathsRecursively.ts index 645e437ce3..6eb757fd96 100644 --- a/packages/node-common/src/getScopeTestPathsRecursively.ts +++ b/packages/node-common/src/getScopeTestPathsRecursively.ts @@ -1,10 +1,7 @@ import { readFileSync } from "node:fs"; import { groupBy } from "lodash-es"; -import { - getScopeTestConfigPaths, - getScopeTestPaths, - type ScopeTestPath, -} from "./getFixturePaths"; +import type { ScopeTestPath } from "./getFixturePaths"; +import { getScopeTestConfigPaths, getScopeTestPaths } from "./getFixturePaths"; export interface ScopeTestConfig { imports?: string[]; diff --git a/packages/test-case-recorder/src/TestCaseRecorder.ts b/packages/test-case-recorder/src/TestCaseRecorder.ts index 56924ea720..dc9a17a1e0 100644 --- a/packages/test-case-recorder/src/TestCaseRecorder.ts +++ b/packages/test-case-recorder/src/TestCaseRecorder.ts @@ -30,8 +30,6 @@ import type { } from "@cursorless/cursorless-engine"; import { defaultSpokenFormMap, - ide, - injectIde, SpokenFormGenerator, } from "@cursorless/cursorless-engine"; import { getRecordedTestsDirPath, walkDirsSync } from "@cursorless/node-common"; @@ -72,11 +70,13 @@ export class TestCaseRecorder { private spokenFormGenerator = new SpokenFormGenerator(defaultSpokenFormMap); constructor( + private ide: IDE, private commandServerApi: CommandServerApi | undefined, private hatTokenMap: HatTokenMap, private storedTargets: StoredTargetMap, + private injectIde: (ide: IDE) => void, ) { - const { runMode } = ide(); + const { runMode } = ide; this.fixtureRoot = runMode === "development" || runMode === "test" @@ -93,7 +93,7 @@ export class TestCaseRecorder { async toggle(options?: RecordTestCaseCommandOptions) { if (this.active) { void showInfo( - ide().messages, + this.ide.messages, "recordStop", "Stopped recording test cases", ); @@ -149,8 +149,8 @@ export class TestCaseRecorder { undefined, ["clipboard"], this.active ? this.extraSnapshotFields : undefined, - ide().activeTextEditor!, - ide(), + this.ide.activeTextEditor!, + this.ide, marks, this.active ? { startTimestamp: this.startTimestamp } : undefined, metadata, @@ -230,7 +230,7 @@ export class TestCaseRecorder { this.paused = false; void showInfo( - ide().messages, + this.ide.messages, "recordStart", `Recording test cases for following commands in:\n${this.targetDirectory}`, ); @@ -241,8 +241,8 @@ export class TestCaseRecorder { private async recordStartTime(showCalibrationDisplay: boolean) { if (showCalibrationDisplay) { await Promise.all( - ide().visibleTextEditors.map((editor) => - ide().setHighlightRanges(TIMING_CALIBRATION_HIGHLIGHT_ID, editor, [ + this.ide.visibleTextEditors.map((editor) => + this.ide.setHighlightRanges(TIMING_CALIBRATION_HIGHLIGHT_ID, editor, [ toLineRange(editor.document.range), ]), ), @@ -258,8 +258,12 @@ export class TestCaseRecorder { if (showCalibrationDisplay) { await Promise.all( - ide().visibleTextEditors.map((editor) => - ide().setHighlightRanges(TIMING_CALIBRATION_HIGHLIGHT_ID, editor, []), + this.ide.visibleTextEditors.map((editor) => + this.ide.setHighlightRanges( + TIMING_CALIBRATION_HIGHLIGHT_ID, + editor, + [], + ), ), ); } @@ -286,9 +290,9 @@ export class TestCaseRecorder { await this.finishTestCase(); } else { // Otherwise, we are starting a new test case - this.originalIde = ide(); + this.originalIde = this.ide; this.spyIde = new SpyIDE(this.originalIde); - injectIde(this.spyIde); + this.injectIde(this.spyIde); const spokenForm = this.spokenFormGenerator.processCommand(command); @@ -320,7 +324,7 @@ export class TestCaseRecorder { await this.testCase.recordInitialState(); - const editor = ide().activeTextEditor!; + const editor = this.ide.activeTextEditor!; if (editor.document.getText().includes("\r\n")) { throw Error( @@ -331,7 +335,7 @@ export class TestCaseRecorder { // NB: We need to copy the editor options rather than storing a reference // because its properties are lazy this.originalTextEditorOptions = { ...editor.options }; - ide().getEditableTextEditor(editor).options = + this.ide.getEditableTextEditor(editor).options = DEFAULT_TEXT_EDITOR_OPTIONS_FOR_TEST; } } @@ -370,14 +374,14 @@ export class TestCaseRecorder { } void showInfo( - ide().messages, + this.ide.messages, "testCaseSaved", message, "View", "Delete", ).then(async (action) => { if (action === "View") { - await ide().openTextDocument(outPath); + await this.ide.openTextDocument(outPath); } if (action === "Delete") { try { @@ -410,11 +414,15 @@ export class TestCaseRecorder { } catch (e) { const errorMessage = '"Cursorless record" must be run from within cursorless directory'; - void showError(ide().messages, "promptSubdirectoryError", errorMessage); + void showError( + this.ide.messages, + "promptSubdirectoryError", + errorMessage, + ); throw new Error(errorMessage, { cause: e }); } - const subdirectorySelection = await ide().showQuickPick( + const subdirectorySelection = await this.ide.showQuickPick( walkDirsSync(this.fixtureRoot).concat("/"), { title: "Select directory for new test cases", @@ -460,13 +468,13 @@ export class TestCaseRecorder { finallyHook() { if (this.originalIde != null) { - injectIde(this.originalIde); + this.injectIde(this.originalIde); } this.spyIde = undefined; this.originalIde = undefined; - const editor = ide().activeTextEditor!; - ide().getEditableTextEditor(editor).options = + const editor = this.ide.activeTextEditor!; + this.ide.getEditableTextEditor(editor).options = this.originalTextEditorOptions; }