Skip to content

Commit 3493ca2

Browse files
authored
Make "inside" fall back to pair when the target doesn't have an interior (#2426)
This one bit me a lot. See the test case for what now works. Basically "inside value" when value is a string is the most common one ## 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 d9bac3e commit 3493ca2

File tree

17 files changed

+163
-70
lines changed

17 files changed

+163
-70
lines changed

data/fixtures/recorded/implicitExpansion/clearBoundsToken.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,10 @@ initialState:
1616
- anchor: {line: 0, character: 1}
1717
active: {line: 0, character: 1}
1818
marks: {}
19-
thrownError: {name: NoContainingScopeError}
19+
finalState:
20+
documentContents: aaa
21+
selections:
22+
- anchor: {line: 0, character: 0}
23+
active: {line: 0, character: 0}
24+
- anchor: {line: 0, character: 3}
25+
active: {line: 0, character: 3}

data/fixtures/recorded/implicitExpansion/clearCoreToken.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,8 @@ initialState:
1616
- anchor: {line: 0, character: 1}
1717
active: {line: 0, character: 1}
1818
marks: {}
19-
thrownError: {name: NoContainingScopeError}
19+
finalState:
20+
documentContents: ()
21+
selections:
22+
- anchor: {line: 0, character: 1}
23+
active: {line: 0, character: 1}

data/fixtures/recorded/implicitExpansion/squareSwitchFunk.yml

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,13 @@ initialState:
2222
- anchor: {line: 2, character: 7}
2323
active: {line: 2, character: 7}
2424
marks: {}
25-
thrownError: {name: NoContainingScopeError}
25+
finalState:
26+
documentContents: |-
27+
[
28+
function myFunk() {
29+
// aaa
30+
}
31+
]
32+
selections:
33+
- anchor: {line: 2, character: 7}
34+
active: {line: 2, character: 7}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
languageId: typescript
2+
command:
3+
version: 7
4+
spokenForm: change bounds value
5+
action:
6+
name: clearAndSetSelection
7+
target:
8+
type: primitive
9+
modifiers:
10+
- {type: excludeInterior}
11+
- type: containingScope
12+
scopeType: {type: value}
13+
usePrePhraseSnapshot: true
14+
initialState:
15+
documentContents: |-
16+
const aaa = {
17+
bbb: "ccc ddd"
18+
}
19+
selections:
20+
- anchor: {line: 1, character: 4}
21+
active: {line: 1, character: 4}
22+
marks: {}
23+
finalState:
24+
documentContents: |-
25+
const aaa = {
26+
bbb: ccc ddd
27+
}
28+
selections:
29+
- anchor: {line: 1, character: 9}
30+
active: {line: 1, character: 9}
31+
- anchor: {line: 1, character: 16}
32+
active: {line: 1, character: 16}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
languageId: typescript
2+
command:
3+
version: 7
4+
spokenForm: change inside value
5+
action:
6+
name: clearAndSetSelection
7+
target:
8+
type: primitive
9+
modifiers:
10+
- {type: interiorOnly}
11+
- type: containingScope
12+
scopeType: {type: value}
13+
usePrePhraseSnapshot: true
14+
initialState:
15+
documentContents: |-
16+
const aaa = {
17+
bbb: "ccc ddd"
18+
}
19+
selections:
20+
- anchor: {line: 1, character: 4}
21+
active: {line: 1, character: 4}
22+
marks: {}
23+
finalState:
24+
documentContents: |-
25+
const aaa = {
26+
bbb: ""
27+
}
28+
selections:
29+
- anchor: {line: 1, character: 10}
30+
active: {line: 1, character: 10}

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { FlashStyle } from "@cursorless/common";
22
import { RangeUpdater } from "../core/updateSelections/RangeUpdater";
33
import { performEditsAndUpdateRanges } from "../core/updateSelections/updateSelections";
44
import { ModifierStageFactory } from "../processTargets/ModifierStageFactory";
5-
import { containingSurroundingPairIfUntypedModifier } from "../processTargets/modifiers/commonContainingScopeIfUntypedModifiers";
65
import { ide } from "../singletons/ide.singleton";
76
import { Target } from "../typings/target.types";
87
import {
@@ -11,12 +10,11 @@ import {
1110
runOnTargetsForEachEditor,
1211
} from "../util/targetUtils";
1312
import { ActionReturnValue } from "./actions.types";
13+
import { getContainingSurroundingPairIfNoBoundaryStage } from "../processTargets/modifiers/InteriorStage";
1414

1515
export default class Rewrap {
1616
getFinalStages = () => [
17-
this.modifierStageFactory.create(
18-
containingSurroundingPairIfUntypedModifier,
19-
),
17+
getContainingSurroundingPairIfNoBoundaryStage(this.modifierStageFactory),
2018
];
2119

2220
constructor(
@@ -32,7 +30,7 @@ export default class Rewrap {
3230
right: string,
3331
): Promise<ActionReturnValue> {
3432
const boundaryTargets = targets.flatMap((target) => {
35-
const boundary = target.getBoundaryStrict();
33+
const boundary = target.getBoundary()!;
3634

3735
if (boundary.length !== 2) {
3836
throw Error("Target must have an opening and closing delimiter");

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,23 @@ export class ModifyIfUntypedStage extends ConditionalModifierBaseStage {
5959
}
6060
}
6161

62+
/**
63+
* Runs {@link nestedModifier} if {@link modificationCondition} returns `true`.
64+
*/
65+
export class ModifyIfConditionStage extends ConditionalModifierBaseStage {
66+
constructor(
67+
modifierStageFactory: ModifierStageFactory,
68+
nestedModifier: Modifier,
69+
private modificationCondition: (target: Target) => boolean,
70+
) {
71+
super(modifierStageFactory, nestedModifier);
72+
}
73+
74+
protected shouldModify(target: Target): boolean {
75+
return this.modificationCondition(target);
76+
}
77+
}
78+
6279
/**
6380
* Runs {@link nestedModifier} if
6481
* - the target has no explicit scope type, ie if

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

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,32 +5,66 @@ import {
55
import { Target } from "../../typings/target.types";
66
import { ModifierStageFactory } from "../ModifierStageFactory";
77
import { ModifierStage } from "../PipelineStages.types";
8-
import { containingSurroundingPairIfUntypedModifier } from "./commonContainingScopeIfUntypedModifiers";
8+
import { ModifyIfConditionStage } from "./ConditionalModifierStages";
99

1010
export class InteriorOnlyStage implements ModifierStage {
11+
private containingSurroundingPairIfNoInteriorStage: ModifierStage;
12+
1113
constructor(
1214
private modifierStageFactory: ModifierStageFactory,
1315
private modifier: InteriorOnlyModifier,
14-
) {}
16+
) {
17+
this.containingSurroundingPairIfNoInteriorStage =
18+
getContainingSurroundingPairIfNoInteriorStage(this.modifierStageFactory);
19+
}
1520

1621
run(target: Target): Target[] {
17-
return this.modifierStageFactory
18-
.create(containingSurroundingPairIfUntypedModifier)
22+
return this.containingSurroundingPairIfNoInteriorStage
1923
.run(target)
20-
.flatMap((target) => target.getInteriorStrict());
24+
.flatMap((target) => target.getInterior()!);
2125
}
2226
}
2327

2428
export class ExcludeInteriorStage implements ModifierStage {
29+
private containingSurroundingPairIfNoBoundaryStage: ModifierStage;
30+
2531
constructor(
2632
private modifierStageFactory: ModifierStageFactory,
2733
private modifier: ExcludeInteriorModifier,
28-
) {}
34+
) {
35+
this.containingSurroundingPairIfNoBoundaryStage =
36+
getContainingSurroundingPairIfNoBoundaryStage(this.modifierStageFactory);
37+
}
2938

3039
run(target: Target): Target[] {
31-
return this.modifierStageFactory
32-
.create(containingSurroundingPairIfUntypedModifier)
40+
return this.containingSurroundingPairIfNoBoundaryStage
3341
.run(target)
34-
.flatMap((target) => target.getBoundaryStrict());
42+
.flatMap((target) => target.getBoundary()!);
3543
}
3644
}
45+
46+
function getContainingSurroundingPairIfNoInteriorStage(
47+
modifierStageFactory: ModifierStageFactory,
48+
): ModifierStage {
49+
return new ModifyIfConditionStage(
50+
modifierStageFactory,
51+
{
52+
type: "containingScope",
53+
scopeType: { type: "surroundingPair", delimiter: "any" },
54+
},
55+
(target) => target.getInterior() == null,
56+
);
57+
}
58+
59+
export function getContainingSurroundingPairIfNoBoundaryStage(
60+
modifierStageFactory: ModifierStageFactory,
61+
): ModifierStage {
62+
return new ModifyIfConditionStage(
63+
modifierStageFactory,
64+
{
65+
type: "containingScope",
66+
scopeType: { type: "surroundingPair", delimiter: "any" },
67+
},
68+
(target) => target.getBoundary() == null,
69+
);
70+
}

packages/cursorless-engine/src/processTargets/modifiers/ItemStage/getIterationScope.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export function getIterationScope(
2828
)
2929
) {
3030
return {
31-
range: surroundingTarget.getInteriorStrict()[0].contentRange,
31+
range: surroundingTarget.getInterior()[0].contentRange,
3232
boundary: getBoundary(surroundingTarget),
3333
};
3434
}
@@ -107,7 +107,7 @@ function useInteriorOfSurroundingTarget(
107107
}
108108

109109
function getBoundary(surroundingTarget: SurroundingPairTarget): [Range, Range] {
110-
return surroundingTarget.getBoundaryStrict().map((t) => t.contentRange) as [
110+
return surroundingTarget.getBoundary().map((t) => t.contentRange) as [
111111
Range,
112112
Range,
113113
];

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

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,6 @@ import { Modifier } from "@cursorless/common";
77
// eslint-disable-next-line @typescript-eslint/no-unused-vars, unused-imports/no-unused-imports
88
import type { Target } from "../../typings/target.types";
99

10-
/**
11-
* Expands the given target to the nearest containing surrounding pair if the
12-
* target has no explicit scope type, ie if {@link Target.hasExplicitScopeType}
13-
* is `false`.
14-
*/
15-
export const containingSurroundingPairIfUntypedModifier: Modifier = {
16-
type: "modifyIfUntyped",
17-
modifier: {
18-
type: "containingScope",
19-
scopeType: { type: "surroundingPair", delimiter: "any" },
20-
},
21-
};
22-
2310
/**
2411
* Expands the given target to the nearest containing line if the target has no
2512
* explicit scope type, ie if {@link Target.hasExplicitScopeType} is `false`.

0 commit comments

Comments
 (0)