Skip to content

Commit 740f658

Browse files
Don't discard everyScope selections for multiple targets (#2893)
Unfortunately became quite a lot of changes to pass the modifier options, but I think this is the only way. It also opens up for more context aware modifiers in the future. Fixes #2889
1 parent f7e1dd2 commit 740f658

19 files changed

+213
-75
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: take every token
5+
action:
6+
name: setSelection
7+
target:
8+
type: primitive
9+
modifiers:
10+
- type: everyScope
11+
scopeType: {type: token}
12+
usePrePhraseSnapshot: false
13+
initialState:
14+
documentContents: |-
15+
aaa bbb ccc
16+
ddd eee
17+
selections:
18+
- anchor: {line: 0, character: 4}
19+
active: {line: 0, character: 11}
20+
- anchor: {line: 1, character: 4}
21+
active: {line: 1, character: 7}
22+
marks: {}
23+
finalState:
24+
documentContents: |-
25+
aaa bbb ccc
26+
ddd eee
27+
selections:
28+
- anchor: {line: 0, character: 4}
29+
active: {line: 0, character: 7}
30+
- anchor: {line: 0, character: 8}
31+
active: {line: 0, character: 11}
32+
- anchor: {line: 1, character: 4}
33+
active: {line: 1, character: 7}

packages/cursorless-engine/src/actions/snippetsLegacy/snippet.ts

+8-3
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,15 @@ import type {
22
SimpleScopeTypeType,
33
SnippetDefinition,
44
} from "@cursorless/common";
5+
import type { ModifierStageFactory } from "../../processTargets/ModifierStageFactory";
6+
import type { ModifierStateOptions } from "../../processTargets/PipelineStages.types";
57
import {
68
Placeholder,
79
Text,
810
Variable,
911
type TextmateSnippet,
1012
} from "../../snippets/vendor/vscodeSnippet/snippetParser";
1113
import { KnownSnippetVariableNames } from "../../snippets/vendor/vscodeSnippet/snippetVariables";
12-
import type { ModifierStageFactory } from "../../processTargets/ModifierStageFactory";
1314
import type { Target } from "../../typings/target.types";
1415

1516
/**
@@ -138,6 +139,10 @@ function findMatchingSnippetDefinitionForSingleTarget(
138139
): number {
139140
const languageId = target.editor.document.languageId;
140141

142+
const options: ModifierStateOptions = {
143+
multipleTargets: false,
144+
};
145+
141146
// We want to find the first definition that matches the given context.
142147
// Note that we just use the first match we find because the definitions are
143148
// guaranteed to come sorted in precedence order.
@@ -165,7 +170,7 @@ function findMatchingSnippetDefinitionForSingleTarget(
165170
type: "containingScope",
166171
scopeType: { type: scopeTypeType },
167172
})
168-
.run(target)[0];
173+
.run(target, options)[0];
169174

170175
if (target.contentRange.isRangeEqual(containingTarget.contentRange)) {
171176
// Skip this scope if the target is exactly the same as the
@@ -177,7 +182,7 @@ function findMatchingSnippetDefinitionForSingleTarget(
177182
scopeType: { type: scopeTypeType },
178183
ancestorIndex: 1,
179184
})
180-
.run(target)[0];
185+
.run(target, options)[0];
181186
}
182187

183188
if (

packages/cursorless-engine/src/processTargets/PipelineStages.types.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ export interface MarkStage {
44
run(): Target[];
55
}
66

7+
export interface ModifierStateOptions {
8+
multipleTargets: boolean;
9+
}
10+
711
export interface ModifierStage {
8-
run(target: Target): Target[];
12+
run(target: Target, options: ModifierStateOptions): Target[];
913
}

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

+13-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ import type {
1414
import type { Target } from "../typings/target.types";
1515
import type { MarkStageFactory } from "./MarkStageFactory";
1616
import type { ModifierStageFactory } from "./ModifierStageFactory";
17-
import type { MarkStage, ModifierStage } from "./PipelineStages.types";
17+
import type {
18+
MarkStage,
19+
ModifierStage,
20+
ModifierStateOptions,
21+
} from "./PipelineStages.types";
1822
import { createContinuousRangeTarget } from "./createContinuousRangeTarget";
1923
import { ImplicitStage } from "./marks/ImplicitStage";
2024
import { ContainingTokenIfUntypedEmptyStage } from "./modifiers/ConditionalModifierStages";
@@ -296,7 +300,10 @@ export function processModifierStages(
296300
// one-by-one and concatenating the results before passing them on to the
297301
// next stage.
298302
modifierStages.forEach((stage) => {
299-
targets = targets.flatMap((target) => stage.run(target));
303+
const options: ModifierStateOptions = {
304+
multipleTargets: targets.length > 1,
305+
};
306+
targets = targets.flatMap((target) => stage.run(target, options));
300307
});
301308

302309
// Then return the output from the final stage
@@ -309,6 +316,9 @@ function getExcludedScope(
309316
scopeType: ScopeType,
310317
direction: Direction,
311318
): Target {
319+
const options: ModifierStateOptions = {
320+
multipleTargets: false,
321+
};
312322
return (
313323
modifierStageFactory
314324
.create({
@@ -321,7 +331,7 @@ function getExcludedScope(
321331
// NB: The following line assumes that content range is always
322332
// contained by domain, so that "every" will properly reconstruct
323333
// the target from the content range.
324-
.run(target)[0]
334+
.run(target, options)[0]
325335
);
326336
}
327337

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import type { CascadingModifier } from "@cursorless/common";
22
import type { Target } from "../../typings/target.types";
33
import type { ModifierStageFactory } from "../ModifierStageFactory";
4-
import type { ModifierStage } from "../PipelineStages.types";
4+
import type {
5+
ModifierStage,
6+
ModifierStateOptions,
7+
} from "../PipelineStages.types";
58

69
/**
710
* Tries each of the given modifiers in turn until one of them doesn't throw an
@@ -25,10 +28,10 @@ export class CascadingStage implements ModifierStage {
2528
return this.nestedStages_;
2629
}
2730

28-
run(target: Target): Target[] {
31+
run(target: Target, options: ModifierStateOptions): Target[] {
2932
for (const nestedStage of this.nestedStages) {
3033
try {
31-
return nestedStage.run(target);
34+
return nestedStage.run(target, options);
3235
} catch (_error) {
3336
continue;
3437
}

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

+6-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import type { Modifier, ModifyIfUntypedModifier } from "@cursorless/common";
22
import type { Target } from "../../typings/target.types";
33
import type { ModifierStageFactory } from "../ModifierStageFactory";
4-
import type { ModifierStage } from "../PipelineStages.types";
4+
import type {
5+
ModifierStage,
6+
ModifierStateOptions,
7+
} from "../PipelineStages.types";
58

69
abstract class ConditionalModifierBaseStage implements ModifierStage {
710
private nestedStage_?: ModifierStage;
@@ -12,12 +15,12 @@ abstract class ConditionalModifierBaseStage implements ModifierStage {
1215
private nestedModifier: Modifier,
1316
) {}
1417

15-
run(target: Target): Target[] {
18+
run(target: Target, options: ModifierStateOptions): Target[] {
1619
if (this.shouldModify(target)) {
1720
// Modify this target
1821
try {
1922
return this.nestedStage
20-
.run(target)
23+
.run(target, options)
2124
.map((newTarget) => newTarget.withThatTarget(target));
2225
} catch (ex) {
2326
// suppressErrors === true => Allow this target to be returned unmodified

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ import type { ContainingScopeModifier } from "@cursorless/common";
22
import { NoContainingScopeError } from "@cursorless/common";
33
import type { Target } from "../../typings/target.types";
44
import type { ModifierStageFactory } from "../ModifierStageFactory";
5-
import type { ModifierStage } from "../PipelineStages.types";
6-
import type { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory";
5+
import type {
6+
ModifierStage,
7+
ModifierStateOptions,
8+
} from "../PipelineStages.types";
79
import { getContainingScopeTarget } from "./getContainingScopeTarget";
10+
import type { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory";
811

912
/**
1013
* This modifier stage expands from the input target to the smallest containing
@@ -31,7 +34,7 @@ export class ContainingScopeStage implements ModifierStage {
3134
private modifier: ContainingScopeModifier,
3235
) {}
3336

34-
run(target: Target): Target[] {
37+
run(target: Target, options: ModifierStateOptions): Target[] {
3538
const { scopeType, ancestorIndex = 0 } = this.modifier;
3639

3740
const scopeHandler = this.scopeHandlerFactory.maybeCreate(
@@ -42,7 +45,7 @@ export class ContainingScopeStage implements ModifierStage {
4245
if (scopeHandler == null) {
4346
return this.modifierStageFactory
4447
.getLegacyScopeStage(this.modifier)
45-
.run(target);
48+
.run(target, options);
4649
}
4750

4851
const containingScopes = getContainingScopeTarget(

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

+9-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1-
import type { EveryScopeModifier, TextEditor, Range } from "@cursorless/common";
1+
import type { EveryScopeModifier, Range, TextEditor } from "@cursorless/common";
22
import { NoContainingScopeError } from "@cursorless/common";
33
import type { Target } from "../../typings/target.types";
44
import type { ModifierStageFactory } from "../ModifierStageFactory";
5-
import type { ModifierStage } from "../PipelineStages.types";
5+
import type {
6+
ModifierStage,
7+
ModifierStateOptions,
8+
} from "../PipelineStages.types";
69
import { getContainingScopeTarget } from "./getContainingScopeTarget";
710
import type { ScopeHandlerFactory } from "./scopeHandlers/ScopeHandlerFactory";
811
import type { TargetScope } from "./scopeHandlers/scope.types";
@@ -35,7 +38,7 @@ export class EveryScopeStage implements ModifierStage {
3538
private modifier: EveryScopeModifier,
3639
) {}
3740

38-
run(target: Target): Target[] {
41+
run(target: Target, options: ModifierStateOptions): Target[] {
3942
const { scopeType } = this.modifier;
4043
const { editor, isReversed } = target;
4144

@@ -47,7 +50,7 @@ export class EveryScopeStage implements ModifierStage {
4750
if (scopeHandler == null) {
4851
return this.modifierStageFactory
4952
.getLegacyScopeStage(this.modifier)
50-
.run(target);
53+
.run(target, options);
5154
}
5255

5356
let scopes: TargetScope[] | undefined;
@@ -62,7 +65,8 @@ export class EveryScopeStage implements ModifierStage {
6265
if (
6366
scopes.length === 1 &&
6467
scopes[0].domain.contains(target.contentRange) &&
65-
!target.hasExplicitScopeType
68+
!target.hasExplicitScopeType &&
69+
!options.multipleTargets
6670
) {
6771
// If the only scope that came back completely contains the input target
6872
// range, we treat the input as if it had no explicit range, expanding

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

+23-10
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import {
66
} from "@cursorless/common";
77
import type { Target } from "../../typings/target.types";
88
import type { ModifierStageFactory } from "../ModifierStageFactory";
9-
import type { ModifierStage } from "../PipelineStages.types";
9+
import type {
10+
ModifierStage,
11+
ModifierStateOptions,
12+
} from "../PipelineStages.types";
1013
import {
1114
getModifierStagesFromTargetModifiers,
1215
processModifierStages,
@@ -53,9 +56,9 @@ class BoundedLineStage implements ModifierStage {
5356
private modifier: HeadModifier | TailModifier,
5457
) {}
5558

56-
run(target: Target): Target[] {
57-
const line = this.getContainingLine(target);
58-
const pairInterior = this.getContainingPairInterior(target);
59+
run(target: Target, options: ModifierStateOptions): Target[] {
60+
const line = this.getContainingLine(target, options);
61+
const pairInterior = this.getContainingPairInterior(target, options);
5962

6063
const intersection =
6164
pairInterior != null
@@ -75,9 +78,12 @@ class BoundedLineStage implements ModifierStage {
7578
];
7679
}
7780

78-
private getContainingPairInterior(target: Target): Target | undefined {
81+
private getContainingPairInterior(
82+
target: Target,
83+
options: ModifierStateOptions,
84+
): Target | undefined {
7985
try {
80-
return this.getContaining(target, {
86+
return this.getContaining(target, options, {
8187
type: "surroundingPairInterior",
8288
delimiter: "any",
8389
})[0];
@@ -89,15 +95,22 @@ class BoundedLineStage implements ModifierStage {
8995
}
9096
}
9197

92-
private getContainingLine(target: Target): Target {
93-
return this.getContaining(target, {
98+
private getContainingLine(
99+
target: Target,
100+
options: ModifierStateOptions,
101+
): Target {
102+
return this.getContaining(target, options, {
94103
type: "line",
95104
})[0];
96105
}
97106

98-
private getContaining(target: Target, scopeType: ScopeType): Target[] {
107+
private getContaining(
108+
target: Target,
109+
options: ModifierStateOptions,
110+
scopeType: ScopeType,
111+
): Target[] {
99112
return this.modifierStageFactory
100113
.create({ type: "containingScope", scopeType })
101-
.run(target);
114+
.run(target, options);
102115
}
103116
}

0 commit comments

Comments
 (0)