Skip to content

Commit 663c5d3

Browse files
authored
keyboard: Add ability to make range past output of modifier (#2418)
- Fixes #2412 ## 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 33e2bc8 commit 663c5d3

File tree

5 files changed

+99
-97
lines changed

5 files changed

+99
-97
lines changed

packages/cursorless-vscode-e2e/src/suite/keyboard/basic.vscode.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,20 @@ const testCases: TestCase[] = [
8383
keySequence: ["da", "aw", "wp", "aw", "wp"],
8484
finalContent: "((a))\n",
8585
},
86+
{
87+
name: "modifier range",
88+
initialContent: "aaa bbb ccc ddd",
89+
// clear bat past its next token
90+
keySequence: ["db", "fk", "n", "st", "c"],
91+
finalContent: "aaa ddd",
92+
},
93+
{
94+
name: "modifier list",
95+
initialContent: "aaa bbb ccc ddd",
96+
// clear bat and its second next token
97+
keySequence: ["db", "fa", "2", "n", "st", "c"],
98+
finalContent: "aaa ccc ",
99+
},
86100
];
87101

88102
suite("Basic keyboard test", async function () {

packages/cursorless-vscode/src/keyboard/KeyboardCommandHandler.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import {
55
SimpleKeyboardActionDescriptor,
66
SpecificKeyboardActionDescriptor,
77
} from "./KeyboardActionType";
8-
import KeyboardCommandsTargeted from "./KeyboardCommandsTargeted";
8+
import KeyboardCommandsTargeted, {
9+
TargetingMode,
10+
} from "./KeyboardCommandsTargeted";
911
import { ModalVscodeCommandDescriptor } from "./TokenTypes";
1012
import { surroundingPairsDelimiters } from "@cursorless/cursorless-engine";
1113
import { isString } from "lodash";
@@ -89,8 +91,14 @@ export class KeyboardCommandHandler {
8991
);
9092
}
9193

92-
modifyTarget({ modifier }: { modifier: Modifier }) {
93-
this.targeted.targetModifier(modifier);
94+
modifyTarget({
95+
modifier,
96+
mode,
97+
}: {
98+
modifier: Modifier;
99+
mode?: TargetingMode;
100+
}) {
101+
this.targeted.targetModifier(modifier, mode);
94102
}
95103
}
96104

@@ -99,7 +107,7 @@ interface DecoratedMarkArg {
99107
color?: HatColor;
100108
shape?: HatShape;
101109
};
102-
mode: "replace" | "extend" | "append";
110+
mode: TargetingMode;
103111
}
104112

105113
interface WrapActionArg {

packages/cursorless-vscode/src/keyboard/KeyboardCommandsTargeted.ts

Lines changed: 69 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import KeyboardCommandsModal from "./KeyboardCommandsModal";
1414
import KeyboardHandler from "./KeyboardHandler";
1515
import { SimpleKeyboardActionDescriptor } from "./KeyboardActionType";
1616

17-
type TargetingMode = "replace" | "extend" | "append";
17+
export type TargetingMode = "replace" | "extend" | "append";
1818

1919
interface TargetDecoratedMarkArgument {
2020
color?: HatColor;
@@ -84,53 +84,56 @@ export default class KeyboardCommandsTargeted {
8484
return;
8585
}
8686

87-
let target: PartialTargetDescriptor = {
88-
type: "primitive",
89-
mark: {
90-
type: "decoratedSymbol",
91-
symbolColor: getStyleName(color, shape),
92-
character,
93-
},
94-
};
87+
return await setKeyboardTarget(
88+
this.applyTargetingMode(
89+
{
90+
type: "primitive",
91+
mark: {
92+
type: "decoratedSymbol",
93+
symbolColor: getStyleName(color, shape),
94+
character,
95+
},
96+
},
97+
mode,
98+
),
99+
);
100+
};
95101

102+
private applyTargetingMode(
103+
target: PartialPrimitiveTargetDescriptor,
104+
mode: TargetingMode,
105+
): PartialTargetDescriptor {
96106
switch (mode) {
97107
case "extend":
98-
target = {
108+
return {
99109
type: "range",
100-
anchor: {
101-
type: "primitive",
102-
mark: {
103-
type: "that",
104-
},
105-
},
110+
anchor: getKeyboardTarget(),
106111
active: target,
107112
excludeActive: false,
108113
excludeAnchor: false,
109114
};
110-
break;
111115
case "append":
112-
target = {
116+
return {
113117
type: "list",
114-
elements: [
115-
{
116-
type: "primitive",
117-
mark: {
118-
type: "that",
119-
},
120-
},
121-
target,
122-
],
118+
elements: [getKeyboardTarget(), target],
123119
};
124-
break;
125120
case "replace":
126-
break;
121+
return target;
127122
}
123+
}
128124

129-
return await executeCursorlessCommand({
130-
name: "private.setKeyboardTarget",
131-
target,
132-
});
133-
};
125+
/**
126+
* Applies {@link modifier} to the current target
127+
* @param param0 Describes the desired modifier
128+
* @returns A promise that resolves to the result of the cursorless command
129+
*/
130+
targetModifier = async (
131+
modifier: Modifier,
132+
mode: TargetingMode = "replace",
133+
) =>
134+
await setKeyboardTarget(
135+
this.applyTargetingMode(getKeyboardTarget(modifier), mode),
136+
);
134137

135138
/**
136139
* Expands the current target to the containing {@link scopeType}
@@ -141,37 +144,9 @@ export default class KeyboardCommandsTargeted {
141144
scopeType,
142145
type = "containingScope",
143146
}: ModifyTargetContainingScopeArgument) =>
144-
await executeCursorlessCommand({
145-
name: "private.setKeyboardTarget",
146-
target: {
147-
type: "primitive",
148-
modifiers: [
149-
{
150-
type,
151-
scopeType,
152-
},
153-
],
154-
mark: {
155-
type: "keyboard",
156-
},
157-
},
158-
});
159-
160-
/**
161-
* Applies {@link modifier} to the current target
162-
* @param param0 Describes the desired modifier
163-
* @returns A promise that resolves to the result of the cursorless command
164-
*/
165-
targetModifier = async (modifier: Modifier) =>
166-
await executeCursorlessCommand({
167-
name: "private.setKeyboardTarget",
168-
target: {
169-
type: "primitive",
170-
modifiers: [modifier],
171-
mark: {
172-
type: "keyboard",
173-
},
174-
},
147+
await this.targetModifier({
148+
type,
149+
scopeType,
175150
});
176151

177152
/**
@@ -249,12 +224,7 @@ export default class KeyboardCommandsTargeted {
249224
) => ActionDescriptor,
250225
{ exitCursorlessMode }: PerformActionOpts,
251226
) => {
252-
const action = constructActionPayload({
253-
type: "primitive",
254-
mark: {
255-
type: "keyboard",
256-
},
257-
});
227+
const action = constructActionPayload(getKeyboardTarget());
258228
const returnValue = await executeCursorlessCommand(action);
259229

260230
if (exitCursorlessMode) {
@@ -264,13 +234,10 @@ export default class KeyboardCommandsTargeted {
264234
} else {
265235
// If we're not exiting cursorless mode, preserve the keyboard mark
266236
// FIXME: Better to just not clobber the keyboard mark on each action?
267-
await executeCursorlessCommand({
268-
name: "private.setKeyboardTarget",
269-
target: {
270-
type: "primitive",
271-
mark: {
272-
type: "that",
273-
},
237+
await setKeyboardTarget({
238+
type: "primitive",
239+
mark: {
240+
type: "that",
274241
},
275242
});
276243
}
@@ -295,16 +262,9 @@ export default class KeyboardCommandsTargeted {
295262
exitCursorlessMode,
296263
}: VscodeCommandOnTargetOptions = {},
297264
) => {
298-
const target: PartialPrimitiveTargetDescriptor = {
299-
type: "primitive",
300-
mark: {
301-
type: "keyboard",
302-
},
303-
};
304-
305265
const returnValue = await executeCursorlessCommand({
306266
name: "executeCommand",
307-
target,
267+
target: getKeyboardTarget(),
308268
commandId,
309269
options: {
310270
restoreSelection: !keepChangedSelection,
@@ -327,13 +287,10 @@ export default class KeyboardCommandsTargeted {
327287
* @returns A promise that resolves to the result of the cursorless command
328288
*/
329289
targetSelection = () =>
330-
executeCursorlessCommand({
331-
name: "private.setKeyboardTarget",
332-
target: {
333-
type: "primitive",
334-
mark: {
335-
type: "cursor",
336-
},
290+
setKeyboardTarget({
291+
type: "primitive",
292+
mark: {
293+
type: "cursor",
337294
},
338295
});
339296

@@ -367,6 +324,25 @@ interface VscodeCommandOnTargetOptions {
367324
exitCursorlessMode?: boolean;
368325
}
369326

327+
function setKeyboardTarget(target: PartialTargetDescriptor) {
328+
return executeCursorlessCommand({
329+
name: "private.setKeyboardTarget",
330+
target,
331+
});
332+
}
333+
334+
function getKeyboardTarget(
335+
...modifiers: Modifier[]
336+
): PartialPrimitiveTargetDescriptor {
337+
return {
338+
type: "primitive",
339+
modifiers: modifiers.length > 0 ? modifiers : undefined,
340+
mark: {
341+
type: "keyboard",
342+
},
343+
};
344+
}
345+
370346
function executeCursorlessCommand(action: ActionDescriptor) {
371347
return runCursorlessCommand({
372348
action,

packages/cursorless-vscode/src/keyboard/grammar/generated/grammar.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ const grammar: Grammar = {
6565
command("targetDecoratedMark", { decoratedMark: $1, mode: "append" })
6666
},
6767
{"name": "main", "symbols": ["modifier"], "postprocess": command("modifyTarget", { modifier: $0 })},
68+
{"name": "main", "symbols": [(keyboardLexer.has("makeRange") ? {type: "makeRange"} : makeRange), "modifier"], "postprocess": command("modifyTarget", { modifier: $1, mode: "extend" })},
69+
{"name": "main", "symbols": [(keyboardLexer.has("makeList") ? {type: "makeList"} : makeList), "modifier"], "postprocess": command("modifyTarget", { modifier: $1, mode: "append" })},
6870
{"name": "main", "symbols": [(keyboardLexer.has("simpleAction") ? {type: "simpleAction"} : simpleAction)], "postprocess": command("performSimpleActionOnTarget", ["actionDescriptor"])},
6971
{"name": "main", "symbols": [(keyboardLexer.has("wrap") ? {type: "wrap"} : wrap), (keyboardLexer.has("pairedDelimiter") ? {type: "pairedDelimiter"} : pairedDelimiter)], "postprocess":
7072
command("performWrapActionOnTarget", ["actionDescriptor", "delimiter"])

packages/cursorless-vscode/src/keyboard/grammar/grammar.ne

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ main -> %makeList decoratedMark {%
2929
# --------------------------- Modifier --------------------------
3030

3131
main -> modifier {% command("modifyTarget", { modifier: $0 }) %}
32+
main -> %makeRange modifier {% command("modifyTarget", { modifier: $1, mode: "extend" }) %}
33+
main -> %makeList modifier {% command("modifyTarget", { modifier: $1, mode: "append" }) %}
3234

3335
# --------------------------- Actions --------------------------
3436

0 commit comments

Comments
 (0)