Skip to content

Commit 30ca50d

Browse files
committed
Fix ordinal stages
1 parent ead733e commit 30ca50d

File tree

5 files changed

+169
-20
lines changed

5 files changed

+169
-20
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ export class ModifierStageFactoryImpl implements ModifierStageFactory {
8282

8383
return new EveryScopeStage(this, this.scopeHandlerFactory, modifier);
8484
case "ordinalScope":
85+
if (modifier.scopeType.type === "instance") {
86+
return new InstanceStage(this, modifier);
87+
}
88+
8589
return new OrdinalScopeStage(this, modifier);
8690
case "relativeScope":
8791
if (modifier.scopeType.type === "instance") {

packages/cursorless-engine/src/processTargets/modifiers/InstanceStage.ts

Lines changed: 71 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
Direction,
33
Modifier,
4+
OrdinalScopeModifier,
45
Range,
56
RelativeScopeModifier,
67
ScopeType,
@@ -13,8 +14,8 @@ import { generateMatchesInRange } from "../../util/getMatchesInRange";
1314
import { ModifierStageFactory } from "../ModifierStageFactory";
1415
import type { ModifierStage } from "../PipelineStages.types";
1516
import { PlainTarget } from "../targets";
16-
import { OutOfRangeError } from "./targetSequenceUtils";
1717
import { ContainingTokenIfUntypedEmptyStage } from "./ConditionalModifierStages";
18+
import { OutOfRangeError } from "./targetSequenceUtils";
1819

1920
export default class InstanceStage implements ModifierStage {
2021
constructor(
@@ -23,15 +24,20 @@ export default class InstanceStage implements ModifierStage {
2324
) {}
2425

2526
run(inputTarget: Target): Target[] {
27+
// If the target is untyped and empty, we want to target the containing
28+
// token. This handles the case where they just say "instance" with an empty
29+
// selection, eg "take every instance".
2630
const target = new ContainingTokenIfUntypedEmptyStage(
2731
this.modifierStageFactory,
2832
).run(inputTarget)[0];
2933

3034
switch (this.modifier.type) {
31-
case "relativeScope":
32-
return this.handleRelativeScope(target, this.modifier);
3335
case "everyScope":
3436
return this.handleEveryScope(target);
37+
case "ordinalScope":
38+
return this.handleOrdinalScope(target, this.modifier);
39+
case "relativeScope":
40+
return this.handleRelativeScope(target, this.modifier);
3541
default:
3642
throw Error(`${this.modifier.type} instance scope not supported`);
3743
}
@@ -41,7 +47,30 @@ export default class InstanceStage implements ModifierStage {
4147
const { editor } = target;
4248

4349
return Array.from(
44-
this.getTargetIterable(target, editor, editor.document.range, "forward"),
50+
this.getTargetIterable(
51+
target,
52+
editor,
53+
this.getEveryRange(editor),
54+
"forward",
55+
),
56+
);
57+
}
58+
59+
private handleOrdinalScope(
60+
target: Target,
61+
{ start, length }: OrdinalScopeModifier,
62+
): Target[] {
63+
const { editor } = target;
64+
65+
return takeFromOffset(
66+
this.getTargetIterable(
67+
target,
68+
editor,
69+
this.getEveryRange(editor),
70+
start >= 0 ? "forward" : "backward",
71+
),
72+
start >= 0 ? start : -(length + start),
73+
length,
4574
);
4675
}
4776

@@ -51,26 +80,20 @@ export default class InstanceStage implements ModifierStage {
5180
): Target[] {
5281
const { editor } = target;
5382

54-
const iterable = this.getTargetIterable(
55-
target,
56-
editor,
83+
const iterationRange =
5784
direction === "forward"
5885
? new Range(target.contentRange.start, editor.document.range.end)
59-
: new Range(editor.document.range.start, target.contentRange.end),
60-
direction,
61-
);
86+
: new Range(editor.document.range.start, target.contentRange.end);
6287

63-
// Skip the first `offset` targets
64-
Array.from(itake(offset, iterable));
65-
66-
// Take the next `length` targets
67-
const targets = Array.from(itake(length, iterable));
68-
69-
if (targets.length < length) {
70-
throw new OutOfRangeError();
71-
}
88+
return takeFromOffset(
89+
this.getTargetIterable(target, editor, iterationRange, direction),
90+
offset,
91+
length,
92+
);
93+
}
7294

73-
return targets;
95+
private getEveryRange(editor: TextEditor): Range {
96+
return editor.document.range;
7497
}
7598

7699
private getTargetIterable(
@@ -139,3 +162,31 @@ function getFilterScopeType(target: Target): ScopeType | null {
139162

140163
return null;
141164
}
165+
166+
/**
167+
* Take `length` items from `iterable` starting at `offset`, throwing an error
168+
* if there are not enough items.
169+
*
170+
* @param iterable The iterable to take from
171+
* @param offset How many items to skip
172+
* @param count How many items to take
173+
* @returns An array of length `length` containing the items from `iterable`
174+
* starting at `offset`
175+
*/
176+
function takeFromOffset<T>(
177+
iterable: Iterable<T>,
178+
offset: number,
179+
count: number,
180+
): T[] {
181+
// Skip the first `offset` targets
182+
Array.from(itake(offset, iterable));
183+
184+
// Take the next `length` targets
185+
const items = Array.from(itake(count, iterable));
186+
187+
if (items.length < count) {
188+
throw new OutOfRangeError();
189+
}
190+
191+
return items;
192+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
languageId: plaintext
2+
command:
3+
version: 5
4+
spokenForm: clear second last instance air
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: ordinalScope
10+
scopeType: {type: instance}
11+
start: -2
12+
length: 1
13+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
14+
usePrePhraseSnapshot: true
15+
initialState:
16+
documentContents: |
17+
aaa bbb ccc aaa ddd aaa eee
18+
selections:
19+
- anchor: {line: 1, character: 0}
20+
active: {line: 1, character: 0}
21+
marks:
22+
default.a:
23+
start: {line: 0, character: 20}
24+
end: {line: 0, character: 23}
25+
finalState:
26+
documentContents: |
27+
aaa bbb ccc ddd aaa eee
28+
selections:
29+
- anchor: {line: 0, character: 12}
30+
active: {line: 0, character: 12}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
languageId: plaintext
2+
command:
3+
version: 5
4+
spokenForm: clear first two instances air
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: ordinalScope
10+
scopeType: {type: instance}
11+
start: 0
12+
length: 2
13+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
14+
usePrePhraseSnapshot: true
15+
initialState:
16+
documentContents: |
17+
aaa bbb ccc aaa ddd aaa eee
18+
selections:
19+
- anchor: {line: 1, character: 0}
20+
active: {line: 1, character: 0}
21+
marks:
22+
default.a:
23+
start: {line: 0, character: 0}
24+
end: {line: 0, character: 3}
25+
finalState:
26+
documentContents: |2
27+
bbb ccc ddd aaa eee
28+
selections:
29+
- anchor: {line: 0, character: 0}
30+
active: {line: 0, character: 0}
31+
- anchor: {line: 0, character: 9}
32+
active: {line: 0, character: 9}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
languageId: plaintext
2+
command:
3+
version: 5
4+
spokenForm: clear last two instances air
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: ordinalScope
10+
scopeType: {type: instance}
11+
start: -2
12+
length: 2
13+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
14+
usePrePhraseSnapshot: true
15+
initialState:
16+
documentContents: |
17+
aaa bbb ccc aaa ddd aaa eee
18+
selections:
19+
- anchor: {line: 1, character: 0}
20+
active: {line: 1, character: 0}
21+
marks:
22+
default.a:
23+
start: {line: 0, character: 0}
24+
end: {line: 0, character: 3}
25+
finalState:
26+
documentContents: |
27+
aaa bbb ccc ddd eee
28+
selections:
29+
- anchor: {line: 0, character: 12}
30+
active: {line: 0, character: 12}
31+
- anchor: {line: 0, character: 17}
32+
active: {line: 0, character: 17}

0 commit comments

Comments
 (0)