diff --git a/cursorless-talon/src/spoken_forms.py b/cursorless-talon/src/spoken_forms.py index 30ad16498df..6d8f4a7519b 100644 --- a/cursorless-talon/src/spoken_forms.py +++ b/cursorless-talon/src/spoken_forms.py @@ -115,7 +115,11 @@ def handle_new_values(csv_name: str, values: list[SpokenFormEntry]): ) disposables = [ - handle_csv("actions.csv"), + handle_csv( + "actions.csv", + extra_allowed_values=["private.setImplicitTarget"], + default_list_name="simple_action", + ), handle_csv("target_connectives.csv"), handle_csv("modifiers.csv"), handle_csv("positions.csv"), diff --git a/packages/common/src/StoredTargetKey.ts b/packages/common/src/StoredTargetKey.ts index 8836191149d..a2566f2b94d 100644 --- a/packages/common/src/StoredTargetKey.ts +++ b/packages/common/src/StoredTargetKey.ts @@ -2,5 +2,6 @@ export const storedTargetKeys = [ "that", "source", "instanceReference", + "implicit", ] as const; export type StoredTargetKey = (typeof storedTargetKeys)[number]; diff --git a/packages/common/src/testUtil/TestCaseSnapshot.ts b/packages/common/src/testUtil/TestCaseSnapshot.ts index 56bf57227bb..cb8690914c8 100644 --- a/packages/common/src/testUtil/TestCaseSnapshot.ts +++ b/packages/common/src/testUtil/TestCaseSnapshot.ts @@ -1,3 +1,4 @@ +import { StoredTargetKey } from "../StoredTargetKey"; import { RangePlainObject, SelectionPlainObject, @@ -5,7 +6,11 @@ import { TargetPlainObject, } from "../util/toPlainObject"; -export type TestCaseSnapshot = { +type MarkKeys = { + [K in `${StoredTargetKey}Mark`]?: TargetPlainObject[]; +}; + +export interface TestCaseSnapshot extends MarkKeys { documentContents: string; selections: SelectionPlainObject[]; clipboard?: string; @@ -13,17 +18,13 @@ export type TestCaseSnapshot = { // https://github.com/cursorless-dev/cursorless/issues/160 visibleRanges?: RangePlainObject[]; marks?: SerializedMarks; - thatMark?: TargetPlainObject[]; - sourceMark?: TargetPlainObject[]; - instanceReferenceMark?: TargetPlainObject[]; timeOffsetSeconds?: number; /** * Extra information about the snapshot. Must be json serializable */ metadata?: unknown; -}; - +} export type ExtraSnapshotField = keyof TestCaseSnapshot; export type ExcludableSnapshotField = keyof TestCaseSnapshot; diff --git a/packages/common/src/types/command/ActionDescriptor.ts b/packages/common/src/types/command/ActionDescriptor.ts index a2384f75f1e..994251c8036 100644 --- a/packages/common/src/types/command/ActionDescriptor.ts +++ b/packages/common/src/types/command/ActionDescriptor.ts @@ -49,6 +49,7 @@ const simpleActionNames = [ "toggleLineBreakpoint", "toggleLineComment", "unfoldRegion", + "private.setImplicitTarget", "private.showParseTree", "private.getTargets", ] as const; diff --git a/packages/cursorless-engine/src/CommandHistory.ts b/packages/cursorless-engine/src/CommandHistory.ts index dd7c969121a..a265a9b8ad1 100644 --- a/packages/cursorless-engine/src/CommandHistory.ts +++ b/packages/cursorless-engine/src/CommandHistory.ts @@ -194,6 +194,7 @@ function sanitizeActionInPlace(action: ActionDescriptor): void { case "swapTargets": case "wrapWithPairedDelimiter": case "findInDocument": + case "private.setImplicitTarget": break; default: { diff --git a/packages/cursorless-engine/src/actions/Actions.ts b/packages/cursorless-engine/src/actions/Actions.ts index c06c40ce90a..3d272ecdb04 100644 --- a/packages/cursorless-engine/src/actions/Actions.ts +++ b/packages/cursorless-engine/src/actions/Actions.ts @@ -33,7 +33,7 @@ import Remove from "./Remove"; import Replace from "./Replace"; import Rewrap from "./Rewrap"; import { ScrollToBottom, ScrollToCenter, ScrollToTop } from "./Scroll"; -import { SetInstanceReference } from "./SetInstanceReference"; +import { SetSpecialTarget } from "./SetSpecialTarget"; import { SetSelection, SetSelectionAfter, @@ -134,7 +134,10 @@ export class Actions implements ActionRecord { scrollToBottom = new ScrollToBottom(); scrollToCenter = new ScrollToCenter(); scrollToTop = new ScrollToTop(); - ["experimental.setInstanceReference"] = new SetInstanceReference(); + ["private.setImplicitTarget"] = new SetSpecialTarget("implicit"); + ["experimental.setInstanceReference"] = new SetSpecialTarget( + "instanceReference", + ); setSelection = new SetSelection(); setSelectionAfter = new SetSelectionAfter(); setSelectionBefore = new SetSelectionBefore(); diff --git a/packages/cursorless-engine/src/actions/SetInstanceReference.ts b/packages/cursorless-engine/src/actions/SetSpecialTarget.ts similarity index 54% rename from packages/cursorless-engine/src/actions/SetInstanceReference.ts rename to packages/cursorless-engine/src/actions/SetSpecialTarget.ts index da2617b2ae1..a7c64cc71c6 100644 --- a/packages/cursorless-engine/src/actions/SetInstanceReference.ts +++ b/packages/cursorless-engine/src/actions/SetSpecialTarget.ts @@ -1,15 +1,18 @@ +import { StoredTargetKey } from "@cursorless/common"; import { Target } from "../typings/target.types"; import { SimpleAction, ActionReturnValue } from "./actions.types"; -export class SetInstanceReference implements SimpleAction { - constructor() { +export class SetSpecialTarget implements SimpleAction { + noAutomaticTokenExpansion = true; + + constructor(private key: StoredTargetKey) { this.run = this.run.bind(this); } async run(targets: Target[]): Promise { return { thatTargets: targets, - instanceReferenceTargets: targets, + [`${this.key}Targets`]: targets, }; } } diff --git a/packages/cursorless-engine/src/actions/actions.types.ts b/packages/cursorless-engine/src/actions/actions.types.ts index 560c3bdb354..f81ded7677d 100644 --- a/packages/cursorless-engine/src/actions/actions.types.ts +++ b/packages/cursorless-engine/src/actions/actions.types.ts @@ -52,6 +52,11 @@ export interface ActionReturnValue { * to determine either the range for "every", or the start point for "next" */ instanceReferenceTargets?: Target[]; + + /** + * A list of targets that become the start of the pipeline when the mark is ommitted. + */ + implicitTargets?: Target[]; } export interface SimpleAction { @@ -62,6 +67,13 @@ export interface SimpleAction { * @param args Extra args to command */ getFinalStages?(): ModifierStage[]; + + /** + * If `true`, don't perform automatic token expansion for " this" with + * empty cursor. Used for actions like `setImplicitTarget` that are just + * loading up the pipeline. + */ + noAutomaticTokenExpansion?: boolean; } /** diff --git a/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts b/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts index 90a7ce9a0b9..c76902f6dd5 100644 --- a/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts +++ b/packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts @@ -18,6 +18,7 @@ import { selectionToStoredTarget } from "./selectionToStoredTarget"; export class CommandRunnerImpl implements CommandRunner { private inferenceContext: InferenceContext; private finalStages: ModifierStage[] = []; + private noAutomaticTokenExpansion: boolean | undefined; constructor( private debug: Debug, @@ -53,6 +54,7 @@ export class CommandRunnerImpl implements CommandRunner { sourceSelections: newSourceSelections, sourceTargets: newSourceTargets, instanceReferenceTargets: newInstanceReferenceTargets, + implicitTargets: newImplicitTargets, } = await this.runAction(action); this.storedTargets.set( @@ -64,6 +66,7 @@ export class CommandRunnerImpl implements CommandRunner { constructStoredTarget(newSourceTargets, newSourceSelections), ); this.storedTargets.set("instanceReference", newInstanceReferenceTargets); + this.storedTargets.set("implicit", newImplicitTargets); return returnValue; } @@ -179,6 +182,8 @@ export class CommandRunnerImpl implements CommandRunner { default: { const action = this.actions[actionDescriptor.name]; this.finalStages = action.getFinalStages?.() ?? []; + this.noAutomaticTokenExpansion = + action.noAutomaticTokenExpansion ?? false; return action.run(this.getTargets(actionDescriptor.target)); } } @@ -191,7 +196,10 @@ export class CommandRunnerImpl implements CommandRunner { partialTargetsDescriptor, ); - return this.pipelineRunner.run(targetDescriptor, this.finalStages); + return this.pipelineRunner.run(targetDescriptor, { + actionFinalStages: this.finalStages, + noAutomaticTokenExpansion: this.noAutomaticTokenExpansion, + }); } private getDestinations( diff --git a/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts b/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts index 25248c4d1e5..578d4db5c7f 100644 --- a/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts +++ b/packages/cursorless-engine/src/core/inferFullTargetDescriptor.ts @@ -104,7 +104,7 @@ function inferPrimitiveTarget( (shouldInferPreviousMark(target) ? getPreviousMark(previousTargets) : null) ?? { - type: "cursor", + type: "implicit", }; const modifiers = diff --git a/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/actions.ts b/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/actions.ts index efd29ec7349..45bd715c632 100644 --- a/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/actions.ts +++ b/packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/actions.ts @@ -58,6 +58,7 @@ export const actions = { joinLines: "join", ["private.showParseTree"]: "parse tree", + ["private.setImplicitTarget"]: "implicit", ["experimental.setInstanceReference"]: "from", editNew: null, diff --git a/packages/cursorless-engine/src/processTargets/MarkStageFactory.ts b/packages/cursorless-engine/src/processTargets/MarkStageFactory.ts index 710ef6e72f5..113b170d109 100644 --- a/packages/cursorless-engine/src/processTargets/MarkStageFactory.ts +++ b/packages/cursorless-engine/src/processTargets/MarkStageFactory.ts @@ -1,6 +1,11 @@ import { Mark } from "../typings/TargetDescriptor"; import { MarkStage } from "./PipelineStages.types"; +export interface MarkStageFactoryOpts { + /** If there is an `instance` modifier downstream in the pipeline */ + isForInstance: boolean; +} + export interface MarkStageFactory { - create(mark: Mark): MarkStage; + create(mark: Mark, opts: MarkStageFactoryOpts): MarkStage; } diff --git a/packages/cursorless-engine/src/processTargets/MarkStageFactoryImpl.ts b/packages/cursorless-engine/src/processTargets/MarkStageFactoryImpl.ts index eca5fa94f2c..0afac392b72 100644 --- a/packages/cursorless-engine/src/processTargets/MarkStageFactoryImpl.ts +++ b/packages/cursorless-engine/src/processTargets/MarkStageFactoryImpl.ts @@ -2,7 +2,7 @@ import { ReadOnlyHatMap } from "@cursorless/common"; import { TargetPipelineRunner } from "."; import { StoredTargetMap } from ".."; import { Mark } from "../typings/TargetDescriptor"; -import { MarkStageFactory } from "./MarkStageFactory"; +import { MarkStageFactory, MarkStageFactoryOpts } from "./MarkStageFactory"; import { MarkStage } from "./PipelineStages.types"; import { CursorStage } from "./marks/CursorStage"; import { DecoratedSymbolStage } from "./marks/DecoratedSymbolStage"; @@ -12,6 +12,7 @@ import { NothingStage } from "./marks/NothingStage"; import { RangeMarkStage } from "./marks/RangeMarkStage"; import { StoredTargetStage } from "./marks/StoredTargetStage"; import { TargetMarkStage } from "./marks/TargetMarkStage"; +import { ImplicitMarkStage } from "./marks/ImplicitMarkStage"; export class MarkStageFactoryImpl implements MarkStageFactory { private targetPipelineRunner!: TargetPipelineRunner; @@ -27,10 +28,12 @@ export class MarkStageFactoryImpl implements MarkStageFactory { this.create = this.create.bind(this); } - create(mark: Mark): MarkStage { + create(mark: Mark, opts: MarkStageFactoryOpts): MarkStage { switch (mark.type) { case "cursor": return new CursorStage(mark); + case "implicit": + return new ImplicitMarkStage(this, opts, this.storedTargets); case "that": case "source": return new StoredTargetStage(this.storedTargets, mark.type); @@ -39,7 +42,7 @@ export class MarkStageFactoryImpl implements MarkStageFactory { case "lineNumber": return new LineNumberStage(mark); case "range": - return new RangeMarkStage(this, mark); + return new RangeMarkStage(this, opts, mark); case "nothing": return new NothingStage(mark); case "target": diff --git a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts index 86f7647244f..c5940a5ce5d 100644 --- a/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts +++ b/packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts @@ -21,6 +21,11 @@ import { ImplicitStage } from "./marks/ImplicitStage"; import { ContainingTokenIfUntypedEmptyStage } from "./modifiers/ConditionalModifierStages"; import { PlainTarget } from "./targets"; +interface TargetPipelineRunnerOpts { + actionFinalStages?: ModifierStage[]; + noAutomaticTokenExpansion?: boolean; +} + export class TargetPipelineRunner { constructor( private modifierStageFactory: ModifierStageFactory, @@ -40,12 +45,18 @@ export class TargetPipelineRunner { * document containing it, and potentially rich context information such as * how to remove the target */ - run(target: TargetDescriptor, actionFinalStages?: ModifierStage[]): Target[] { + run( + target: TargetDescriptor, + { + actionFinalStages = [], + noAutomaticTokenExpansion = false, + }: TargetPipelineRunnerOpts = {}, + ): Target[] { return new TargetPipeline( this.modifierStageFactory, this.markStageFactory, target, - actionFinalStages ?? [], + { actionFinalStages, noAutomaticTokenExpansion }, ).run(); } } @@ -55,7 +66,7 @@ class TargetPipeline { private modifierStageFactory: ModifierStageFactory, private markStageFactory: MarkStageFactory, private target: TargetDescriptor, - private actionFinalStages: ModifierStage[], + private opts: Required, ) {} /** @@ -203,7 +214,9 @@ class TargetPipeline { markStage = new ImplicitStage(); targetModifierStages = []; } else { - markStage = this.markStageFactory.create(targetDescriptor.mark); + markStage = this.markStageFactory.create(targetDescriptor.mark, { + isForInstance: targetDescriptor.modifiers.some(isInstanceModifier), + }); targetModifierStages = getModifierStagesFromTargetModifiers( this.modifierStageFactory, targetDescriptor.modifiers, @@ -218,11 +231,13 @@ class TargetPipeline { */ const modifierStages = [ ...targetModifierStages, - ...this.actionFinalStages, + ...this.opts.actionFinalStages, // This performs auto-expansion to token when you say eg "take this" with an // empty selection - new ContainingTokenIfUntypedEmptyStage(this.modifierStageFactory), + ...(this.opts.noAutomaticTokenExpansion + ? [] + : [new ContainingTokenIfUntypedEmptyStage(this.modifierStageFactory)]), ]; // Run all targets through the modifier stages @@ -368,3 +383,14 @@ function targetsToVerticalTarget( } } } + +function isInstanceModifier(modifier: Modifier): boolean { + switch (modifier.type) { + case "everyScope": + case "ordinalScope": + case "relativeScope": + return modifier.scopeType.type === "instance"; + default: + return false; + } +} diff --git a/packages/cursorless-engine/src/processTargets/marks/ImplicitMarkStage.ts b/packages/cursorless-engine/src/processTargets/marks/ImplicitMarkStage.ts new file mode 100644 index 00000000000..d948d84a998 --- /dev/null +++ b/packages/cursorless-engine/src/processTargets/marks/ImplicitMarkStage.ts @@ -0,0 +1,27 @@ +import type { Target } from "../../typings/target.types"; +import type { MarkStage } from "../PipelineStages.types"; +import { MarkStageFactory, MarkStageFactoryOpts } from "../MarkStageFactory"; +import { StoredTargetMap } from "../.."; + +export class ImplicitMarkStage implements MarkStage { + private cursorMarkStage: MarkStage; + + constructor( + markStageFactory: MarkStageFactory, + private opts: MarkStageFactoryOpts, + private storedTargets: StoredTargetMap, + ) { + this.cursorMarkStage = markStageFactory.create( + { type: "cursor" }, + { isForInstance: this.opts.isForInstance }, + ); + } + + run(): Target[] { + if (this.opts.isForInstance) { + return this.cursorMarkStage.run(); + } + + return this.storedTargets.get("implicit") ?? this.cursorMarkStage.run(); + } +} diff --git a/packages/cursorless-engine/src/processTargets/marks/RangeMarkStage.ts b/packages/cursorless-engine/src/processTargets/marks/RangeMarkStage.ts index 4758b045ccf..332bd531bb5 100644 --- a/packages/cursorless-engine/src/processTargets/marks/RangeMarkStage.ts +++ b/packages/cursorless-engine/src/processTargets/marks/RangeMarkStage.ts @@ -1,18 +1,25 @@ import { RangeMark } from "@cursorless/common"; import { Target } from "../../typings/target.types"; -import { MarkStageFactory } from "../MarkStageFactory"; +import { MarkStageFactory, MarkStageFactoryOpts } from "../MarkStageFactory"; import { MarkStage } from "../PipelineStages.types"; import { targetsToContinuousTarget } from "../TargetPipelineRunner"; export class RangeMarkStage implements MarkStage { constructor( private markStageFactory: MarkStageFactory, + private opts: MarkStageFactoryOpts, private mark: RangeMark, ) {} run(): Target[] { - const anchorStage = this.markStageFactory.create(this.mark.anchor); - const activeStage = this.markStageFactory.create(this.mark.active); + const anchorStage = this.markStageFactory.create( + this.mark.anchor, + this.opts, + ); + const activeStage = this.markStageFactory.create( + this.mark.active, + this.opts, + ); const anchorTargets = anchorStage.run(); const activeTargets = activeStage.run(); diff --git a/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts b/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts index 156819c7c8c..8dda1c03946 100644 --- a/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts +++ b/packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts @@ -75,9 +75,7 @@ export class InstanceStage implements ModifierStage { target: Target, { direction, offset, length }: RelativeScopeModifier, ): Target[] { - const referenceTargets = this.storedTargets.get("instanceReference") ?? [ - target, - ]; + const referenceTargets = this.getStoredTargets() ?? [target]; return referenceTargets.flatMap((referenceTarget) => { const { editor } = referenceTarget; @@ -104,14 +102,20 @@ export class InstanceStage implements ModifierStage { }); } + private getStoredTargets() { + return ( + this.storedTargets.get("instanceReference") ?? + this.storedTargets.get("implicit") + ); + } + private getEveryRanges({ editor: targetEditor, }: Target): readonly (readonly [TextEditor, Range])[] { return ( - this.storedTargets - .get("instanceReference") - ?.map(({ editor, contentRange }) => [editor, contentRange] as const) ?? - ([[targetEditor, targetEditor.document.range]] as const) + this.getStoredTargets()?.map( + ({ editor, contentRange }) => [editor, contentRange] as const, + ) ?? ([[targetEditor, targetEditor.document.range]] as const) ); } diff --git a/packages/cursorless-engine/src/testUtil/takeSnapshot.ts b/packages/cursorless-engine/src/testUtil/takeSnapshot.ts index d4b6286cebe..f55c7e9aece 100644 --- a/packages/cursorless-engine/src/testUtil/takeSnapshot.ts +++ b/packages/cursorless-engine/src/testUtil/takeSnapshot.ts @@ -11,7 +11,8 @@ import { TestCaseSnapshot, TextEditor, } from "@cursorless/common"; -import type { StoredTargetMap } from "../core/StoredTargets"; +import { type StoredTargetMap } from "../core/StoredTargets"; +import { storedTargetKeys } from "@cursorless/common"; export async function takeSnapshot( storedTargets: StoredTargetMap | undefined, @@ -45,26 +46,12 @@ export async function takeSnapshot( snapshot.visibleRanges = editor.visibleRanges.map(rangeToPlainObject); } - const thatMarkTargets = storedTargets?.get("that"); - if (thatMarkTargets != null && !excludeFields.includes("thatMark")) { - snapshot.thatMark = thatMarkTargets.map((target) => target.toPlainObject()); - } - - const sourceMarkTargets = storedTargets?.get("source"); - if (sourceMarkTargets != null && !excludeFields.includes("sourceMark")) { - snapshot.sourceMark = sourceMarkTargets.map((target) => - target.toPlainObject(), - ); - } - - const instanceReferenceMarkTargets = storedTargets?.get("instanceReference"); - if ( - instanceReferenceMarkTargets != null && - !excludeFields.includes("instanceReferenceMark") - ) { - snapshot.instanceReferenceMark = instanceReferenceMarkTargets.map( - (target) => target.toPlainObject(), - ); + for (const storedTargetKey of storedTargetKeys) { + const targets = storedTargets?.get(storedTargetKey); + const key = `${storedTargetKey}Mark` as const; + if (targets != null && !excludeFields.includes(key)) { + snapshot[key] = targets.map((target) => target.toPlainObject()); + } } if (extraFields.includes("timeOffsetSeconds")) { diff --git a/packages/cursorless-engine/src/typings/TargetDescriptor.ts b/packages/cursorless-engine/src/typings/TargetDescriptor.ts index bcf96035fab..920e7137ac3 100644 --- a/packages/cursorless-engine/src/typings/TargetDescriptor.ts +++ b/packages/cursorless-engine/src/typings/TargetDescriptor.ts @@ -6,7 +6,11 @@ import { ScopeType, } from "@cursorless/common"; -export type Mark = PartialMark | TargetMark; +export interface ImplicitMark { + type: "implicit"; +} + +export type Mark = PartialMark | TargetMark | ImplicitMark; export interface PrimitiveTargetDescriptor { type: "primitive"; diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/activeLine.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/activeLine.yml new file mode 100644 index 00000000000..a71ece65948 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/activeLine.yml @@ -0,0 +1,41 @@ +languageId: plaintext +command: + version: 6 + spokenForm: implicit line + action: + name: private.setImplicitTarget + target: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: line} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + aaa bbb aaa + aaa + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + marks: {} +finalState: + documentContents: |- + aaa bbb aaa + aaa + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + thatMark: + - type: LineTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 11} + isReversed: false + hasExplicitRange: true + implicitMark: + - type: LineTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 11} + isReversed: false + hasExplicitRange: true diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/activeThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/activeThis.yml new file mode 100644 index 00000000000..ea444a6d8c8 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/activeThis.yml @@ -0,0 +1,35 @@ +languageId: plaintext +command: + version: 6 + spokenForm: implicit this + action: + name: private.setImplicitTarget + target: + type: primitive + mark: {type: cursor} + usePrePhraseSnapshot: true +initialState: + documentContents: aaa bbb + selections: + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} + marks: {} +finalState: + documentContents: aaa bbb + selections: + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} + thatMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 7} + end: {line: 0, character: 7} + isReversed: false + hasExplicitRange: false + implicitMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 7} + end: {line: 0, character: 7} + isReversed: false + hasExplicitRange: false diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/changeNextInstanceChar.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/changeNextInstanceChar.yml new file mode 100644 index 00000000000..ce81a4e812f --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/changeNextInstanceChar.yml @@ -0,0 +1,42 @@ +languageId: plaintext +command: + version: 6 + spokenForm: change next instance char + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: instance} + offset: 1 + length: 1 + direction: forward + - type: containingScope + scopeType: {type: character} + usePrePhraseSnapshot: true +initialState: + documentContents: aba aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} + instanceReferenceMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 0} + isReversed: false + hasExplicitRange: false +finalState: + documentContents: ba aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - type: RawSelectionTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 0} + isReversed: false + hasExplicitRange: true diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/changePreviousChar.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/changePreviousChar.yml new file mode 100644 index 00000000000..6aad605b423 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/changePreviousChar.yml @@ -0,0 +1,40 @@ +languageId: plaintext +command: + version: 6 + spokenForm: change previous char + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: character} + offset: 1 + length: 1 + direction: backward + usePrePhraseSnapshot: true +initialState: + documentContents: aaa bbb + selections: + - anchor: {line: 0, character: 7} + active: {line: 0, character: 7} + marks: {} + implicitMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 7} + end: {line: 0, character: 7} + isReversed: false + hasExplicitRange: false +finalState: + documentContents: aaa bb + selections: + - anchor: {line: 0, character: 5} + active: {line: 0, character: 5} + thatMark: + - type: RawSelectionTarget + contentRange: + start: {line: 0, character: 5} + end: {line: 0, character: 5} + isReversed: false + hasExplicitRange: true diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/fromThis.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/fromThis.yml new file mode 100644 index 00000000000..119874995bb --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/fromThis.yml @@ -0,0 +1,35 @@ +languageId: plaintext +command: + version: 6 + spokenForm: from this + action: + name: experimental.setInstanceReference + target: + type: primitive + mark: {type: cursor} + usePrePhraseSnapshot: true +initialState: + documentContents: aba aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + marks: {} +finalState: + documentContents: aba aaa + selections: + - anchor: {line: 0, character: 0} + active: {line: 0, character: 0} + thatMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 0} + isReversed: false + hasExplicitRange: false + instanceReferenceMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 0} + isReversed: false + hasExplicitRange: false diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/marks/swapTokenWithNextToken.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/marks/swapTokenWithNextToken.yml new file mode 100644 index 00000000000..e71b80964d2 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/marks/swapTokenWithNextToken.yml @@ -0,0 +1,40 @@ +languageId: plaintext +command: + version: 6 + spokenForm: swap token with next token + action: + name: swapTargets + target1: + type: primitive + modifiers: + - type: containingScope + scopeType: {type: token} + target2: + type: primitive + modifiers: + - type: relativeScope + scopeType: {type: token} + offset: 1 + length: 1 + direction: forward + usePrePhraseSnapshot: true +initialState: + documentContents: | + aaa bbb ccc + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} + marks: {} + implicitMark: + - type: UntypedTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 3} + isReversed: false + hasExplicitRange: false +finalState: + documentContents: | + bbb aaa ccc + selections: + - anchor: {line: 1, character: 0} + active: {line: 1, character: 0} diff --git a/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/changeEveryInstance.yml b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/changeEveryInstance.yml new file mode 100644 index 00000000000..1e1c9282d48 --- /dev/null +++ b/packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/modifiers/instance/changeEveryInstance.yml @@ -0,0 +1,36 @@ +languageId: plaintext +command: + version: 6 + spokenForm: change every instance + action: + name: clearAndSetSelection + target: + type: primitive + modifiers: + - type: everyScope + scopeType: {type: instance} + usePrePhraseSnapshot: true +initialState: + documentContents: |- + aaa bbb aaa + aaa + selections: + - anchor: {line: 0, character: 3} + active: {line: 0, character: 3} + marks: {} + implicitMark: + - type: LineTarget + contentRange: + start: {line: 0, character: 0} + end: {line: 0, character: 11} + isReversed: false + hasExplicitRange: true +finalState: + documentContents: |2- + 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/recorded.vscode.test.ts b/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts index 23a440c5ca7..83bae63d402 100644 --- a/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts +++ b/packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts @@ -18,6 +18,7 @@ import { splitKey, SpyIDE, spyIDERecordedValuesToPlainObject, + storedTargetKeys, TestCaseFixtureLegacy, TextEditor, TokenHat, @@ -89,15 +90,10 @@ async function runTest(file: string, spyIde: SpyIDE) { editor.selections = fixture.initialState.selections.map(createSelection); - setStoredTarget(editor, "that", fixture.initialState.thatMark); - - setStoredTarget(editor, "source", fixture.initialState.sourceMark); - - setStoredTarget( - editor, - "instanceReference", - fixture.initialState.instanceReferenceMark, - ); + for (const storedTargetKey of storedTargetKeys) { + const key = `${storedTargetKey}Mark` as const; + setStoredTarget(editor, storedTargetKey, fixture.initialState[key]); + } if (fixture.initialState.clipboard) { vscode.env.clipboard.writeText(fixture.initialState.clipboard); @@ -162,16 +158,11 @@ async function runTest(file: string, spyIde: SpyIDE) { excludeFields.push("clipboard"); } - if (fixture.finalState?.thatMark == null) { - excludeFields.push("thatMark"); - } - - if (fixture.finalState?.sourceMark == null) { - excludeFields.push("sourceMark"); - } - - if (fixture.finalState?.instanceReferenceMark == null) { - excludeFields.push("instanceReferenceMark"); + for (const storedTargetKey of storedTargetKeys) { + const key = `${storedTargetKey}Mark` as const; + if (fixture.finalState?.[key] == null) { + excludeFields.push(key); + } } // FIXME Visible ranges are not asserted, see: diff --git a/packages/cursorless-vscode/src/storedTargetHighlighter.ts b/packages/cursorless-vscode/src/storedTargetHighlighter.ts index d8b244beae3..ac1742bc450 100644 --- a/packages/cursorless-vscode/src/storedTargetHighlighter.ts +++ b/packages/cursorless-vscode/src/storedTargetHighlighter.ts @@ -11,6 +11,7 @@ import { mapValues } from "lodash"; import { usingSetting } from "./usingSetting"; const targetColorMap: Partial> = { + implicit: "content", instanceReference: "domain", };