From 7eb4e46fdb4e556b0cea90da3ecaeda398c6bb1a Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 24 May 2023 22:32:52 +0100 Subject: [PATCH 1/5] Support "instance" pseudo-scopes --- cursorless-talon/src/modifiers/scopes.py | 1 + .../command/PartialTargetDescriptor.types.ts | 1 + .../src/core/handleHoistedModifiers.ts | 18 ++- .../processTargets/ModifierStageFactory.ts | 1 + .../ModifierStageFactoryImpl.ts | 20 +++ .../modifiers/ContainingScopeStage.ts | 6 + .../modifiers/EveryScopeStage.ts | 6 + .../processTargets/modifiers/InstanceStage.ts | 141 ++++++++++++++++++ .../modifiers/RelativeExclusiveScopeStage.ts | 6 + .../modifiers/RelativeInclusiveScopeStage.ts | 6 + .../scopeHandlers/BaseScopeHandler.ts | 1 + .../scopeHandlers/ScopeHandlerFactoryImpl.ts | 17 +++ .../scopeHandlers/scopeHandler.types.ts | 6 + .../bringDrumAfterTwoInstancesAir.yml | 37 +++++ .../instance/chuckTwoInstancesLineAir.yml | 37 +++++ .../instance/clearEveryInstanceAir.yml | 36 +++++ .../instance/clearEveryInstanceAirPastBat.yml | 40 +++++ ...clearEveryInstanceEveryTokenAirPastBat.yml | 46 ++++++ .../instance/clearEveryInstancePastBat.yml | 34 +++++ .../clearEveryInstanceThisPastBat.yml | 35 +++++ .../instance/clearFirstInstanceAir.yml | 30 ++++ .../instance/clearNextInstanceAir.yml | 31 ++++ .../instance/clearNextInstanceAirPastBat.yml | 41 +++++ .../instance/clearNextTwoInstancesAir.yml | 33 ++++ .../instance/clearPastNextInstance.yml | 38 +++++ .../instance/clearPreviousTwoInstancesAir.yml | 33 ++++ .../instance/clearThreeInstances.yml | 21 +++ .../modifiers/instance/clearTwoInstances.yml | 29 ++++ .../modifiers/instance/clearTwoInstances2.yml | 27 ++++ .../instance/clearTwoInstancesBackward.yml | 27 ++++ .../instance/clearTwoInstancesBat.yml | 33 ++++ .../instance/clearTwoInstancesBat2.yml | 33 ++++ .../clearTwoInstancesBlueAirPastBat.yml | 43 ++++++ ...clearTwoInstancesEveryTokenBatPastDrum.yml | 63 ++++++++ .../instance/clearTwoInstancesJustBat.yml | 34 +++++ .../instance/clearTwoInstancesLineAir.yml | 39 +++++ .../clearTwoInstancesTwoClicksAir.yml | 38 +++++ .../preEveryCarEveryInstanceAirPastBat.yml | 66 ++++++++ .../preEveryInstanceEveryCarAirPastBat.yml | 70 +++++++++ .../instance/preEveryInstanceFirstCarAir.yml | 42 ++++++ 40 files changed, 1265 insertions(+), 1 deletion(-) create mode 100644 packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/bringDrumAfterTwoInstancesAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/chuckTwoInstancesLineAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceAirPastBat.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceEveryTokenAirPastBat.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstancePastBat.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceThisPastBat.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearFirstInstanceAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextInstanceAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextInstanceAirPastBat.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextTwoInstancesAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearPastNextInstance.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearPreviousTwoInstancesAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearThreeInstances.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstances.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstances2.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBackward.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBat.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBat2.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBlueAirPastBat.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesEveryTokenBatPastDrum.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesJustBat.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesLineAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesTwoClicksAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryCarEveryInstanceAirPastBat.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryInstanceEveryCarAirPastBat.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryInstanceFirstCarAir.yml diff --git a/cursorless-talon/src/modifiers/scopes.py b/cursorless-talon/src/modifiers/scopes.py index 61308cff95..a6a741d2fa 100644 --- a/cursorless-talon/src/modifiers/scopes.py +++ b/cursorless-talon/src/modifiers/scopes.py @@ -29,6 +29,7 @@ "funk name": "functionName", "funk": "namedFunction", "if state": "ifStatement", + "instance": "instance", "item": "collectionItem", "key": "collectionKey", "lambda": "anonymousFunction", diff --git a/packages/common/src/types/command/PartialTargetDescriptor.types.ts b/packages/common/src/types/command/PartialTargetDescriptor.types.ts index 849077a3a5..c95b25cd01 100644 --- a/packages/common/src/types/command/PartialTargetDescriptor.types.ts +++ b/packages/common/src/types/command/PartialTargetDescriptor.types.ts @@ -86,6 +86,7 @@ export type SimpleScopeTypeType = | "functionCallee" | "functionName" | "ifStatement" + | "instance" | "list" | "map" | "name" diff --git a/packages/cursorless-engine/src/core/handleHoistedModifiers.ts b/packages/cursorless-engine/src/core/handleHoistedModifiers.ts index 8fbbb3e35f..635d25132c 100644 --- a/packages/cursorless-engine/src/core/handleHoistedModifiers.ts +++ b/packages/cursorless-engine/src/core/handleHoistedModifiers.ts @@ -146,7 +146,8 @@ const hoistedModifierTypes: HoistedModifierType[] = [ // "every" ranges, eg "every line air past bat" { accept(modifier: Modifier) { - return modifier.type === "everyScope" + return modifier.type === "everyScope" && + modifier.scopeType.type !== "instance" ? { accepted: true, transformTarget(target: RangeTargetDescriptor) { @@ -159,4 +160,19 @@ const hoistedModifierTypes: HoistedModifierType[] = [ : { accepted: false }; }, }, + + // "instance" modifiers treat the range as the instance to search for, eg + // "every instance air past bat" searches for instances of the text of the + // range "air past bat". + { + accept(modifier: Modifier) { + return { + accepted: + (modifier.type === "everyScope" || + modifier.type === "relativeScope" || + modifier.type === "ordinalScope") && + modifier.scopeType.type === "instance", + }; + }, + }, ]; diff --git a/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts index 35709141b9..22528ad53f 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts @@ -10,4 +10,5 @@ export interface ModifierStageFactory { getLegacyScopeStage( modifier: ContainingScopeModifier | EveryScopeModifier, ): ModifierStage; + getPseudoScopeStage(modifier: Modifier): ModifierStage; } diff --git a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts index b84bc6c80e..d459968d45 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts @@ -16,6 +16,7 @@ import { KeepEmptyFilterStage, } from "./modifiers/FilterStages"; import { HeadStage, TailStage } from "./modifiers/HeadTailStage"; +import InstanceStage from "./modifiers/InstanceStage"; import { ExcludeInteriorStage, InteriorOnlyStage, @@ -143,4 +144,23 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { ); } } + + getPseudoScopeStage(modifier: Modifier): ModifierStage { + if ( + modifier.type === "containingScope" || + modifier.type === "everyScope" || + modifier.type === "relativeScope" + ) { + switch (modifier.scopeType.type) { + case "instance": + return new InstanceStage(this, modifier); + default: + throw Error( + "Unsupported pseudo scope type '${modifier.scopeType.type}'", + ); + } + } + + throw Error("Unsupported pseudo scope modifier '${modifier.type}'"); + } } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts index b1f0af5910..ee78e9273f 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts @@ -47,6 +47,12 @@ export class ContainingScopeStage implements ModifierStage { .run(target); } + if (scopeHandler.isPseudoScope) { + return this.modifierStageFactory + .getPseudoScopeStage(this.modifier) + .run(target); + } + const containingScope = getContainingScopeTarget( target, scopeHandler, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts index c0fa052f93..80839c4f77 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts @@ -50,6 +50,12 @@ export class EveryScopeStage implements ModifierStage { .run(target); } + if (scopeHandler.isPseudoScope) { + return this.modifierStageFactory + .getPseudoScopeStage(this.modifier) + .run(target); + } + let scopes: TargetScope[] | undefined; if (target.hasExplicitRange) { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts new file mode 100644 index 0000000000..b46f31e844 --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts @@ -0,0 +1,141 @@ +import { + Direction, + Modifier, + Range, + RelativeScopeModifier, + ScopeType, + TextEditor, +} from "@cursorless/common"; +import { ifilter, imap, itake } from "itertools"; +import { escapeRegExp } from "lodash"; +import type { Target } from "../../typings/target.types"; +import { generateMatchesInRange } from "../../util/getMatchesInRange"; +import { ModifierStageFactory } from "../ModifierStageFactory"; +import type { ModifierStage } from "../PipelineStages.types"; +import { PlainTarget } from "../targets"; +import { OutOfRangeError } from "./targetSequenceUtils"; +import { ContainingTokenIfUntypedEmptyStage } from "./ConditionalModifierStages"; + +export default class InstanceStage implements ModifierStage { + constructor( + private modifierStageFactory: ModifierStageFactory, + private modifier: Modifier, + ) {} + + run(inputTarget: Target): Target[] { + const target = new ContainingTokenIfUntypedEmptyStage( + this.modifierStageFactory, + ).run(inputTarget)[0]; + + switch (this.modifier.type) { + case "relativeScope": + return this.handleRelativeScope(target, this.modifier); + case "everyScope": + return this.handleEveryScope(target); + default: + throw Error(`${this.modifier.type} instance scope not supported`); + } + } + + private handleEveryScope(target: Target): Target[] { + const { editor } = target; + + return Array.from( + this.getTargetIterable(target, editor, editor.document.range, "forward"), + ); + } + + private handleRelativeScope( + target: Target, + { direction, offset, length }: RelativeScopeModifier, + ): Target[] { + const { editor } = target; + + const iterable = this.getTargetIterable( + target, + editor, + direction === "forward" + ? new Range(target.contentRange.start, editor.document.range.end) + : new Range(editor.document.range.start, target.contentRange.end), + direction, + ); + + // Skip the first `offset` targets + Array.from(itake(offset, iterable)); + + // Take the next `length` targets + const targets = Array.from(itake(length, iterable)); + + if (targets.length < length) { + throw new OutOfRangeError(); + } + + return targets; + } + + private getTargetIterable( + target: Target, + editor: TextEditor, + searchRange: Range, + direction: Direction, + ): Iterable { + const iterable = imap( + generateMatchesInRange( + new RegExp(escapeRegExp(target.contentText), "g"), + editor, + searchRange, + direction, + ), + (range) => + new PlainTarget({ + contentRange: range, + editor, + isReversed: false, + isToken: false, + }), + ); + + const filterScopeType = getFilterScopeType(target); + + if (filterScopeType != null) { + const containingScopeModifier = this.modifierStageFactory.create({ + type: "containingScope", + scopeType: filterScopeType, + }); + + return ifilter( + imap(iterable, (target) => { + try { + const containingScope = containingScopeModifier.run(target); + + if ( + containingScope.length === 1 && + containingScope[0].contentRange.isRangeEqual(target.contentRange) + ) { + return containingScope[0]; + } + + return null; + } catch (err) { + return null; + } + }), + (target): target is Target => target != null, + ) as Iterable; + } + + return iterable; + } +} + +function getFilterScopeType(target: Target): ScopeType | null { + if (target.isLine) { + return { type: "line" }; + } + + if (target.isToken) { + return { type: "token" }; + } + + return null; +} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts index fca69fcee8..577d5f5f73 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts @@ -32,6 +32,12 @@ export default class RelativeExclusiveScopeStage implements ModifierStage { return runLegacy(this.modifierStageFactory, this.modifier, target); } + if (scopeHandler.isPseudoScope) { + return this.modifierStageFactory + .getPseudoScopeStage(this.modifier) + .run(target); + } + const { isReversed, editor, contentRange: inputRange } = target; const { length: desiredScopeCount, direction, offset } = this.modifier; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts index 196d328632..4e17681f1d 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts @@ -42,6 +42,12 @@ export class RelativeInclusiveScopeStage implements ModifierStage { return runLegacy(this.modifierStageFactory, this.modifier, target); } + if (scopeHandler.isPseudoScope) { + return this.modifierStageFactory + .getPseudoScopeStage(this.modifier) + .run(target); + } + const { isReversed, editor, contentRange } = target; const { length: desiredScopeCount, direction } = this.modifier; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts index 599fbbc113..cbebccd022 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts @@ -24,6 +24,7 @@ export default abstract class BaseScopeHandler implements ScopeHandler { public abstract readonly iterationScopeType: ScopeType | CustomScopeType; public readonly includeAdjacentInEvery: boolean = false; + public readonly isPseudoScope: boolean = false; /** * Indicates whether scopes are allowed to contain one another. If `false`, we diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts index 998a096fb6..1207aba29b 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts @@ -58,6 +58,9 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { return new ParagraphScopeHandler(scopeType, languageId); case "custom": return scopeType.scopeHandler; + case "instance": + // Handle instance pseudoscope with its own special modifier + return getPseudoScope(scopeType); default: return this.languageDefinitions .get(languageId) @@ -65,3 +68,17 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { } } } + +function getPseudoScope(scopeType: ScopeType): ScopeHandler { + return { + scopeType, + generateScopes(_editor, _position, _direction, _requirements) { + throw new Error("Not implemented"); + }, + get iterationScopeType(): ScopeType { + throw new Error("Not implemented"); + }, + includeAdjacentInEvery: false, + isPseudoScope: true, + }; +} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts index 9224802c99..41b7c7623b 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts @@ -87,6 +87,12 @@ export interface ScopeHandler { * range is at the beginning or end of the line. */ readonly includeAdjacentInEvery: boolean; + + /** + * Used for pseudoscopes like "instance", that don't correspond to a real + * scope type, but are used to represent modifiers like "next instance". + */ + readonly isPseudoScope: boolean; } export type ContainmentPolicy = diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/bringDrumAfterTwoInstancesAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/bringDrumAfterTwoInstancesAir.yml new file mode 100644 index 0000000000..e55884936d --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/bringDrumAfterTwoInstancesAir.yml @@ -0,0 +1,37 @@ +languageId: plaintext +command: + version: 5 + spokenForm: bring drum after two instances air + action: {name: replaceWithTarget} + targets: + - type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: d} + - type: primitive + modifiers: + - {type: position, position: after} + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc ddd aaa + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.d: + start: {line: 0, character: 12} + end: {line: 0, character: 15} + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: | + aaa ddd bbb ccc ddd aaa ddd + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/chuckTwoInstancesLineAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/chuckTwoInstancesLineAir.yml new file mode 100644 index 0000000000..7478fffceb --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/chuckTwoInstancesLineAir.yml @@ -0,0 +1,37 @@ +languageId: plaintext +command: + version: 5 + spokenForm: chuck two instances line air + action: {name: remove} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + - type: containingScope + scopeType: {type: line} + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb + aaa bbb ccc + aaa bbb + ddd + selections: + - anchor: {line: 4, character: 0} + active: {line: 4, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: | + aaa bbb ccc + ddd + selections: + - anchor: {line: 2, character: 0} + active: {line: 2, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceAir.yml new file mode 100644 index 0000000000..455143f686 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceAir.yml @@ -0,0 +1,36 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear every instance air + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: instance} + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa ddd aaa + eee aaa fff + selections: + - anchor: {line: 2, character: 0} + active: {line: 2, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: |2 + bbb ccc ddd + eee fff + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + - anchor: {line: 1, character: 4} + active: {line: 1, character: 4} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceAirPastBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceAirPastBat.yml new file mode 100644 index 0000000000..a081e85eb3 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceAirPastBat.yml @@ -0,0 +1,40 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear every instance air past bat + action: {name: clearAndSetSelection} + targets: + - type: range + anchor: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: instance} + mark: {type: decoratedSymbol, symbolColor: default, character: a} + active: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: b} + excludeAnchor: false + excludeActive: false + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbb + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: |2 + ccc + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceEveryTokenAirPastBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceEveryTokenAirPastBat.yml new file mode 100644 index 0000000000..c91f28f989 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceEveryTokenAirPastBat.yml @@ -0,0 +1,46 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear every instance every token air past bat + action: {name: clearAndSetSelection} + targets: + - type: range + anchor: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: instance} + - type: everyScope + scopeType: {type: token} + mark: {type: decoratedSymbol, symbolColor: default, character: a} + active: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: b} + excludeAnchor: false + excludeActive: false + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbb + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: |2 + ccc + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstancePastBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstancePastBat.yml new file mode 100644 index 0000000000..7b487b5d14 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstancePastBat.yml @@ -0,0 +1,34 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear every instance past bat + action: {name: clearAndSetSelection} + targets: + - type: range + anchor: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: instance} + active: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: b} + excludeAnchor: false + excludeActive: false + usePrePhraseSnapshot: true +initialState: + documentContents: aaa bbb ccc aaa bbb + selections: + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + marks: + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: aa ccc aa + selections: + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceThisPastBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceThisPastBat.yml new file mode 100644 index 0000000000..5ecc92fb8d --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearEveryInstanceThisPastBat.yml @@ -0,0 +1,35 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear every instance this past bat + action: {name: clearAndSetSelection} + targets: + - type: range + anchor: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: instance} + mark: {type: cursor} + active: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: b} + excludeAnchor: false + excludeActive: false + usePrePhraseSnapshot: true +initialState: + documentContents: aaa bbb ccc aaa bbb + selections: + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + marks: + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: " ccc " + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearFirstInstanceAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearFirstInstanceAir.yml new file mode 100644 index 0000000000..dd81543d27 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearFirstInstanceAir.yml @@ -0,0 +1,30 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear first instance air + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: ordinalScope + scopeType: {type: instance} + start: 0 + length: 1 + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa ddd aaa + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 20} + end: {line: 0, character: 23} +finalState: + documentContents: |2 + bbb ccc aaa ddd aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextInstanceAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextInstanceAir.yml new file mode 100644 index 0000000000..3b5faa3630 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextInstanceAir.yml @@ -0,0 +1,31 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear next instance air + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 1 + length: 1 + direction: forward + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa ddd aaa + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: | + aaa bbb ccc ddd aaa + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextInstanceAirPastBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextInstanceAirPastBat.yml new file mode 100644 index 0000000000..0f31a11367 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextInstanceAirPastBat.yml @@ -0,0 +1,41 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear next instance air past bat + action: {name: clearAndSetSelection} + targets: + - type: range + anchor: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 1 + length: 1 + direction: forward + mark: {type: decoratedSymbol, symbolColor: default, character: a} + active: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: b} + excludeAnchor: false + excludeActive: false + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbb + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: | + aaa bbb ccc + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextTwoInstancesAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextTwoInstancesAir.yml new file mode 100644 index 0000000000..bdc373fcd6 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearNextTwoInstancesAir.yml @@ -0,0 +1,33 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear next two instances air + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 1 + length: 2 + direction: forward + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa ddd aaa + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: | + aaa bbb ccc ddd + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + - anchor: {line: 0, character: 17} + active: {line: 0, character: 17} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearPastNextInstance.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearPastNextInstance.yml new file mode 100644 index 0000000000..cb10386d79 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearPastNextInstance.yml @@ -0,0 +1,38 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear past next instance + action: {name: clearAndSetSelection} + targets: + - type: range + anchor: {type: implicit} + active: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 1 + length: 1 + direction: forward + excludeAnchor: false + excludeActive: false + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb aaa fff + ddd eee ddd ggg + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: {} +finalState: + documentContents: |2 + fff + ggg + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearPreviousTwoInstancesAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearPreviousTwoInstancesAir.yml new file mode 100644 index 0000000000..d43dcaf7c8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearPreviousTwoInstancesAir.yml @@ -0,0 +1,33 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear previous two instances air + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 1 + length: 2 + direction: backward + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa ddd aaa + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 20} + end: {line: 0, character: 23} +finalState: + documentContents: |2 + bbb ccc ddd aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearThreeInstances.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearThreeInstances.yml new file mode 100644 index 0000000000..152ad5e8e1 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearThreeInstances.yml @@ -0,0 +1,21 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear three instances + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 3 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: aaa bbb aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +thrownError: {name: OutOfRangeError} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstances.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstances.yml new file mode 100644 index 0000000000..a431db15a4 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstances.yml @@ -0,0 +1,29 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa ddd aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: |2 + bbb ccc ddd aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstances2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstances2.yml new file mode 100644 index 0000000000..48b2b4db73 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstances2.yml @@ -0,0 +1,27 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: aaa bbb aaaa bbb aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 3} + marks: {} +finalState: + documentContents: " bbb a bbb aaa" + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBackward.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBackward.yml new file mode 100644 index 0000000000..d6eda0d5cd --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBackward.yml @@ -0,0 +1,27 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances backward + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: backward + usePrePhraseSnapshot: true +initialState: + documentContents: bbb aaa bbb aaa bbb + selections: + - anchor: {line: 0, character: 19} + active: {line: 0, character: 19} + marks: {} +finalState: + documentContents: "bbb aaa aaa " + selections: + - anchor: {line: 0, character: 8} + active: {line: 0, character: 8} + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBat.yml new file mode 100644 index 0000000000..dcefef0309 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBat.yml @@ -0,0 +1,33 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances bat + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + mark: {type: decoratedSymbol, symbolColor: default, character: b} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbb aaa bbb + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: | + aaa ccc aaa aaa bbb + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBat2.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBat2.yml new file mode 100644 index 0000000000..3a9d9c1b51 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBat2.yml @@ -0,0 +1,33 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances bat + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + mark: {type: decoratedSymbol, symbolColor: default, character: b} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbbb aaa bbb + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: | + aaa ccc aaa bbbb aaa + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + - anchor: {line: 0, character: 22} + active: {line: 0, character: 22} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBlueAirPastBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBlueAirPastBat.yml new file mode 100644 index 0000000000..e6281906c7 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesBlueAirPastBat.yml @@ -0,0 +1,43 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances blue air past bat + action: {name: clearAndSetSelection} + targets: + - type: range + anchor: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + mark: {type: decoratedSymbol, symbolColor: blue, character: a} + active: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: b} + excludeAnchor: false + excludeActive: false + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbbb aaa bbb + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + blue.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: |2 + ccc aaa bbbb + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesEveryTokenBatPastDrum.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesEveryTokenBatPastDrum.yml new file mode 100644 index 0000000000..a9454f1122 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesEveryTokenBatPastDrum.yml @@ -0,0 +1,63 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances every token bat past drum + action: {name: clearAndSetSelection} + targets: + - type: range + anchor: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + - type: everyScope + scopeType: {type: token} + mark: {type: decoratedSymbol, symbolColor: default, character: b} + active: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: d} + excludeAnchor: false + excludeActive: false + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc ddd eee + aaa + bbb + ccc + ddd + eee + selections: + - anchor: {line: 6, character: 0} + active: {line: 6, character: 0} + marks: + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} + default.d: + start: {line: 0, character: 12} + end: {line: 0, character: 15} +finalState: + documentContents: | + aaa eee + aaa + + + + eee + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + - anchor: {line: 2, character: 0} + active: {line: 2, character: 0} + - anchor: {line: 3, character: 0} + active: {line: 3, character: 0} + - anchor: {line: 4, character: 0} + active: {line: 4, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesJustBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesJustBat.yml new file mode 100644 index 0000000000..39d4c1f0a2 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesJustBat.yml @@ -0,0 +1,34 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances just bat + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + - {type: toRawSelection} + mark: {type: decoratedSymbol, symbolColor: default, character: b} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbbb aaa bbb + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: | + aaa ccc aaa b aaa bbb + selections: + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesLineAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesLineAir.yml new file mode 100644 index 0000000000..01fc8ee06c --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesLineAir.yml @@ -0,0 +1,39 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances line air + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + - type: containingScope + scopeType: {type: line} + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb + aaa bbb ccc + aaa bbb + selections: + - anchor: {line: 3, character: 0} + active: {line: 3, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: |+ + + aaa bbb ccc + + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 2, character: 0} + active: {line: 2, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesTwoClicksAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesTwoClicksAir.yml new file mode 100644 index 0000000000..5224309236 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesTwoClicksAir.yml @@ -0,0 +1,38 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances two clicks air + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + - type: relativeScope + scopeType: {type: token} + offset: 0 + length: 2 + direction: forward + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbbb aaa bbb + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: |2 + ccc aaa bbbb + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryCarEveryInstanceAirPastBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryCarEveryInstanceAirPastBat.yml new file mode 100644 index 0000000000..baae4837c8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryCarEveryInstanceAirPastBat.yml @@ -0,0 +1,66 @@ +languageId: plaintext +command: + version: 5 + spokenForm: pre every car every instance air past bat + action: {name: setSelectionBefore} + targets: + - type: range + anchor: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: character} + - type: everyScope + scopeType: {type: instance} + mark: {type: decoratedSymbol, symbolColor: default, character: a} + active: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: b} + excludeAnchor: false + excludeActive: false + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbb + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: | + aaa bbb ccc aaa bbb + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + - anchor: {line: 0, character: 15} + active: {line: 0, character: 15} + - anchor: {line: 0, character: 16} + active: {line: 0, character: 16} + - anchor: {line: 0, character: 17} + active: {line: 0, character: 17} + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryInstanceEveryCarAirPastBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryInstanceEveryCarAirPastBat.yml new file mode 100644 index 0000000000..1341b609cb --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryInstanceEveryCarAirPastBat.yml @@ -0,0 +1,70 @@ +languageId: plaintext +command: + version: 5 + spokenForm: pre every instance every car air past bat + action: {name: setSelectionBefore} + targets: + - type: range + anchor: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: instance} + - type: everyScope + scopeType: {type: character} + mark: {type: decoratedSymbol, symbolColor: default, character: a} + active: + type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: b} + excludeAnchor: false + excludeActive: false + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbb + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: | + aaa bbb ccc aaa bbb + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} + - anchor: {line: 0, character: 11} + active: {line: 0, character: 11} + - anchor: {line: 0, character: 15} + active: {line: 0, character: 15} + - anchor: {line: 0, character: 4} + active: {line: 0, character: 4} + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + - anchor: {line: 0, character: 16} + active: {line: 0, character: 16} + - anchor: {line: 0, character: 17} + active: {line: 0, character: 17} + - anchor: {line: 0, character: 18} + active: {line: 0, character: 18} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryInstanceFirstCarAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryInstanceFirstCarAir.yml new file mode 100644 index 0000000000..ce84324bb7 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/preEveryInstanceFirstCarAir.yml @@ -0,0 +1,42 @@ +languageId: plaintext +command: + version: 5 + spokenForm: pre every instance first car air + action: {name: setSelectionBefore} + targets: + - type: primitive + modifiers: + - type: everyScope + scopeType: {type: instance} + - type: ordinalScope + scopeType: {type: character} + start: 0 + length: 1 + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbb + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: | + aaa bbb ccc aaa bbb + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + - anchor: {line: 0, character: 2} + active: {line: 0, character: 2} + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + - anchor: {line: 0, character: 13} + active: {line: 0, character: 13} + - anchor: {line: 0, character: 14} + active: {line: 0, character: 14} From 1ed880fe41553f85949200efb68dc4b9aa52f3ba Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Wed, 24 May 2023 22:53:57 +0100 Subject: [PATCH 2/5] Simplify "pseudo-scope" handling --- .../processTargets/ModifierStageFactory.ts | 1 - .../ModifierStageFactoryImpl.ts | 27 ++++++------------- .../modifiers/ContainingScopeStage.ts | 6 ----- .../modifiers/EveryScopeStage.ts | 6 ----- .../modifiers/RelativeExclusiveScopeStage.ts | 6 ----- .../modifiers/RelativeInclusiveScopeStage.ts | 6 ----- .../scopeHandlers/BaseScopeHandler.ts | 1 - .../scopeHandlers/ScopeHandlerFactoryImpl.ts | 16 +---------- .../scopeHandlers/scopeHandler.types.ts | 6 ----- 9 files changed, 9 insertions(+), 66 deletions(-) diff --git a/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts index 22528ad53f..35709141b9 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactory.ts @@ -10,5 +10,4 @@ export interface ModifierStageFactory { getLegacyScopeStage( modifier: ContainingScopeModifier | EveryScopeModifier, ): ModifierStage; - getPseudoScopeStage(modifier: Modifier): ModifierStage; } diff --git a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts index d459968d45..878be31955 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts @@ -76,10 +76,18 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { modifier, ); case "everyScope": + if (modifier.scopeType.type === "instance") { + return new InstanceStage(this, modifier); + } + return new EveryScopeStage(this, this.scopeHandlerFactory, modifier); case "ordinalScope": return new OrdinalScopeStage(this, modifier); case "relativeScope": + if (modifier.scopeType.type === "instance") { + return new InstanceStage(this, modifier); + } + return new RelativeScopeStage(this, this.scopeHandlerFactory, modifier); case "keepContentFilter": return new KeepContentFilterStage(modifier); @@ -144,23 +152,4 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { ); } } - - getPseudoScopeStage(modifier: Modifier): ModifierStage { - if ( - modifier.type === "containingScope" || - modifier.type === "everyScope" || - modifier.type === "relativeScope" - ) { - switch (modifier.scopeType.type) { - case "instance": - return new InstanceStage(this, modifier); - default: - throw Error( - "Unsupported pseudo scope type '${modifier.scopeType.type}'", - ); - } - } - - throw Error("Unsupported pseudo scope modifier '${modifier.type}'"); - } } diff --git a/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts index ee78e9273f..b1f0af5910 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/ContainingScopeStage.ts @@ -47,12 +47,6 @@ export class ContainingScopeStage implements ModifierStage { .run(target); } - if (scopeHandler.isPseudoScope) { - return this.modifierStageFactory - .getPseudoScopeStage(this.modifier) - .run(target); - } - const containingScope = getContainingScopeTarget( target, scopeHandler, diff --git a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts index 80839c4f77..c0fa052f93 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/EveryScopeStage.ts @@ -50,12 +50,6 @@ export class EveryScopeStage implements ModifierStage { .run(target); } - if (scopeHandler.isPseudoScope) { - return this.modifierStageFactory - .getPseudoScopeStage(this.modifier) - .run(target); - } - let scopes: TargetScope[] | undefined; if (target.hasExplicitRange) { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts index 577d5f5f73..fca69fcee8 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/RelativeExclusiveScopeStage.ts @@ -32,12 +32,6 @@ export default class RelativeExclusiveScopeStage implements ModifierStage { return runLegacy(this.modifierStageFactory, this.modifier, target); } - if (scopeHandler.isPseudoScope) { - return this.modifierStageFactory - .getPseudoScopeStage(this.modifier) - .run(target); - } - const { isReversed, editor, contentRange: inputRange } = target; const { length: desiredScopeCount, direction, offset } = this.modifier; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts index 4e17681f1d..196d328632 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/RelativeInclusiveScopeStage.ts @@ -42,12 +42,6 @@ export class RelativeInclusiveScopeStage implements ModifierStage { return runLegacy(this.modifierStageFactory, this.modifier, target); } - if (scopeHandler.isPseudoScope) { - return this.modifierStageFactory - .getPseudoScopeStage(this.modifier) - .run(target); - } - const { isReversed, editor, contentRange } = target; const { length: desiredScopeCount, direction } = this.modifier; diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts index cbebccd022..599fbbc113 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.ts @@ -24,7 +24,6 @@ export default abstract class BaseScopeHandler implements ScopeHandler { public abstract readonly iterationScopeType: ScopeType | CustomScopeType; public readonly includeAdjacentInEvery: boolean = false; - public readonly isPseudoScope: boolean = false; /** * Indicates whether scopes are allowed to contain one another. If `false`, we diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts index 1207aba29b..ad0413ceaf 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/ScopeHandlerFactoryImpl.ts @@ -60,7 +60,7 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { return scopeType.scopeHandler; case "instance": // Handle instance pseudoscope with its own special modifier - return getPseudoScope(scopeType); + throw Error("Unexpected scope type 'instance'"); default: return this.languageDefinitions .get(languageId) @@ -68,17 +68,3 @@ export class ScopeHandlerFactoryImpl implements ScopeHandlerFactory { } } } - -function getPseudoScope(scopeType: ScopeType): ScopeHandler { - return { - scopeType, - generateScopes(_editor, _position, _direction, _requirements) { - throw new Error("Not implemented"); - }, - get iterationScopeType(): ScopeType { - throw new Error("Not implemented"); - }, - includeAdjacentInEvery: false, - isPseudoScope: true, - }; -} diff --git a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts index 41b7c7623b..9224802c99 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/scopeHandler.types.ts @@ -87,12 +87,6 @@ export interface ScopeHandler { * range is at the beginning or end of the line. */ readonly includeAdjacentInEvery: boolean; - - /** - * Used for pseudoscopes like "instance", that don't correspond to a real - * scope type, but are used to represent modifiers like "next instance". - */ - readonly isPseudoScope: boolean; } export type ContainmentPolicy = From ead733e5c458d83fe9f87d54ff5e414b67643722 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Thu, 25 May 2023 08:04:01 +0100 Subject: [PATCH 3/5] More test cases --- .../instance/clearTwoInstancesAirAndBat.yml | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesAirAndBat.yml diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesAirAndBat.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesAirAndBat.yml new file mode 100644 index 0000000000..a50466e8bc --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesAirAndBat.yml @@ -0,0 +1,44 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances air and bat + action: {name: clearAndSetSelection} + targets: + - type: list + elements: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + mark: {type: decoratedSymbol, symbolColor: default, character: a} + - type: primitive + mark: {type: decoratedSymbol, symbolColor: default, character: b} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa bbb + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} + default.b: + start: {line: 0, character: 4} + end: {line: 0, character: 7} +finalState: + documentContents: |2 + ccc + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 1} + active: {line: 0, character: 1} + - anchor: {line: 0, character: 6} + active: {line: 0, character: 6} + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} From 30ca50dc75e49c4df3a03913338e7b3d87eca926 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Thu, 25 May 2023 09:03:48 +0100 Subject: [PATCH 4/5] Fix ordinal stages --- .../ModifierStageFactoryImpl.ts | 4 + .../processTargets/modifiers/InstanceStage.ts | 91 +++++++++++++++---- .../instants/clearSecondLastInstanceAir.yml | 30 ++++++ .../instance/clearFirstTwoInstancesAir.yml | 32 +++++++ .../instance/clearLastTwoInstancesAir.yml | 32 +++++++ 5 files changed, 169 insertions(+), 20 deletions(-) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/instants/clearSecondLastInstanceAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearFirstTwoInstancesAir.yml create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearLastTwoInstancesAir.yml diff --git a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts index 878be31955..5dfeca7ad2 100644 --- a/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/ModifierStageFactoryImpl.ts @@ -82,6 +82,10 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory { return new EveryScopeStage(this, this.scopeHandlerFactory, modifier); case "ordinalScope": + if (modifier.scopeType.type === "instance") { + return new InstanceStage(this, modifier); + } + return new OrdinalScopeStage(this, modifier); case "relativeScope": if (modifier.scopeType.type === "instance") { diff --git a/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts index b46f31e844..0c4c4dab97 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts @@ -1,6 +1,7 @@ import { Direction, Modifier, + OrdinalScopeModifier, Range, RelativeScopeModifier, ScopeType, @@ -13,8 +14,8 @@ import { generateMatchesInRange } from "../../util/getMatchesInRange"; import { ModifierStageFactory } from "../ModifierStageFactory"; import type { ModifierStage } from "../PipelineStages.types"; import { PlainTarget } from "../targets"; -import { OutOfRangeError } from "./targetSequenceUtils"; import { ContainingTokenIfUntypedEmptyStage } from "./ConditionalModifierStages"; +import { OutOfRangeError } from "./targetSequenceUtils"; export default class InstanceStage implements ModifierStage { constructor( @@ -23,15 +24,20 @@ export default class InstanceStage implements ModifierStage { ) {} run(inputTarget: Target): 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]; switch (this.modifier.type) { - case "relativeScope": - return this.handleRelativeScope(target, this.modifier); case "everyScope": return this.handleEveryScope(target); + case "ordinalScope": + return this.handleOrdinalScope(target, this.modifier); + case "relativeScope": + return this.handleRelativeScope(target, this.modifier); default: throw Error(`${this.modifier.type} instance scope not supported`); } @@ -41,7 +47,30 @@ export default class InstanceStage implements ModifierStage { const { editor } = target; return Array.from( - this.getTargetIterable(target, editor, editor.document.range, "forward"), + this.getTargetIterable( + target, + editor, + this.getEveryRange(editor), + "forward", + ), + ); + } + + private handleOrdinalScope( + target: Target, + { start, length }: OrdinalScopeModifier, + ): Target[] { + const { editor } = target; + + return takeFromOffset( + this.getTargetIterable( + target, + editor, + this.getEveryRange(editor), + start >= 0 ? "forward" : "backward", + ), + start >= 0 ? start : -(length + start), + length, ); } @@ -51,26 +80,20 @@ export default class InstanceStage implements ModifierStage { ): Target[] { const { editor } = target; - const iterable = this.getTargetIterable( - target, - editor, + const iterationRange = direction === "forward" ? new Range(target.contentRange.start, editor.document.range.end) - : new Range(editor.document.range.start, target.contentRange.end), - direction, - ); + : new Range(editor.document.range.start, target.contentRange.end); - // Skip the first `offset` targets - Array.from(itake(offset, iterable)); - - // Take the next `length` targets - const targets = Array.from(itake(length, iterable)); - - if (targets.length < length) { - throw new OutOfRangeError(); - } + return takeFromOffset( + this.getTargetIterable(target, editor, iterationRange, direction), + offset, + length, + ); + } - return targets; + private getEveryRange(editor: TextEditor): Range { + return editor.document.range; } private getTargetIterable( @@ -139,3 +162,31 @@ function getFilterScopeType(target: Target): ScopeType | null { return null; } + +/** + * Take `length` items from `iterable` starting at `offset`, throwing an error + * if there are not enough items. + * + * @param iterable The iterable to take from + * @param offset How many items to skip + * @param count How many items to take + * @returns An array of length `length` containing the items from `iterable` + * starting at `offset` + */ +function takeFromOffset( + iterable: Iterable, + offset: number, + count: number, +): T[] { + // Skip the first `offset` targets + Array.from(itake(offset, iterable)); + + // Take the next `length` targets + const items = Array.from(itake(count, iterable)); + + if (items.length < count) { + throw new OutOfRangeError(); + } + + return items; +} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/instants/clearSecondLastInstanceAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/instants/clearSecondLastInstanceAir.yml new file mode 100644 index 0000000000..6944233218 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/instants/clearSecondLastInstanceAir.yml @@ -0,0 +1,30 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear second last instance air + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: ordinalScope + scopeType: {type: instance} + start: -2 + length: 1 + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa ddd aaa eee + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 20} + end: {line: 0, character: 23} +finalState: + documentContents: | + aaa bbb ccc ddd aaa eee + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearFirstTwoInstancesAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearFirstTwoInstancesAir.yml new file mode 100644 index 0000000000..b1ef8317ee --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearFirstTwoInstancesAir.yml @@ -0,0 +1,32 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear first two instances air + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: ordinalScope + scopeType: {type: instance} + start: 0 + length: 2 + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa ddd aaa eee + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: |2 + bbb ccc ddd aaa eee + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + - anchor: {line: 0, character: 9} + active: {line: 0, character: 9} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearLastTwoInstancesAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearLastTwoInstancesAir.yml new file mode 100644 index 0000000000..10756b96b3 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearLastTwoInstancesAir.yml @@ -0,0 +1,32 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear last two instances air + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: ordinalScope + scopeType: {type: instance} + start: -2 + length: 2 + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc aaa ddd aaa eee + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 3} +finalState: + documentContents: | + aaa bbb ccc ddd eee + selections: + - anchor: {line: 0, character: 12} + active: {line: 0, character: 12} + - anchor: {line: 0, character: 17} + active: {line: 0, character: 17} From e0461288acbdc2557c5b912872b582c494015cf3 Mon Sep 17 00:00:00 2001 From: Pokey Rule <755842+pokey@users.noreply.github.com> Date: Thu, 25 May 2023 16:19:51 +0100 Subject: [PATCH 5/5] Handle words --- docs/user/README.md | 22 +++++++++++- .../processTargets/modifiers/InstanceStage.ts | 13 +++++++ .../src/processTargets/targets/BaseTarget.ts | 1 + .../targets/SubTokenWordTarget.ts | 1 + .../src/typings/target.types.ts | 3 ++ .../instance/clearTwoInstancesLastWordAir.yml | 35 +++++++++++++++++++ 6 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesLastWordAir.yml diff --git a/docs/user/README.md b/docs/user/README.md index e235dc7bb7..6adfdd0247 100644 --- a/docs/user/README.md +++ b/docs/user/README.md @@ -311,6 +311,26 @@ foo.bar baz|bongo Saying `"every paint"` would select `foo.bar` and `baz|bongo`. +##### `"instance"` + +The `"instance"` modifier searches for occurrences of the text of the target. For example: + +- `"take every instance air"`: selects all instances of the token with a hat over the letter `a` in the whole document +- `"take two instances air"`: selects the first two instances of the token with a hat over the letter `a`, starting from that token itself +- `"take next instance air"`: selects the next instance of the token with a hat over the letter `a`, starting from that token itself +- `"chuck every instance two tokens air"`: deletes all occurrences of the two tokens beginning from the token with a hat over the letter `a`. For example if there were a hat over the `a` in `aaa.bbb`, it would delete every occurrence of `aaa.` in the file. +- `"take every instance air past bat"`: if there were hats over the `a` and `b` in `aaa ccc bbb ddd`, it would selects all occurrences of `aaa ccc bbb` (which is the text corresponding to the range `"air past bat"`) + +Note in the final example how the `"instance"` modifier constructs the instance based on the range `"air past bat"`, rather than the individual tokens `"air"` and `"bat"`, as you might expect given the way other modifiers behave. Effectively `"instance"` applies to everything after the `"instance"` modifier, rather than just the next modifier. + +Note also that `"instance"` considers the type of target used to construct the instance. So for example, `"take every instance air"` will only select tokens that +are identical to the token with a hat over the letter `a`, skipping over bigger tokens that contain the token with a hat over the letter `a` as a substring. For example, if there were a hat over the `a` in `aaa`, it would select every occurrence of `aaa` in the file, but not `aaaaa`. If you want to avoid this behaviour, you can +use the `"just"` modifier, eg `"take every instance just air"`. + +If your cursor is touching a token, you can say `"take every instance air"` to select all instances of the given token. + +Pro tip: if you say eg `"take five instances air"`, and it turns out you need more, you can say eg `"take that and next two instances that"` to select the next two instances after the last instance you selected. + ##### Surrounding pair Cursorless has support for expanding the selection to the nearest containing paired delimiter, eg the surrounding parentheses, curly brackets, etc. @@ -351,7 +371,7 @@ For example: If your cursor / mark is between two delimiters (not adjacent to one), then saying either "left" or "right" will cause cursorless to just expand to the nearest delimiters on either side, without trying to determine whether they are opening or closing delimiters. -#### `"its"` +##### `"its"` The the modifier `"its"` is intended to be used as part of a compound target, and will tell Cursorless to use the previously mentioned mark in the compound target. diff --git a/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts index 0c4c4dab97..2950efd44f 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts @@ -121,6 +121,12 @@ export default class InstanceStage implements ModifierStage { const filterScopeType = getFilterScopeType(target); if (filterScopeType != null) { + // If the target is a line, token or word, we want to filter out any + // instances of the text that are not also a line, token or word. For + // those that are, we want to return the target as a line, token or word + // target. For example, if the user says "take every instance air", we + // won't return matches where we find the text of the "air" token as part + // of a larger token. const containingScopeModifier = this.modifierStageFactory.create({ type: "containingScope", scopeType: filterScopeType, @@ -129,6 +135,9 @@ export default class InstanceStage implements ModifierStage { return ifilter( imap(iterable, (target) => { try { + // 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); if ( @@ -160,6 +169,10 @@ function getFilterScopeType(target: Target): ScopeType | null { return { type: "token" }; } + if (target.isWord) { + return { type: "word" }; + } + return null; } diff --git a/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts b/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts index aea3a789af..3f22cfca11 100644 --- a/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/BaseTarget.ts @@ -37,6 +37,7 @@ export default abstract class BaseTarget implements Target { isRaw = false; isImplicit = false; isNotebookCell = false; + isWord = false; constructor(parameters: CommonTargetParameters) { this.state = { diff --git a/packages/cursorless-engine/src/processTargets/targets/SubTokenWordTarget.ts b/packages/cursorless-engine/src/processTargets/targets/SubTokenWordTarget.ts index e190f057ca..e9b040dba2 100644 --- a/packages/cursorless-engine/src/processTargets/targets/SubTokenWordTarget.ts +++ b/packages/cursorless-engine/src/processTargets/targets/SubTokenWordTarget.ts @@ -14,6 +14,7 @@ export default class SubTokenWordTarget extends BaseTarget { private trailingDelimiterRange_?: Range; insertionDelimiter: string; isToken = false; + isWord = true; constructor(parameters: SubTokenTargetParameters) { super(parameters); diff --git a/packages/cursorless-engine/src/typings/target.types.ts b/packages/cursorless-engine/src/typings/target.types.ts index 7e97b74da6..5ec834289d 100644 --- a/packages/cursorless-engine/src/typings/target.types.ts +++ b/packages/cursorless-engine/src/typings/target.types.ts @@ -47,6 +47,9 @@ export interface Target { /** If true this target should be treated as a token */ readonly isToken: boolean; + /** If true this target should be treated as a word */ + readonly isWord: boolean; + /** * If `true`, then this target has an explicit scope type, and so should never * be automatically expanded to a containing scope. diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesLastWordAir.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesLastWordAir.yml new file mode 100644 index 0000000000..414ab6c509 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/clearTwoInstancesLastWordAir.yml @@ -0,0 +1,35 @@ +languageId: plaintext +command: + version: 5 + spokenForm: clear two instances last word air + action: {name: clearAndSetSelection} + targets: + - type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 0 + length: 2 + direction: forward + - type: ordinalScope + scopeType: {type: word} + start: -1 + length: 1 + mark: {type: decoratedSymbol, symbolColor: default, character: a} + usePrePhraseSnapshot: true +initialState: + documentContents: aaaBbb cccBbbb dddBbb + selections: + - anchor: {line: 0, character: 21} + active: {line: 0, character: 21} + marks: + default.a: + start: {line: 0, character: 0} + end: {line: 0, character: 6} +finalState: + documentContents: aaa cccBbbb ddd + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + - anchor: {line: 0, character: 15} + active: {line: 0, character: 15}