Skip to content

Commit 0b7756e

Browse files
authored
Support multiple targets per scope (#1509)
- Depends on #1506 ## Checklist - [ ] 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 135b5b5 commit 0b7756e

21 files changed

+123
-56
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,6 @@ export class ContainingScopeStage implements ModifierStage {
5757
throw new NoContainingScopeError(this.modifier.scopeType.type);
5858
}
5959

60-
return [containingScope];
60+
return containingScope;
6161
}
6262
}

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

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -73,29 +73,27 @@ export class EveryScopeStage implements ModifierStage {
7373
if (scopes == null) {
7474
// If target had no explicit range, or was contained by a single target
7575
// instance, expand to iteration scope before overlapping
76-
scopes = getScopesOverlappingRange(
76+
scopes = this.getDefaultIterationRange(
7777
scopeHandler,
78-
editor,
79-
this.getDefaultIterationRange(
80-
scopeHandler,
81-
this.scopeHandlerFactory,
82-
target,
83-
),
78+
this.scopeHandlerFactory,
79+
target,
80+
).flatMap((iterationRange) =>
81+
getScopesOverlappingRange(scopeHandler, editor, iterationRange),
8482
);
8583
}
8684

8785
if (scopes.length === 0) {
8886
throw new NoContainingScopeError(scopeType.type);
8987
}
9088

91-
return scopes.map((scope) => scope.getTarget(isReversed));
89+
return scopes.flatMap((scope) => scope.getTargets(isReversed));
9290
}
9391

9492
getDefaultIterationRange(
9593
scopeHandler: ScopeHandler,
9694
scopeHandlerFactory: ScopeHandlerFactory,
9795
target: Target,
98-
): Range {
96+
): Range[] {
9997
const iterationScopeHandler = scopeHandlerFactory.create(
10098
scopeHandler.iterationScopeType,
10199
target.editor.document.languageId,
@@ -116,7 +114,7 @@ export class EveryScopeStage implements ModifierStage {
116114
);
117115
}
118116

119-
return iterationScopeTarget.contentRange;
117+
return iterationScopeTarget.map((target) => target.contentRange);
120118
}
121119
}
122120

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ export default class RelativeExclusiveScopeStage implements ModifierStage {
6464
// When we hit offset, that becomes proximal scope
6565
if (desiredScopeCount === 1) {
6666
// Just yield it if we only want 1 scope
67-
return [scope.getTarget(isReversed)];
67+
return scope.getTargets(isReversed);
6868
}
6969

7070
proximalScope = scope;
@@ -73,7 +73,7 @@ export default class RelativeExclusiveScopeStage implements ModifierStage {
7373

7474
if (scopeCount === offset + desiredScopeCount - 1) {
7575
// Then make a range when we get the desired number of scopes
76-
return [constructScopeRangeTarget(isReversed, proximalScope!, scope)];
76+
return constructScopeRangeTarget(isReversed, proximalScope!, scope);
7777
}
7878
}
7979

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,10 @@ export class RelativeInclusiveScopeStage implements ModifierStage {
7474
throw new OutOfRangeError();
7575
}
7676

77-
return [
78-
constructScopeRangeTarget(
79-
isReversed,
80-
scopes[0],
81-
scopes[scopes.length - 1],
82-
),
83-
];
77+
return constructScopeRangeTarget(
78+
isReversed,
79+
scopes[0],
80+
scopes[scopes.length - 1],
81+
);
8482
}
8583
}

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

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,20 @@ export function constructScopeRangeTarget(
1717
isReversed: boolean,
1818
scope1: TargetScope,
1919
scope2: TargetScope,
20-
): Target {
20+
): Target[] {
2121
if (scope1 === scope2) {
22-
return scope1.getTarget(isReversed);
22+
return scope1.getTargets(isReversed);
2323
}
2424

25-
const target1 = scope1.getTarget(isReversed);
26-
const target2 = scope2.getTarget(isReversed);
25+
const targets1 = scope1.getTargets(isReversed);
26+
const targets2 = scope2.getTargets(isReversed);
27+
28+
if (targets1.length !== 1 || targets2.length !== 1) {
29+
throw Error("Scope range targets must be single-target");
30+
}
31+
32+
const [target1] = targets1;
33+
const [target2] = targets2;
2734

2835
const isScope2After = target2.contentRange.start.isAfterOrEqual(
2936
target1.contentRange.start,
@@ -33,10 +40,7 @@ export function constructScopeRangeTarget(
3340
? [target1, target2]
3441
: [target2, target1];
3542

36-
return startTarget.createContinuousRangeTarget(
37-
isReversed,
38-
endTarget,
39-
true,
40-
true,
41-
);
43+
return [
44+
startTarget.createContinuousRangeTarget(isReversed, endTarget, true, true),
45+
];
4246
}

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export function getContainingScopeTarget(
1717
target: Target,
1818
scopeHandler: ScopeHandler,
1919
ancestorIndex: number = 0,
20-
): Target | undefined {
20+
): Target[] | undefined {
2121
const {
2222
isReversed,
2323
editor,
@@ -46,7 +46,7 @@ export function getContainingScopeTarget(
4646
return undefined;
4747
}
4848

49-
return scope.getTarget(isReversed);
49+
return scope.getTargets(isReversed);
5050
}
5151

5252
const startScope = expandFromPosition(
@@ -62,7 +62,7 @@ export function getContainingScopeTarget(
6262
}
6363

6464
if (startScope.domain.contains(end)) {
65-
return startScope.getTarget(isReversed);
65+
return startScope.getTargets(isReversed);
6666
}
6767

6868
const endScope = expandFromPosition(

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/BaseScopeHandler.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ suite("BaseScopeHandler", () => {
128128
const inputScopes = testCase.scopes.map((scope) => ({
129129
editor,
130130
domain: toRange(scope.start, scope.end),
131-
getTarget: () => undefined as any,
131+
getTargets: () => undefined as any,
132132
}));
133133

134134
assert.deepStrictEqual(

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/CharacterScopeHandler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ export default class CharacterScopeHandler extends NestedScopeHandler {
3333
(range) => ({
3434
editor,
3535
domain: range,
36-
getTarget: (isReversed) =>
36+
getTargets: (isReversed) => [
3737
new PlainTarget({
3838
editor,
3939
contentRange: range,
4040
isReversed,
4141
isToken: false,
4242
}),
43+
],
4344
}),
4445
);
4546
}

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/DocumentScopeHandler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,13 @@ export default class DocumentScopeHandler extends BaseScopeHandler {
2323
yield {
2424
editor,
2525
domain: contentRange,
26-
getTarget: (isReversed) =>
26+
getTargets: (isReversed) => [
2727
new DocumentTarget({
2828
editor,
2929
isReversed,
3030
contentRange,
3131
}),
32+
],
3233
};
3334
}
3435
}

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/IdentifierScopeHandler.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,13 @@ export default class IdentifierScopeHandler extends NestedScopeHandler {
2121
(range) => ({
2222
editor,
2323
domain: range,
24-
getTarget: (isReversed) =>
24+
getTargets: (isReversed) => [
2525
new TokenTarget({
2626
editor,
2727
contentRange: range,
2828
isReversed,
2929
}),
30+
],
3031
}),
3132
);
3233
}

0 commit comments

Comments
 (0)