diff --git a/data/fixtures/recorded/implicitExpansion/bringAirToEndOfThis.yml b/data/fixtures/recorded/implicitExpansion/bringAirToEndOfThis.yml new file mode 100644 index 0000000000..ff63e89211 --- /dev/null +++ b/data/fixtures/recorded/implicitExpansion/bringAirToEndOfThis.yml @@ -0,0 +1,36 @@ +languageId: plaintext +command: + version: 7 + spokenForm: bring air to end of this + action: + name: replaceWithTarget + source: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: a} + destination: + type: primitive + insertionMode: to + target: + type: primitive + mark: {type: cursor} + modifiers: + - {type: endOf} + usePrePhraseSnapshot: false +initialState: + documentContents: |- + a + bbb + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 1} +finalState: + documentContents: |- + a + bbba + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} diff --git a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts index 7491f8cde3..2f9c325894 100644 --- a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts +++ b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts @@ -208,6 +208,7 @@ class TargetPipeline { ): Target[] { let markStage: MarkStage; let targetModifierStages: ModifierStage[]; + let automaticTokenExpansionBefore = false; if (targetDescriptor.type === "implicit") { markStage = new ImplicitStage(); @@ -218,28 +219,62 @@ class TargetPipeline { this.modifierStageFactory, targetDescriptor.modifiers, ); + automaticTokenExpansionBefore = + doAutomaticTokenExpansionBefore(targetDescriptor); } // First, get the targets output by the mark const markOutputTargets = markStage.run(); + const [preStages, postStages] = this.getPreAndPostStages( + automaticTokenExpansionBefore, + ); + /** * The modifier pipeline that will be applied to construct our final targets */ const modifierStages = [ + ...preStages, ...targetModifierStages, ...this.opts.actionFinalStages, - - // This performs auto-expansion to token when you say eg "take this" with an - // empty selection - ...(this.opts.noAutomaticTokenExpansion - ? [] - : [new ContainingTokenIfUntypedEmptyStage(this.modifierStageFactory)]), + ...postStages, ]; // Run all targets through the modifier stages return processModifierStages(modifierStages, markOutputTargets); } + + private getPreAndPostStages( + automaticTokenExpansionBefore: boolean, + ): [ModifierStage[], ModifierStage[]] { + if (this.opts.noAutomaticTokenExpansion) { + return [[], []]; + } + // This performs auto-expansion to token when you say eg "take this" with an + // empty selection + const stage = new ContainingTokenIfUntypedEmptyStage( + this.modifierStageFactory, + ); + if (automaticTokenExpansionBefore) { + return [[stage], []]; + } + return [[], [stage]]; + } +} + +/** + * Determines whether we should automatically expand the token before the target. + * True if the target has modifiers that are all "startOf" or "endOf". + */ +function doAutomaticTokenExpansionBefore( + targetDescriptor: PrimitiveTargetDescriptor, +): boolean { + return ( + targetDescriptor.modifiers.length > 0 && + targetDescriptor.modifiers.every( + ({ type }) => type === "startOf" || type === "endOf", + ) + ); } /** Convert a list of target modifiers to modifier stages */