diff --git a/packages/lib-common/src/types/command/PartialTargetDescriptor.types.ts b/packages/lib-common/src/types/command/PartialTargetDescriptor.types.ts index 13a9239a9d..191430f7b8 100644 --- a/packages/lib-common/src/types/command/PartialTargetDescriptor.types.ts +++ b/packages/lib-common/src/types/command/PartialTargetDescriptor.types.ts @@ -260,11 +260,6 @@ export interface SurroundingPairInteriorScopeType { delimiter: SurroundingPairName; } -export interface OneOfScopeType { - type: "oneOf"; - scopeTypes: ScopeType[]; -} - export interface GlyphScopeType { type: "glyph"; character: string; @@ -275,7 +270,6 @@ export type ScopeType = | SurroundingPairScopeType | SurroundingPairInteriorScopeType | CustomRegexScopeType - | OneOfScopeType | GlyphScopeType; export interface ContainingSurroundingPairModifier extends ContainingScopeModifier { diff --git a/packages/lib-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts b/packages/lib-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts index 6a9ab25a29..3fde97e050 100644 --- a/packages/lib-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts +++ b/packages/lib-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts @@ -226,7 +226,6 @@ export class PrimitiveTargetSpokenFormGenerator { handleScopeType(scopeType: ScopeType): SpokenFormComponent { switch (scopeType.type) { - case "oneOf": case "surroundingPairInterior": throw new NoSpokenFormError(`Scope type '${scopeType.type}'`); case "glyph": diff --git a/packages/lib-engine/src/processTargets/ModifierStageFactory.ts b/packages/lib-engine/src/processTargets/ModifierStageFactory.ts index e0ea848494..b0a3c22d65 100644 --- a/packages/lib-engine/src/processTargets/ModifierStageFactory.ts +++ b/packages/lib-engine/src/processTargets/ModifierStageFactory.ts @@ -1,6 +1,7 @@ import type { Modifier } from "@cursorless/lib-common"; import type { ModifierStage } from "./PipelineStages.types"; +import type { ComplexModifier } from "./modifiers/modifier.types"; export interface ModifierStageFactory { - create(modifier: Modifier): ModifierStage; + create(modifier: Modifier | ComplexModifier): ModifierStage; } diff --git a/packages/lib-engine/src/processTargets/ModifierStageFactoryImpl.ts b/packages/lib-engine/src/processTargets/ModifierStageFactoryImpl.ts index ab6ade2553..cb784472a0 100644 --- a/packages/lib-engine/src/processTargets/ModifierStageFactoryImpl.ts +++ b/packages/lib-engine/src/processTargets/ModifierStageFactoryImpl.ts @@ -24,6 +24,7 @@ import { RangeModifierStage } from "./modifiers/RangeModifierStage"; import { RawSelectionStage } from "./modifiers/RawSelectionStage"; import { RelativeScopeStage } from "./modifiers/RelativeScopeStage"; import { VisibleStage } from "./modifiers/VisibleStage"; +import type { ComplexModifier } from "./modifiers/modifier.types"; import type { ScopeHandlerFactory } from "./modifiers/scopeHandlers/ScopeHandlerFactory"; export class ModifierStageFactoryImpl implements ModifierStageFactory { @@ -35,7 +36,7 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { this.create = this.create.bind(this); } - create(modifier: Modifier): ModifierStage { + create(modifier: Modifier | ComplexModifier): ModifierStage { switch (modifier.type) { case "startOf": return new StartOfStage(); @@ -62,11 +63,10 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { if (ClassFunctionNameStage.use(modifier.scopeType)) { return new ClassFunctionNameStage(this, modifier); } - return new ContainingScopeStage( - this, - this.scopeHandlerFactory, - modifier, - ); + return new ContainingScopeStage(this.scopeHandlerFactory, modifier); + + case "complexContainingScope": + return new ContainingScopeStage(this.scopeHandlerFactory, modifier); case "preferredScope": if (ClassFunctionNameStage.use(modifier.scopeType)) { diff --git a/packages/lib-engine/src/processTargets/modifiers/ContainingScopeStage.ts b/packages/lib-engine/src/processTargets/modifiers/ContainingScopeStage.ts index fd3b7a3f46..35359688f5 100644 --- a/packages/lib-engine/src/processTargets/modifiers/ContainingScopeStage.ts +++ b/packages/lib-engine/src/processTargets/modifiers/ContainingScopeStage.ts @@ -1,9 +1,9 @@ import type { ContainingScopeModifier } from "@cursorless/lib-common"; import { NoContainingScopeError } from "@cursorless/lib-common"; import type { Target } from "../../typings/target.types"; -import type { ModifierStageFactory } from "../ModifierStageFactory"; import type { ModifierStage } from "../PipelineStages.types"; import { getContainingScopeTarget } from "./getContainingScopeTarget"; +import type { ComplexContainingScopeModifier } from "./modifier.types"; import type { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory"; /** @@ -26,9 +26,8 @@ import type { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory"; */ export class ContainingScopeStage implements ModifierStage { constructor( - private modifierStageFactory: ModifierStageFactory, private scopeHandlerFactory: ScopeHandlerFactory, - private modifier: ContainingScopeModifier, + private modifier: ContainingScopeModifier | ComplexContainingScopeModifier, ) {} run(target: Target): Target[] { diff --git a/packages/lib-engine/src/processTargets/modifiers/HeadTailStage.ts b/packages/lib-engine/src/processTargets/modifiers/HeadTailStage.ts index f1a0b72308..81747bbd19 100644 --- a/packages/lib-engine/src/processTargets/modifiers/HeadTailStage.ts +++ b/packages/lib-engine/src/processTargets/modifiers/HeadTailStage.ts @@ -101,7 +101,7 @@ class BoundedLineStage implements ModifierStage { try { return this.modifierStageFactory .create({ - type: "containingScope", + type: "complexContainingScope", scopeType: compoundInteriorScopeType, }) .run(target, options)[0]; diff --git a/packages/lib-engine/src/processTargets/modifiers/InteriorStage.ts b/packages/lib-engine/src/processTargets/modifiers/InteriorStage.ts index 4fef6dcc48..340907d499 100644 --- a/packages/lib-engine/src/processTargets/modifiers/InteriorStage.ts +++ b/packages/lib-engine/src/processTargets/modifiers/InteriorStage.ts @@ -1,4 +1,4 @@ -import type { InteriorOnlyModifier, ScopeType } from "@cursorless/lib-common"; +import type { InteriorOnlyModifier } from "@cursorless/lib-common"; import { NoContainingScopeError, UnsupportedScopeError, @@ -9,6 +9,7 @@ import type { ModifierStage, ModifierStateOptions, } from "../PipelineStages.types"; +import type { SortedScopeType } from "./scopeHandlers/scopeHandler.types"; export class InteriorOnlyStage implements ModifierStage { constructor( @@ -56,7 +57,7 @@ export class InteriorOnlyStage implements ModifierStage { try { return this.modifierStageFactory .create({ - type: "containingScope", + type: "complexContainingScope", scopeType: compoundInteriorScopeType, }) .run(target, options); @@ -69,8 +70,8 @@ export class InteriorOnlyStage implements ModifierStage { } } -export const compoundInteriorScopeType: ScopeType = { - type: "oneOf", +export const compoundInteriorScopeType: SortedScopeType = { + type: "sorted", scopeTypes: [ { type: "interior", diff --git a/packages/lib-engine/src/processTargets/modifiers/PreferredScopeStage.ts b/packages/lib-engine/src/processTargets/modifiers/PreferredScopeStage.ts index 9e1296e8db..c7b75c5cd2 100644 --- a/packages/lib-engine/src/processTargets/modifiers/PreferredScopeStage.ts +++ b/packages/lib-engine/src/processTargets/modifiers/PreferredScopeStage.ts @@ -24,7 +24,6 @@ export class PreferredScopeStage implements ModifierStage { const { scopeType } = this.modifier; const containingScopeStage = new ContainingScopeStage( - this.modifierStageFactory, this.scopeHandlerFactory, { type: "containingScope", scopeType }, ); diff --git a/packages/lib-engine/src/processTargets/modifiers/modifier.types.ts b/packages/lib-engine/src/processTargets/modifiers/modifier.types.ts new file mode 100644 index 0000000000..ac61c319e5 --- /dev/null +++ b/packages/lib-engine/src/processTargets/modifiers/modifier.types.ts @@ -0,0 +1,10 @@ +import type { ScopeType } from "@cursorless/lib-common"; +import type { ComplexScopeType } from "./scopeHandlers/scopeHandler.types"; + +export interface ComplexContainingScopeModifier { + type: "complexContainingScope"; + scopeType: ScopeType | ComplexScopeType; + ancestorIndex?: number; +} + +export type ComplexModifier = ComplexContainingScopeModifier; diff --git a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts index 3e3ed5d407..e2007cf49c 100644 --- a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts +++ b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/BoundedScopeHandler.ts @@ -17,6 +17,7 @@ import type { TargetScope } from "./scope.types"; import type { ScopeHandler, ScopeIteratorRequirements, + SortedScopeType, } from "./scopeHandler.types"; import type { ScopeHandlerFactory } from "./ScopeHandlerFactory"; import { isEveryScopeModifier } from "./util/isHintsEveryScope"; @@ -46,17 +47,9 @@ abstract class BoundedBaseScopeHandler extends BaseScopeHandler { ); } - get iterationScopeType(): ScopeType { - switch (this.targetScopeHandler.iterationScopeType.type) { - case "custom": - case "fallback": - case "conditional": - throw Error( - `Iteration scope type can't be '${this.targetScopeHandler.iterationScopeType.type}' for BoundedBaseScopeHandler`, - ); - } + get iterationScopeType(): SortedScopeType { return { - type: "oneOf", + type: "sorted", scopeTypes: [ this.targetScopeHandler.iterationScopeType, { diff --git a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/CollectionItemScopeHandler.ts b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/CollectionItemScopeHandler.ts index b3bfa3bc2f..4b502f0379 100644 --- a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/CollectionItemScopeHandler.ts +++ b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/CollectionItemScopeHandler/CollectionItemScopeHandler.ts @@ -46,11 +46,10 @@ export class CollectionItemScopeHandler extends BaseScopeHandler { return textualScopeHandler; } - return SortedScopeHandler.createFromScopeHandlers( - scopeHandlerFactory, - languageId, - [languageScopeHandler, textualScopeHandler], - ); + return new SortedScopeHandler(scopeHandlerFactory, languageId, [ + languageScopeHandler, + textualScopeHandler, + ]); })(); } diff --git a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/ConditionalScopeHandler.ts b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/ConditionalScopeHandler.ts index d50b0312f5..46f505e844 100644 --- a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/ConditionalScopeHandler.ts +++ b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/ConditionalScopeHandler.ts @@ -10,6 +10,7 @@ import { BaseScopeHandler } from "./BaseScopeHandler"; import type { TargetScope } from "./scope.types"; import type { ConditionalScopeType, + ScopeHandler, ScopeIteratorRequirements, } from "./scopeHandler.types"; import type { ScopeHandlerFactory } from "./ScopeHandlerFactory"; @@ -18,10 +19,26 @@ export class ConditionalScopeHandler extends BaseScopeHandler { public scopeType = undefined; protected isHierarchical = true; - constructor( - public scopeHandlerFactory: ScopeHandlerFactory, + static maybeCreate( + scopeHandlerFactory: ScopeHandlerFactory, + conditionalScopeType: ConditionalScopeType, + languageId: string, + ): ConditionalScopeHandler | undefined { + const scopeHandler = scopeHandlerFactory.maybeCreate( + conditionalScopeType.scopeType, + languageId, + ); + + if (scopeHandler == null) { + return undefined; + } + + return new ConditionalScopeHandler(scopeHandler, conditionalScopeType); + } + + private constructor( + private scopeHandler: ScopeHandler, private conditionalScopeType: ConditionalScopeType, - private languageId: string, ) { super(); } @@ -38,13 +55,8 @@ export class ConditionalScopeHandler extends BaseScopeHandler { direction: Direction, hints: ScopeIteratorRequirements, ): Iterable { - const scopeHandler = this.scopeHandlerFactory.create( - this.conditionalScopeType.scopeType, - this.languageId, - ); - return ifilter( - scopeHandler.generateScopes(editor, position, direction, hints), + this.scopeHandler.generateScopes(editor, position, direction, hints), this.conditionalScopeType.predicate, ); } diff --git a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/FallbackScopeHandler.ts b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/FallbackScopeHandler.ts index d9bc38fa4f..445812f533 100644 --- a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/FallbackScopeHandler.ts +++ b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/FallbackScopeHandler.ts @@ -1,8 +1,6 @@ import { - NoContainingScopeError, type Direction, type Position, - type ScopeType, type TextEditor, } from "@cursorless/lib-common"; import { BaseScopeHandler } from "./BaseScopeHandler"; @@ -18,30 +16,39 @@ export class FallbackScopeHandler extends BaseScopeHandler { public scopeType = undefined; protected isHierarchical = true; - static create( + static maybeCreate( scopeHandlerFactory: ScopeHandlerFactory, scopeType: FallbackScopeType, languageId: string, - ): ScopeHandler { - const scopeHandlers = scopeType.scopeTypes.map((scopeType) => - scopeHandlerFactory.create(scopeType, languageId), - ); + ): ScopeHandler | undefined { + const scopeHandlers = scopeType.scopeTypes + .map((scopeType) => + scopeHandlerFactory.maybeCreate(scopeType, languageId), + ) + .filter( + (scopeHandler): scopeHandler is ScopeHandler => scopeHandler != null, + ); - return this.createFromScopeHandlers(scopeHandlers); - } + if (scopeHandlers.length === 0) { + return undefined; + } + + if (scopeHandlers.length === 1) { + return scopeHandlers[0]; + } - static createFromScopeHandlers(scopeHandlers: ScopeHandler[]): ScopeHandler { return new FallbackScopeHandler(scopeHandlers); } - private constructor(private scopeHandlers: ScopeHandler[]) { + constructor(private scopeHandlers: ScopeHandler[]) { super(); } - get iterationScopeType(): ScopeType { - throw new NoContainingScopeError( - "Iteration scope for FallbackScopeHandler", - ); + get iterationScopeType(): FallbackScopeType { + return { + type: "fallback", + scopeTypes: this.scopeHandlers.map((s) => s.iterationScopeType), + }; } *generateScopeCandidates( diff --git a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts index ce13253aa3..602cbbdffb 100644 --- a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts +++ b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/NotebookCellScopeHandler.ts @@ -46,11 +46,10 @@ export class NotebookCellScopeHandler extends BaseScopeHandler { return apiScopeHandler; } - return SortedScopeHandler.createFromScopeHandlers( - scopeHandlerFactory, - languageId, - [languageScopeHandler, apiScopeHandler], - ); + return new SortedScopeHandler(scopeHandlerFactory, languageId, [ + languageScopeHandler, + apiScopeHandler, + ]); })(); } diff --git a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts index dd1a20ee29..6225b8b53a 100644 --- a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts +++ b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts @@ -135,12 +135,12 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { ); case "custom": return scopeType.scopeHandler; - case "oneOf": - return SortedScopeHandler.create(this, scopeType, languageId); + case "sorted": + return SortedScopeHandler.maybeCreate(this, scopeType, languageId); case "fallback": - return FallbackScopeHandler.create(this, scopeType, languageId); + return FallbackScopeHandler.maybeCreate(this, scopeType, languageId); case "conditional": - return new ConditionalScopeHandler(this, scopeType, languageId); + return ConditionalScopeHandler.maybeCreate(this, scopeType, languageId); default: // Pseudoscopes are handled separately in their own modifiers. if (pseudoScopes.has(scopeType.type)) { diff --git a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/SortedScopeHandler.ts b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/SortedScopeHandler.ts index e798279326..10dc28fb2c 100644 --- a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/SortedScopeHandler.ts +++ b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/SortedScopeHandler.ts @@ -1,9 +1,4 @@ -import type { - Direction, - OneOfScopeType, - Position, - TextEditor, -} from "@cursorless/lib-common"; +import type { Direction, Position, TextEditor } from "@cursorless/lib-common"; import { BaseScopeHandler } from "./BaseScopeHandler"; import { advanceIteratorsUntil, getInitialIteratorInfos } from "./IteratorInfo"; import type { ScopeHandlerFactory } from "./ScopeHandlerFactory"; @@ -13,6 +8,7 @@ import type { CustomScopeType, ScopeHandler, ScopeIteratorRequirements, + SortedScopeType, } from "./scopeHandler.types"; export class SortedScopeHandler extends BaseScopeHandler { @@ -21,11 +17,11 @@ export class SortedScopeHandler extends BaseScopeHandler { private iterationScopeHandler: SortedScopeHandler | undefined; private lastYieldedIndex: number | undefined; - static create( + static maybeCreate( scopeHandlerFactory: ScopeHandlerFactory, - scopeType: OneOfScopeType, + scopeType: SortedScopeType, languageId: string, - ): ScopeHandler { + ): ScopeHandler | undefined { const scopeHandlers = scopeType.scopeTypes .map((scopeType) => scopeHandlerFactory.maybeCreate(scopeType, languageId), @@ -34,50 +30,39 @@ export class SortedScopeHandler extends BaseScopeHandler { (scopeHandler): scopeHandler is ScopeHandler => scopeHandler != null, ); + if (scopeHandlers.length === 0) { + return undefined; + } + if (scopeHandlers.length === 1) { return scopeHandlers[0]; } - return this.createFromScopeHandlers( + return new SortedScopeHandler( scopeHandlerFactory, languageId, scopeHandlers, ); } - static createFromScopeHandlers( - scopeHandlerFactory: ScopeHandlerFactory, - languageId: string, - scopeHandlers: ScopeHandler[], - ): ScopeHandler { - const getIterationScopeHandler = () => - new SortedScopeHandler( - scopeHandlers.map((scopeHandler) => - scopeHandlerFactory.create( - scopeHandler.iterationScopeType, - languageId, - ), - ), - () => { - throw new Error( - "SortedScopeHandler: Iteration scope is not implemented", - ); - }, - ); - - return new SortedScopeHandler(scopeHandlers, getIterationScopeHandler); - } - - private constructor( + constructor( + private scopeHandlerFactory: ScopeHandlerFactory, + private languageId: string, private scopeHandlers: ScopeHandler[], - private getIterationScopeHandler: () => SortedScopeHandler, ) { super(); } get iterationScopeType(): CustomScopeType { if (this.iterationScopeHandler == null) { - this.iterationScopeHandler = this.getIterationScopeHandler(); + const iterationScopeHandlers = this.scopeHandlers.map((s) => + this.scopeHandlerFactory.create(s.iterationScopeType, this.languageId), + ); + this.iterationScopeHandler = new SortedScopeHandler( + this.scopeHandlerFactory, + this.languageId, + iterationScopeHandlers, + ); } return { type: "custom", diff --git a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts index fa975cabae..b1bbb335cb 100644 --- a/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts +++ b/packages/lib-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts @@ -25,6 +25,15 @@ export interface FallbackScopeType { scopeTypes: (ScopeType | ComplexScopeType)[]; } +/** + * Used to handle sorted scope types. The scope types are yielded in sorted + * order. + */ +export interface SortedScopeType { + type: "sorted"; + scopeTypes: (ScopeType | ComplexScopeType)[]; +} + /** * Used to handle conditional scope types. The predicate determines if the * scope should be yielded or not. @@ -38,6 +47,7 @@ export interface ConditionalScopeType { export type ComplexScopeType = | CustomScopeType | FallbackScopeType + | SortedScopeType | ConditionalScopeType; /** diff --git a/packages/lib-engine/src/scopeProviders/ScopeInfoProvider.ts b/packages/lib-engine/src/scopeProviders/ScopeInfoProvider.ts index 78b815140a..d447cefa95 100644 --- a/packages/lib-engine/src/scopeProviders/ScopeInfoProvider.ts +++ b/packages/lib-engine/src/scopeProviders/ScopeInfoProvider.ts @@ -185,14 +185,5 @@ function isLanguageSpecific(scopeType: ScopeType): boolean { case "customRegex": case "glyph": return false; - - case "oneOf": - throw Error( - `Can't decide whether scope type ${JSON.stringify( - scopeType, - undefined, - 3, - )} is language-specific`, - ); } } diff --git a/tsconfig.base.json b/tsconfig.base.json index c5963ed8a1..d1a4dfbfa4 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -1,6 +1,5 @@ { "compilerOptions": { - "baseUrl": ".", "module": "ESNext", "moduleResolution": "Bundler", "moduleDetection": "force",