diff --git a/data/fixtures/recorded/everyScope/takeEveryToken.yml b/data/fixtures/recorded/everyScope/takeEveryToken.yml new file mode 100644 index 0000000000..723b3fdfaa --- /dev/null +++ b/data/fixtures/recorded/everyScope/takeEveryToken.yml @@ -0,0 +1,33 @@ +languageId: plaintext +command: + version: 7 + spokenForm: take every token + action: + name: setSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: token} + usePrePhraseSnapshot: false +initialState: + documentContents: |- + aaa bbb ccc + ddd eee + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 11} + - anchor: {line: 1, character: 4} + active: {line: 1, character: 7} + marks: {} +finalState: + documentContents: |- + aaa bbb ccc + ddd eee + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 7} + - anchor: {line: 0, character: 8} + active: {line: 0, character: 11} + - anchor: {line: 1, character: 4} + active: {line: 1, character: 7} diff --git a/packages/cursorless-engine/src/actions/snippetsLegacy/snippet.ts b/packages/cursorless-engine/src/actions/snippetsLegacy/snippet.ts index 8cffb3ac06..7989759b97 100644 --- a/packages/cursorless-engine/src/actions/snippetsLegacy/snippet.ts +++ b/packages/cursorless-engine/src/actions/snippetsLegacy/snippet.ts @@ -2,6 +2,8 @@ import type { SimpleScopeTypeType, SnippetDefinition, } from "@cursorless/common"; +import type { ModifierStageFactory } from "../../processTargets/ModifierStageFactory"; +import type { ModifierStateOptions } from "../../processTargets/PipelineStages.types"; import { Placeholder, Text, @@ -9,7 +11,6 @@ import { type TextmateSnippet, } from "../../snippets/vendor/vscodeSnippet/snippetParser"; import { KnownSnippetVariableNames } from "../../snippets/vendor/vscodeSnippet/snippetVariables"; -import type { ModifierStageFactory } from "../../processTargets/ModifierStageFactory"; import type { Target } from "../../typings/target.types"; /** @@ -138,6 +139,10 @@ function findMatchingSnippetDefinitionForSingleTarget( ): number { const languageId = target.editor.document.languageId; + const options: ModifierStateOptions = { + multipleTargets: false, + }; + // We want to find the first definition that matches the given context. // Note that we just use the first match we find because the definitions are // guaranteed to come sorted in precedence order. @@ -165,7 +170,7 @@ function findMatchingSnippetDefinitionForSingleTarget( type: "containingScope", scopeType: { type: scopeTypeType }, }) - .run(target)[0]; + .run(target, options)[0]; if (target.contentRange.isRangeEqual(containingTarget.contentRange)) { // Skip this scope if the target is exactly the same as the @@ -177,7 +182,7 @@ function findMatchingSnippetDefinitionForSingleTarget( scopeType: { type: scopeTypeType }, ancestorIndex: 1, }) - .run(target)[0]; + .run(target, options)[0]; } if ( diff --git a/packages/cursorless-engine/src/processTargets/PipelineStages.types.ts b/packages/cursorless-engine/src/processTargets/PipelineStages.types.ts index a2068d38ef..f0c3573180 100644 --- a/packages/cursorless-engine/src/processTargets/PipelineStages.types.ts +++ b/packages/cursorless-engine/src/processTargets/PipelineStages.types.ts @@ -4,6 +4,10 @@ export interface MarkStage { run(): Target[]; } +export interface ModifierStateOptions { + multipleTargets: boolean; +} + export interface ModifierStage { - run(target: Target): Target[]; + run(target: Target, options: ModifierStateOptions): Target[]; } diff --git a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts index 2f9c325894..6f99909b72 100644 --- a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts +++ b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts @@ -14,7 +14,11 @@ import type { import type { Target } from "../typings/target.types"; import type { MarkStageFactory } from "./MarkStageFactory"; import type { ModifierStageFactory } from "./ModifierStageFactory"; -import type { MarkStage, ModifierStage } from "./PipelineStages.types"; +import type { + MarkStage, + ModifierStage, + ModifierStateOptions, +} from "./PipelineStages.types"; import { createContinuousRangeTarget } from "./createContinuousRangeTarget"; import { ImplicitStage } from "./marks/ImplicitStage"; import { ContainingTokenIfUntypedEmptyStage } from "./modifiers/ConditionalModifierStages"; @@ -296,7 +300,10 @@ export function processModifierStages( // one-by-one and concatenating the results before passing them on to the // next stage. modifierStages.forEach((stage) => { - targets = targets.flatMap((target) => stage.run(target)); + const options: ModifierStateOptions = { + multipleTargets: targets.length > 1, + }; + targets = targets.flatMap((target) => stage.run(target, options)); }); // Then return the output from the final stage @@ -309,6 +316,9 @@ function getExcludedScope( scopeType: ScopeType, direction: Direction, ): Target { + const options: ModifierStateOptions = { + multipleTargets: false, + }; return ( modifierStageFactory .create({ @@ -321,7 +331,7 @@ function getExcludedScope( // NB: The following line assumes that content range is always // contained by domain, so that "every" will properly reconstruct // the target from the content range. - .run(target)[0] + .run(target, options)[0] ); } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/CascadingStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/CascadingStage.ts index e025326893..698fe9ee15 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/CascadingStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/CascadingStage.ts @@ -1,7 +1,10 @@ import type { CascadingModifier } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { ModifierStage } from "../PipelineStages.types"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; /** * Tries each of the given modifiers in turn until one of them doesn't throw an @@ -25,10 +28,10 @@ export class CascadingStage implements ModifierStage { return this.nestedStages_; } - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { for (const nestedStage of this.nestedStages) { try { - return nestedStage.run(target); + return nestedStage.run(target, options); } catch (_error) { continue; } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/ConditionalModifierStages.ts b/packages/cursorless-engine/src/processTargets/modifiers/ConditionalModifierStages.ts index cbb14cdfee..a3bdc7e205 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/ConditionalModifierStages.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/ConditionalModifierStages.ts @@ -1,7 +1,10 @@ import type { Modifier, ModifyIfUntypedModifier } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { ModifierStage } from "../PipelineStages.types"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; abstract class ConditionalModifierBaseStage implements ModifierStage { private nestedStage_?: ModifierStage; @@ -12,12 +15,12 @@ abstract class ConditionalModifierBaseStage implements ModifierStage { private nestedModifier: Modifier, ) {} - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { if (this.shouldModify(target)) { // Modify this target try { return this.nestedStage - .run(target) + .run(target, options) .map((newTarget) => newTarget.withThatTarget(target)); } catch (ex) { // suppressErrors === true => Allow this target to be returned unmodified diff --git a/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts index dff36ba8a7..35c019b9bb 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts @@ -2,9 +2,12 @@ import type { ContainingScopeModifier } 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"; -import type { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; import { getContainingScopeTarget } from "./getContainingScopeTarget"; +import type { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory"; /** * This modifier stage expands from the input target to the smallest containing @@ -31,7 +34,7 @@ export class ContainingScopeStage implements ModifierStage { private modifier: ContainingScopeModifier, ) {} - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { const { scopeType, ancestorIndex = 0 } = this.modifier; const scopeHandler = this.scopeHandlerFactory.maybeCreate( @@ -42,7 +45,7 @@ export class ContainingScopeStage implements ModifierStage { if (scopeHandler == null) { return this.modifierStageFactory .getLegacyScopeStage(this.modifier) - .run(target); + .run(target, options); } const containingScopes = getContainingScopeTarget( diff --git a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts index 1f529138d7..582ad2920e 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts @@ -1,8 +1,11 @@ -import type { EveryScopeModifier, TextEditor, Range } from "@cursorless/common"; +import type { EveryScopeModifier, Range, TextEditor } 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"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; import { getContainingScopeTarget } from "./getContainingScopeTarget"; import type { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory"; import type { TargetScope } from "./scopeHandlers/scope.types"; @@ -35,7 +38,7 @@ export class EveryScopeStage implements ModifierStage { private modifier: EveryScopeModifier, ) {} - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { const { scopeType } = this.modifier; const { editor, isReversed } = target; @@ -47,7 +50,7 @@ export class EveryScopeStage implements ModifierStage { if (scopeHandler == null) { return this.modifierStageFactory .getLegacyScopeStage(this.modifier) - .run(target); + .run(target, options); } let scopes: TargetScope[] | undefined; @@ -62,7 +65,8 @@ export class EveryScopeStage implements ModifierStage { if ( scopes.length === 1 && scopes[0].domain.contains(target.contentRange) && - !target.hasExplicitScopeType + !target.hasExplicitScopeType && + !options.multipleTargets ) { // If the only scope that came back completely contains the input target // range, we treat the input as if it had no explicit range, expanding diff --git a/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts index ea66c39525..0b01c72658 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/HeadTailStage.ts @@ -6,7 +6,10 @@ import { } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { ModifierStage } from "../PipelineStages.types"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; import { getModifierStagesFromTargetModifiers, processModifierStages, @@ -53,9 +56,9 @@ class BoundedLineStage implements ModifierStage { private modifier: HeadModifier | TailModifier, ) {} - run(target: Target): Target[] { - const line = this.getContainingLine(target); - const pairInterior = this.getContainingPairInterior(target); + run(target: Target, options: ModifierStateOptions): Target[] { + const line = this.getContainingLine(target, options); + const pairInterior = this.getContainingPairInterior(target, options); const intersection = pairInterior != null @@ -75,9 +78,12 @@ class BoundedLineStage implements ModifierStage { ]; } - private getContainingPairInterior(target: Target): Target | undefined { + private getContainingPairInterior( + target: Target, + options: ModifierStateOptions, + ): Target | undefined { try { - return this.getContaining(target, { + return this.getContaining(target, options, { type: "surroundingPairInterior", delimiter: "any", })[0]; @@ -89,15 +95,22 @@ class BoundedLineStage implements ModifierStage { } } - private getContainingLine(target: Target): Target { - return this.getContaining(target, { + private getContainingLine( + target: Target, + options: ModifierStateOptions, + ): Target { + return this.getContaining(target, options, { type: "line", })[0]; } - private getContaining(target: Target, scopeType: ScopeType): Target[] { + private getContaining( + target: Target, + options: ModifierStateOptions, + scopeType: ScopeType, + ): Target[] { return this.modifierStageFactory .create({ type: "containingScope", scopeType }) - .run(target); + .run(target, options); } } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts index 2572aa864a..c3e0ee7f26 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts @@ -9,13 +9,16 @@ import type { import { Range } from "@cursorless/common"; import { flatmap, ifilter, imap, itake } from "itertools"; import { escapeRegExp } from "lodash-es"; +import type { StoredTargetMap } from "../.."; import type { Target } from "../../typings/target.types"; import { generateMatchesInRange } from "../../util/getMatchesInRange"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { ModifierStage } from "../PipelineStages.types"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; import { PlainTarget } from "../targets"; import { ContainingTokenIfUntypedEmptyStage } from "./ConditionalModifierStages"; -import type { StoredTargetMap } from "../.."; import { OutOfRangeError } from "./listUtils"; export class InstanceStage implements ModifierStage { @@ -25,36 +28,40 @@ export class InstanceStage implements ModifierStage { private modifier: Modifier, ) {} - run(inputTarget: Target): Target[] { + run(inputTarget: Target, options: ModifierStateOptions): Target[] { // If the target is untyped and empty, we want to target the containing // token. This handles the case where they just say "instance" with an empty // selection, eg "take every instance". const target = new ContainingTokenIfUntypedEmptyStage( this.modifierStageFactory, - ).run(inputTarget)[0]; + ).run(inputTarget, options)[0]; switch (this.modifier.type) { case "everyScope": - return this.handleEveryScope(target); + return this.handleEveryScope(target, options); case "ordinalScope": - return this.handleOrdinalScope(target, this.modifier); + return this.handleOrdinalScope(target, options, this.modifier); case "relativeScope": - return this.handleRelativeScope(target, this.modifier); + return this.handleRelativeScope(target, options, this.modifier); default: throw Error(`${this.modifier.type} instance scope not supported`); } } - private handleEveryScope(target: Target): Target[] { + private handleEveryScope( + target: Target, + options: ModifierStateOptions, + ): Target[] { return Array.from( flatmap(this.getEveryRanges(target), ([editor, searchRange]) => - this.getTargetIterable(target, editor, searchRange, "forward"), + this.getTargetIterable(target, options, editor, searchRange, "forward"), ), ); } private handleOrdinalScope( target: Target, + options: ModifierStateOptions, { start, length, scopeType }: OrdinalScopeModifier, ): Target[] { return this.getEveryRanges(target).flatMap(([editor, searchRange]) => @@ -62,6 +69,7 @@ export class InstanceStage implements ModifierStage { scopeType, this.getTargetIterable( target, + options, editor, searchRange, start >= 0 ? "forward" : "backward", @@ -74,6 +82,7 @@ export class InstanceStage implements ModifierStage { private handleRelativeScope( target: Target, + options: ModifierStateOptions, { direction, offset, length, scopeType }: RelativeScopeModifier, ): Target[] { const referenceTargets = this.storedTargets.get("instanceReference") ?? [ @@ -99,7 +108,13 @@ export class InstanceStage implements ModifierStage { return takeFromOffset( scopeType, - this.getTargetIterable(target, editor, iterationRange, direction), + this.getTargetIterable( + target, + options, + editor, + iterationRange, + direction, + ), offset === 0 ? 0 : offset - 1, length, ); @@ -119,6 +134,7 @@ export class InstanceStage implements ModifierStage { private getTargetIterable( target: Target, + options: ModifierStateOptions, editor: TextEditor, searchRange: Range, direction: Direction, @@ -159,7 +175,10 @@ export class InstanceStage implements ModifierStage { // Just try to expand to the containing scope. If it fails or is not // equal to the target, we know the match is not a line, token or // word. - const containingScope = containingScopeModifier.run(target); + const containingScope = containingScopeModifier.run( + target, + options, + ); if ( containingScope.length === 1 && diff --git a/packages/cursorless-engine/src/processTargets/modifiers/InteriorStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/InteriorStage.ts index 3ae2d9899b..59ce7bd31b 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/InteriorStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/InteriorStage.ts @@ -4,7 +4,10 @@ import type { } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { ModifierStage } from "../PipelineStages.types"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; import { ModifyIfConditionStage } from "./ConditionalModifierStages"; export class InteriorOnlyStage implements ModifierStage { @@ -13,7 +16,7 @@ export class InteriorOnlyStage implements ModifierStage { private modifier: InteriorOnlyModifier, ) {} - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { const interior = target.getInterior(); if (interior != null) { @@ -28,7 +31,7 @@ export class InteriorOnlyStage implements ModifierStage { }, }); - return containingModifier.run(target); + return containingModifier.run(target, options); } } @@ -43,9 +46,9 @@ export class ExcludeInteriorStage implements ModifierStage { getContainingSurroundingPairIfNoBoundaryStage(this.modifierStageFactory); } - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { return this.containingSurroundingPairIfNoBoundaryStage - .run(target) + .run(target, options) .flatMap((target) => target.getBoundary()!); } } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/LeadingTrailingStages.ts b/packages/cursorless-engine/src/processTargets/modifiers/LeadingTrailingStages.ts index 2bd92a3cce..f4578a1eb5 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/LeadingTrailingStages.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/LeadingTrailingStages.ts @@ -1,7 +1,10 @@ import type { LeadingModifier, TrailingModifier } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { ModifierStage } from "../PipelineStages.types"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; import { containingTokenIfUntypedModifier } from "./commonContainingScopeIfUntypedModifiers"; /** @@ -21,10 +24,10 @@ export class LeadingStage implements ModifierStage { private modifier: LeadingModifier, ) {} - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { return this.modifierStageFactory .create(containingTokenIfUntypedModifier) - .run(target) + .run(target, options) .map((target) => { const leading = target.getLeadingDelimiterTarget(); if (leading == null) { @@ -41,10 +44,10 @@ export class TrailingStage implements ModifierStage { private modifier: TrailingModifier, ) {} - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { return this.modifierStageFactory .create(containingTokenIfUntypedModifier) - .run(target) + .run(target, options) .map((target) => { const trailing = target.getTrailingDelimiterTarget(); if (trailing == null) { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/OrdinalScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/OrdinalScopeStage.ts index 1e9e30d8a7..f1ddc58d36 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/OrdinalScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/OrdinalScopeStage.ts @@ -1,7 +1,10 @@ import type { OrdinalScopeModifier } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { ModifierStage } from "../PipelineStages.types"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; import { sliceStrict } from "./listUtils"; import { createRangeTargetFromIndices, @@ -14,10 +17,11 @@ export class OrdinalScopeStage implements ModifierStage { private modifier: OrdinalScopeModifier, ) {} - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { const targets = getEveryScopeTargets( this.modifierStageFactory, target, + options, this.modifier.scopeType, ); diff --git a/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts index 03e9fb7ca3..f2f0f7d412 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/PreferredScopeStage.ts @@ -5,7 +5,10 @@ import { } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { ModifierStage } from "../PipelineStages.types"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; import { ContainingScopeStage } from "./ContainingScopeStage"; import type { TargetScope } from "./scopeHandlers/scope.types"; import type { ScopeHandler } from "./scopeHandlers/scopeHandler.types"; @@ -23,7 +26,7 @@ export class PreferredScopeStage implements ModifierStage { private modifier: PreferredScopeModifier, ) {} - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { const { scopeType } = this.modifier; const containingScopeStage = new ContainingScopeStage( @@ -33,7 +36,7 @@ export class PreferredScopeStage implements ModifierStage { ); try { - return containingScopeStage.run(target); + return containingScopeStage.run(target, options); } catch (ex) { // NoContainingScopeError is thrown if no containing scope was found, which is fine. if (!(ex instanceof NoContainingScopeError)) { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/RangeModifierStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/RangeModifierStage.ts index 2c91c4b006..ebfdfaedab 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/RangeModifierStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/RangeModifierStage.ts @@ -1,7 +1,10 @@ import type { RangeModifier } from "@cursorless/common"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { ModifierStage } from "../PipelineStages.types"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; import { targetsToContinuousTarget } from "../TargetPipelineRunner"; export class RangeModifierStage implements ModifierStage { @@ -10,11 +13,11 @@ export class RangeModifierStage implements ModifierStage { private modifier: RangeModifier, ) {} - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { const anchorStage = this.modifierStageFactory.create(this.modifier.anchor); const activeStage = this.modifierStageFactory.create(this.modifier.active); - const anchorTargets = anchorStage.run(target); - const activeTargets = activeStage.run(target); + const anchorTargets = anchorStage.run(target, options); + const activeTargets = activeStage.run(target, options); if (anchorTargets.length !== 1 || activeTargets.length !== 1) { throw new Error("Expected single anchor and active target"); diff --git a/packages/cursorless-engine/src/processTargets/modifiers/RelativeScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/RelativeScopeStage.ts index 709304a361..60a05a93e7 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/RelativeScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/RelativeScopeStage.ts @@ -2,9 +2,16 @@ import { NoContainingScopeError, type RelativeScopeModifier, } from "@cursorless/common"; +import { islice, itake } from "itertools"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; -import type { ModifierStage } from "../PipelineStages.types"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../PipelineStages.types"; +import { constructScopeRangeTarget } from "./constructScopeRangeTarget"; +import { getPreferredScopeTouchingPosition } from "./getPreferredScopeTouchingPosition"; +import { OutOfRangeError } from "./listUtils"; import { runLegacy } from "./relativeScopeLegacy"; import type { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory"; import type { TargetScope } from "./scopeHandlers/scope.types"; @@ -12,10 +19,6 @@ import type { ContainmentPolicy, ScopeHandler, } from "./scopeHandlers/scopeHandler.types"; -import { islice, itake } from "itertools"; -import { OutOfRangeError } from "./listUtils"; -import { constructScopeRangeTarget } from "./constructScopeRangeTarget"; -import { getPreferredScopeTouchingPosition } from "./getPreferredScopeTouchingPosition"; /** * Handles relative modifiers input, eg "next funk", "two funks", "previous two @@ -30,14 +33,19 @@ export class RelativeScopeStage implements ModifierStage { private modifier: RelativeScopeModifier, ) {} - run(target: Target): Target[] { + run(target: Target, options: ModifierStateOptions): Target[] { const scopeHandler = this.scopeHandlerFactory.maybeCreate( this.modifier.scopeType, target.editor.document.languageId, ); if (scopeHandler == null) { - return runLegacy(this.modifierStageFactory, this.modifier, target); + return runLegacy( + this.modifierStageFactory, + this.modifier, + target, + options, + ); } const scopes = Array.from( diff --git a/packages/cursorless-engine/src/processTargets/modifiers/relativeScopeLegacy.ts b/packages/cursorless-engine/src/processTargets/modifiers/relativeScopeLegacy.ts index f757d88dbd..77223d02f4 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/relativeScopeLegacy.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/relativeScopeLegacy.ts @@ -2,13 +2,14 @@ import type { Range, RelativeScopeModifier } from "@cursorless/common"; import { findLastIndex } from "lodash-es"; import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; +import type { ModifierStateOptions } from "../PipelineStages.types"; import { UntypedTarget } from "../targets"; +import { OutOfRangeError } from "./listUtils"; import { createRangeTargetFromIndices, getEveryScopeTargets, } from "./targetSequenceUtils"; import { TooFewScopesError } from "./TooFewScopesError"; -import { OutOfRangeError } from "./listUtils"; interface ContainingIndices { start: number; @@ -19,6 +20,7 @@ export function runLegacy( modifierStageFactory: ModifierStageFactory, modifier: RelativeScopeModifier, target: Target, + options: ModifierStateOptions, ): Target[] { /** * A list of targets in the iteration scope for the input {@link target}. @@ -32,6 +34,7 @@ export function runLegacy( const targets = getEveryScopeTargets( modifierStageFactory, createTargetWithoutExplicitRange(target), + options, modifier.scopeType, ); diff --git a/packages/cursorless-engine/src/processTargets/modifiers/targetSequenceUtils.ts b/packages/cursorless-engine/src/processTargets/modifiers/targetSequenceUtils.ts index b2c528ad4c..373d647294 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/targetSequenceUtils.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/targetSequenceUtils.ts @@ -3,6 +3,7 @@ import type { Target } from "../../typings/target.types"; import type { ModifierStageFactory } from "../ModifierStageFactory"; import { createContinuousRangeTarget } from "../createContinuousRangeTarget"; import { assertIndices } from "./listUtils"; +import type { ModifierStateOptions } from "../PipelineStages.types"; /** * Construct a single range target between two targets in a list of targets, @@ -38,11 +39,12 @@ export function createRangeTargetFromIndices( export function getEveryScopeTargets( modifierStageFactory: ModifierStageFactory, target: Target, + options: ModifierStateOptions, scopeType: ScopeType, ): Target[] { const containingStage = modifierStageFactory.create({ type: "everyScope", scopeType, }); - return containingStage.run(target); + return containingStage.run(target, options); } diff --git a/packages/cursorless-engine/src/scopeProviders/getIterationScopeRanges.ts b/packages/cursorless-engine/src/scopeProviders/getIterationScopeRanges.ts index 678d6cc165..d4c9de3bb4 100644 --- a/packages/cursorless-engine/src/scopeProviders/getIterationScopeRanges.ts +++ b/packages/cursorless-engine/src/scopeProviders/getIterationScopeRanges.ts @@ -4,7 +4,10 @@ import type { TextEditor, } from "@cursorless/common"; import { map } from "itertools"; -import type { ModifierStage } from "../processTargets/PipelineStages.types"; +import type { + ModifierStage, + ModifierStateOptions, +} from "../processTargets/PipelineStages.types"; import type { ScopeHandler } from "../processTargets/modifiers/scopeHandlers/scopeHandler.types"; import type { Target } from "../typings/target.types"; import { getTargetRanges } from "./getTargetRanges"; @@ -27,6 +30,9 @@ export function getIterationScopeRanges( iterationRange: Range, includeIterationNestedTargets: boolean, ): IterationScopeRanges[] { + const options: ModifierStateOptions = { + multipleTargets: true, + }; return map( iterationScopeHandler.generateScopes( editor, @@ -43,7 +49,9 @@ export function getIterationScopeRanges( ranges: scope.getTargets(false).map((target) => ({ range: target.contentRange, targets: includeIterationNestedTargets - ? getEveryScopeLenient(everyStage, target).map(getTargetRanges) + ? getEveryScopeLenient(everyStage, target, options).map( + getTargetRanges, + ) : undefined, })), }; @@ -51,9 +59,13 @@ export function getIterationScopeRanges( ); } -function getEveryScopeLenient(everyStage: ModifierStage, target: Target) { +function getEveryScopeLenient( + everyStage: ModifierStage, + target: Target, + options: ModifierStateOptions, +) { try { - return everyStage.run(target); + return everyStage.run(target, options); } catch (err) { if ((err as Error).name === "NoContainingScopeError") { return [];