Skip to content

Commit f2c9f4a

Browse files
authored
No automatic token expansion for "from"; stored target cleanup (#2170)
## Checklist - [x] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [-] I have updated the [docs](https://github.com/cursorless-dev/cursorless/tree/main/docs) and [cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet) - [-] I have not broken the cheatsheet
1 parent 2ae73a7 commit f2c9f4a

File tree

10 files changed

+144
-57
lines changed

10 files changed

+144
-57
lines changed

packages/common/src/testUtil/TestCaseSnapshot.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,30 @@
1+
import { StoredTargetKey } from "../StoredTargetKey";
12
import {
23
RangePlainObject,
34
SelectionPlainObject,
45
SerializedMarks,
56
TargetPlainObject,
67
} from "../util/toPlainObject";
78

8-
export type TestCaseSnapshot = {
9+
type MarkKeys = {
10+
[K in `${StoredTargetKey}Mark`]?: TargetPlainObject[];
11+
};
12+
13+
export interface TestCaseSnapshot extends MarkKeys {
914
documentContents: string;
1015
selections: SelectionPlainObject[];
1116
clipboard?: string;
1217
// FIXME Visible ranges are not asserted during testing, see:
1318
// https://github.com/cursorless-dev/cursorless/issues/160
1419
visibleRanges?: RangePlainObject[];
1520
marks?: SerializedMarks;
16-
thatMark?: TargetPlainObject[];
17-
sourceMark?: TargetPlainObject[];
18-
instanceReferenceMark?: TargetPlainObject[];
1921
timeOffsetSeconds?: number;
2022

2123
/**
2224
* Extra information about the snapshot. Must be json serializable
2325
*/
2426
metadata?: unknown;
25-
};
26-
27+
}
2728
export type ExtraSnapshotField = keyof TestCaseSnapshot;
2829
export type ExcludableSnapshotField = keyof TestCaseSnapshot;
2930

packages/cursorless-engine/src/actions/Actions.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ import Remove from "./Remove";
3333
import Replace from "./Replace";
3434
import Rewrap from "./Rewrap";
3535
import { ScrollToBottom, ScrollToCenter, ScrollToTop } from "./Scroll";
36-
import { SetInstanceReference } from "./SetInstanceReference";
36+
import { SetSpecialTarget } from "./SetSpecialTarget";
3737
import {
3838
SetSelection,
3939
SetSelectionAfter,
@@ -134,7 +134,9 @@ export class Actions implements ActionRecord {
134134
scrollToBottom = new ScrollToBottom();
135135
scrollToCenter = new ScrollToCenter();
136136
scrollToTop = new ScrollToTop();
137-
["experimental.setInstanceReference"] = new SetInstanceReference();
137+
["experimental.setInstanceReference"] = new SetSpecialTarget(
138+
"instanceReference",
139+
);
138140
setSelection = new SetSelection();
139141
setSelectionAfter = new SetSelectionAfter();
140142
setSelectionBefore = new SetSelectionBefore();
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
1+
import { StoredTargetKey } from "@cursorless/common";
12
import { Target } from "../typings/target.types";
23
import { SimpleAction, ActionReturnValue } from "./actions.types";
34

4-
export class SetInstanceReference implements SimpleAction {
5-
constructor() {
5+
export class SetSpecialTarget implements SimpleAction {
6+
noAutomaticTokenExpansion = true;
7+
8+
constructor(private key: StoredTargetKey) {
69
this.run = this.run.bind(this);
710
}
811

912
async run(targets: Target[]): Promise<ActionReturnValue> {
1013
return {
1114
thatTargets: targets,
12-
instanceReferenceTargets: targets,
15+
[`${this.key}Targets`]: targets,
1316
};
1417
}
1518
}

packages/cursorless-engine/src/actions/actions.types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ export interface SimpleAction {
6262
* @param args Extra args to command
6363
*/
6464
getFinalStages?(): ModifierStage[];
65+
66+
/**
67+
* If `true`, don't perform automatic token expansion for "<action> this" with
68+
* empty cursor. Used for actions like `setImplicitTarget` that are just
69+
* loading up the pipeline.
70+
*/
71+
noAutomaticTokenExpansion?: boolean;
6572
}
6673

6774
/**

packages/cursorless-engine/src/core/commandRunner/CommandRunnerImpl.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { selectionToStoredTarget } from "./selectionToStoredTarget";
1818
export class CommandRunnerImpl implements CommandRunner {
1919
private inferenceContext: InferenceContext;
2020
private finalStages: ModifierStage[] = [];
21+
private noAutomaticTokenExpansion: boolean | undefined;
2122

2223
constructor(
2324
private debug: Debug,
@@ -179,6 +180,8 @@ export class CommandRunnerImpl implements CommandRunner {
179180
default: {
180181
const action = this.actions[actionDescriptor.name];
181182
this.finalStages = action.getFinalStages?.() ?? [];
183+
this.noAutomaticTokenExpansion =
184+
action.noAutomaticTokenExpansion ?? false;
182185
return action.run(this.getTargets(actionDescriptor.target));
183186
}
184187
}
@@ -191,7 +194,10 @@ export class CommandRunnerImpl implements CommandRunner {
191194
partialTargetsDescriptor,
192195
);
193196

194-
return this.pipelineRunner.run(targetDescriptor, this.finalStages);
197+
return this.pipelineRunner.run(targetDescriptor, {
198+
actionFinalStages: this.finalStages,
199+
noAutomaticTokenExpansion: this.noAutomaticTokenExpansion,
200+
});
195201
}
196202

197203
private getDestinations(

packages/cursorless-engine/src/processTargets/TargetPipelineRunner.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ import { ImplicitStage } from "./marks/ImplicitStage";
2121
import { ContainingTokenIfUntypedEmptyStage } from "./modifiers/ConditionalModifierStages";
2222
import { PlainTarget } from "./targets";
2323

24+
interface TargetPipelineRunnerOpts {
25+
actionFinalStages?: ModifierStage[];
26+
noAutomaticTokenExpansion?: boolean;
27+
}
28+
2429
export class TargetPipelineRunner {
2530
constructor(
2631
private modifierStageFactory: ModifierStageFactory,
@@ -40,12 +45,18 @@ export class TargetPipelineRunner {
4045
* document containing it, and potentially rich context information such as
4146
* how to remove the target
4247
*/
43-
run(target: TargetDescriptor, actionFinalStages?: ModifierStage[]): Target[] {
48+
run(
49+
target: TargetDescriptor,
50+
{
51+
actionFinalStages = [],
52+
noAutomaticTokenExpansion = false,
53+
}: TargetPipelineRunnerOpts = {},
54+
): Target[] {
4455
return new TargetPipeline(
4556
this.modifierStageFactory,
4657
this.markStageFactory,
4758
target,
48-
actionFinalStages ?? [],
59+
{ actionFinalStages, noAutomaticTokenExpansion },
4960
).run();
5061
}
5162
}
@@ -55,7 +66,7 @@ class TargetPipeline {
5566
private modifierStageFactory: ModifierStageFactory,
5667
private markStageFactory: MarkStageFactory,
5768
private target: TargetDescriptor,
58-
private actionFinalStages: ModifierStage[],
69+
private opts: Required<TargetPipelineRunnerOpts>,
5970
) {}
6071

6172
/**
@@ -218,11 +229,13 @@ class TargetPipeline {
218229
*/
219230
const modifierStages = [
220231
...targetModifierStages,
221-
...this.actionFinalStages,
232+
...this.opts.actionFinalStages,
222233

223234
// This performs auto-expansion to token when you say eg "take this" with an
224235
// empty selection
225-
new ContainingTokenIfUntypedEmptyStage(this.modifierStageFactory),
236+
...(this.opts.noAutomaticTokenExpansion
237+
? []
238+
: [new ContainingTokenIfUntypedEmptyStage(this.modifierStageFactory)]),
226239
];
227240

228241
// Run all targets through the modifier stages

packages/cursorless-engine/src/testUtil/takeSnapshot.ts

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ import {
1111
TestCaseSnapshot,
1212
TextEditor,
1313
} from "@cursorless/common";
14-
import type { StoredTargetMap } from "../core/StoredTargets";
14+
import { type StoredTargetMap } from "../core/StoredTargets";
15+
import { storedTargetKeys } from "@cursorless/common";
1516

1617
export async function takeSnapshot(
1718
storedTargets: StoredTargetMap | undefined,
@@ -45,26 +46,12 @@ export async function takeSnapshot(
4546
snapshot.visibleRanges = editor.visibleRanges.map(rangeToPlainObject);
4647
}
4748

48-
const thatMarkTargets = storedTargets?.get("that");
49-
if (thatMarkTargets != null && !excludeFields.includes("thatMark")) {
50-
snapshot.thatMark = thatMarkTargets.map((target) => target.toPlainObject());
51-
}
52-
53-
const sourceMarkTargets = storedTargets?.get("source");
54-
if (sourceMarkTargets != null && !excludeFields.includes("sourceMark")) {
55-
snapshot.sourceMark = sourceMarkTargets.map((target) =>
56-
target.toPlainObject(),
57-
);
58-
}
59-
60-
const instanceReferenceMarkTargets = storedTargets?.get("instanceReference");
61-
if (
62-
instanceReferenceMarkTargets != null &&
63-
!excludeFields.includes("instanceReferenceMark")
64-
) {
65-
snapshot.instanceReferenceMark = instanceReferenceMarkTargets.map(
66-
(target) => target.toPlainObject(),
67-
);
49+
for (const storedTargetKey of storedTargetKeys) {
50+
const targets = storedTargets?.get(storedTargetKey);
51+
const key = `${storedTargetKey}Mark` as const;
52+
if (targets != null && !excludeFields.includes(key)) {
53+
snapshot[key] = targets.map((target) => target.toPlainObject());
54+
}
6855
}
6956

7057
if (extraFields.includes("timeOffsetSeconds")) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
languageId: plaintext
2+
command:
3+
version: 6
4+
spokenForm: change next instance char
5+
action:
6+
name: clearAndSetSelection
7+
target:
8+
type: primitive
9+
modifiers:
10+
- type: relativeScope
11+
scopeType: {type: instance}
12+
offset: 1
13+
length: 1
14+
direction: forward
15+
- type: containingScope
16+
scopeType: {type: character}
17+
usePrePhraseSnapshot: true
18+
initialState:
19+
documentContents: aba aaa
20+
selections:
21+
- anchor: {line: 0, character: 0}
22+
active: {line: 0, character: 0}
23+
marks: {}
24+
instanceReferenceMark:
25+
- type: UntypedTarget
26+
contentRange:
27+
start: {line: 0, character: 0}
28+
end: {line: 0, character: 0}
29+
isReversed: false
30+
hasExplicitRange: false
31+
finalState:
32+
documentContents: ba aaa
33+
selections:
34+
- anchor: {line: 0, character: 0}
35+
active: {line: 0, character: 0}
36+
thatMark:
37+
- type: RawSelectionTarget
38+
contentRange:
39+
start: {line: 0, character: 0}
40+
end: {line: 0, character: 0}
41+
isReversed: false
42+
hasExplicitRange: true
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
languageId: plaintext
2+
command:
3+
version: 6
4+
spokenForm: from this
5+
action:
6+
name: experimental.setInstanceReference
7+
target:
8+
type: primitive
9+
mark: {type: cursor}
10+
usePrePhraseSnapshot: true
11+
initialState:
12+
documentContents: aba aaa
13+
selections:
14+
- anchor: {line: 0, character: 0}
15+
active: {line: 0, character: 0}
16+
marks: {}
17+
finalState:
18+
documentContents: aba aaa
19+
selections:
20+
- anchor: {line: 0, character: 0}
21+
active: {line: 0, character: 0}
22+
thatMark:
23+
- type: UntypedTarget
24+
contentRange:
25+
start: {line: 0, character: 0}
26+
end: {line: 0, character: 0}
27+
isReversed: false
28+
hasExplicitRange: false
29+
instanceReferenceMark:
30+
- type: UntypedTarget
31+
contentRange:
32+
start: {line: 0, character: 0}
33+
end: {line: 0, character: 0}
34+
isReversed: false
35+
hasExplicitRange: false

packages/cursorless-vscode-e2e/src/suite/recorded.vscode.test.ts

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
splitKey,
1919
SpyIDE,
2020
spyIDERecordedValuesToPlainObject,
21+
storedTargetKeys,
2122
TestCaseFixtureLegacy,
2223
TextEditor,
2324
TokenHat,
@@ -89,15 +90,10 @@ async function runTest(file: string, spyIde: SpyIDE) {
8990

9091
editor.selections = fixture.initialState.selections.map(createSelection);
9192

92-
setStoredTarget(editor, "that", fixture.initialState.thatMark);
93-
94-
setStoredTarget(editor, "source", fixture.initialState.sourceMark);
95-
96-
setStoredTarget(
97-
editor,
98-
"instanceReference",
99-
fixture.initialState.instanceReferenceMark,
100-
);
93+
for (const storedTargetKey of storedTargetKeys) {
94+
const key = `${storedTargetKey}Mark` as const;
95+
setStoredTarget(editor, storedTargetKey, fixture.initialState[key]);
96+
}
10197

10298
if (fixture.initialState.clipboard) {
10399
vscode.env.clipboard.writeText(fixture.initialState.clipboard);
@@ -162,16 +158,11 @@ async function runTest(file: string, spyIde: SpyIDE) {
162158
excludeFields.push("clipboard");
163159
}
164160

165-
if (fixture.finalState?.thatMark == null) {
166-
excludeFields.push("thatMark");
167-
}
168-
169-
if (fixture.finalState?.sourceMark == null) {
170-
excludeFields.push("sourceMark");
171-
}
172-
173-
if (fixture.finalState?.instanceReferenceMark == null) {
174-
excludeFields.push("instanceReferenceMark");
161+
for (const storedTargetKey of storedTargetKeys) {
162+
const key = `${storedTargetKey}Mark` as const;
163+
if (fixture.finalState?.[key] == null) {
164+
excludeFields.push(key);
165+
}
175166
}
176167

177168
// FIXME Visible ranges are not asserted, see:

0 commit comments

Comments
 (0)