Skip to content

Commit 328a934

Browse files
Change implementation of default bounded line for head/tail modifiers (#2799)
Today's implementation is too quirky with its single versus multiline behavior. I have multiple times gotten an unexpected behavior. ## Checklist - [x] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [x] 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 --------- Co-authored-by: Phil Cohen <[email protected]>
1 parent 2c19566 commit 328a934

File tree

5 files changed

+102
-34
lines changed

5 files changed

+102
-34
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: change head
5+
action:
6+
name: clearAndSetSelection
7+
target:
8+
type: primitive
9+
modifiers:
10+
- {type: extendThroughStartOf}
11+
usePrePhraseSnapshot: false
12+
initialState:
13+
documentContents: |-
14+
(aaa
15+
)
16+
selections:
17+
- anchor: {line: 0, character: 4}
18+
active: {line: 0, character: 4}
19+
marks: {}
20+
finalState:
21+
documentContents: |-
22+
(
23+
)
24+
selections:
25+
- anchor: {line: 0, character: 1}
26+
active: {line: 0, character: 1}

packages/common/src/types/command/PartialTargetDescriptor.types.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -253,8 +253,6 @@ export interface SurroundingPairScopeType {
253253
export interface SurroundingPairInteriorScopeType {
254254
type: "surroundingPairInterior";
255255
delimiter: SurroundingPairName;
256-
// If true don't yield multiline pairs
257-
requireSingleLine?: boolean;
258256
}
259257

260258
export interface OneOfScopeType {
Lines changed: 75 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import type { HeadModifier, Modifier, TailModifier } from "@cursorless/common";
1+
import {
2+
NoContainingScopeError,
3+
type HeadModifier,
4+
type ScopeType,
5+
type TailModifier,
6+
} from "@cursorless/common";
27
import type { Target } from "../../typings/target.types";
38
import type { ModifierStageFactory } from "../ModifierStageFactory";
49
import type { ModifierStage } from "../PipelineStages.types";
510
import {
611
getModifierStagesFromTargetModifiers,
712
processModifierStages,
813
} from "../TargetPipelineRunner";
9-
import { HeadTailTarget } from "../targets";
14+
import { HeadTailTarget, PlainTarget } from "../targets";
1015

1116
export class HeadTailStage implements ModifierStage {
1217
constructor(
@@ -15,34 +20,11 @@ export class HeadTailStage implements ModifierStage {
1520
) {}
1621

1722
run(target: Target): Target[] {
18-
const modifiers: Modifier[] = this.modifier.modifiers ?? [
19-
{
20-
type: "containingScope",
21-
scopeType: {
22-
type: "oneOf",
23-
scopeTypes: [
24-
{
25-
type: "line",
26-
},
27-
{
28-
type: "surroundingPairInterior",
29-
delimiter: "any",
30-
requireSingleLine: true,
31-
},
32-
],
33-
},
34-
},
35-
];
36-
37-
const modifierStages = getModifierStagesFromTargetModifiers(
38-
this.modifierStageFactory,
39-
modifiers,
40-
);
23+
const modifierStages = this.getModifierStages();
4124
const modifiedTargets = processModifierStages(modifierStages, [target]);
25+
const isHead = this.modifier.type === "extendThroughStartOf";
4226

4327
return modifiedTargets.map((modifiedTarget) => {
44-
const isHead = this.modifier.type === "extendThroughStartOf";
45-
4628
return new HeadTailTarget({
4729
editor: target.editor,
4830
isReversed: isHead,
@@ -52,4 +34,70 @@ export class HeadTailStage implements ModifierStage {
5234
});
5335
});
5436
}
37+
38+
private getModifierStages(): ModifierStage[] {
39+
if (this.modifier.modifiers != null) {
40+
return getModifierStagesFromTargetModifiers(
41+
this.modifierStageFactory,
42+
this.modifier.modifiers,
43+
);
44+
}
45+
46+
return [new BoundedLineStage(this.modifierStageFactory, this.modifier)];
47+
}
48+
}
49+
50+
class BoundedLineStage implements ModifierStage {
51+
constructor(
52+
private modifierStageFactory: ModifierStageFactory,
53+
private modifier: HeadModifier | TailModifier,
54+
) {}
55+
56+
run(target: Target): Target[] {
57+
const line = this.getContainingLine(target);
58+
const pairInterior = this.getContainingPairInterior(target);
59+
60+
const intersection =
61+
pairInterior != null
62+
? line.contentRange.intersection(pairInterior.contentRange)
63+
: null;
64+
65+
if (intersection == null || intersection.isEmpty) {
66+
return [line];
67+
}
68+
69+
return [
70+
new PlainTarget({
71+
editor: target.editor,
72+
isReversed: target.isReversed,
73+
contentRange: intersection,
74+
}),
75+
];
76+
}
77+
78+
private getContainingPairInterior(target: Target): Target | undefined {
79+
try {
80+
return this.getContaining(target, {
81+
type: "surroundingPairInterior",
82+
delimiter: "any",
83+
})[0];
84+
} catch (error) {
85+
if (error instanceof NoContainingScopeError) {
86+
return undefined;
87+
}
88+
throw error;
89+
}
90+
}
91+
92+
private getContainingLine(target: Target): Target {
93+
return this.getContaining(target, {
94+
type: "line",
95+
})[0];
96+
}
97+
98+
private getContaining(target: Target, scopeType: ScopeType): Target[] {
99+
return this.modifierStageFactory
100+
.create({ type: "containingScope", scopeType })
101+
.run(target);
102+
}
55103
}

packages/cursorless-engine/src/processTargets/modifiers/scopeHandlers/SurroundingPairScopeHandler/SurroundingPairInteriorScopeHandler.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,6 @@ export class SurroundingPairInteriorScopeHandler extends BaseScopeHandler {
4545
);
4646

4747
for (const scope of scopes) {
48-
if (this.scopeType.requireSingleLine && !scope.domain.isSingleLine) {
49-
continue;
50-
}
51-
5248
yield {
5349
editor,
5450
domain: scope.domain,

packages/cursorless-org-docs/src/docs/user/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ The modifiers `"head"` and `"tail"` can be used to expand a target through the b
314314
- `"take head air"`: selects the mark through to start of the line
315315
- `"take tail air"`: selects the mark through to the end of the line
316316

317-
When inside a single-line surrounding pair (eg parentheses, brackets, etc) the head/tail modifier will only expand to the interior of that pair instead of the whole line. You can explicitly say `"head line"` or `"tail line"` to get the line behavior.
317+
When inside a surrounding pair (eg parentheses, brackets, etc) the head/tail modifier will only expand to the interior of that pair instead of the whole line. You can explicitly say `"head line"` or `"tail line"` to get the line behavior.
318318

319319
When followed by a modifier, they will expand their input to the start or end of the given modifier range. For example:
320320

0 commit comments

Comments
 (0)