Skip to content

Commit 364ef93

Browse files
Add next/previous support to custom command (#2969)
`"take next token"` `"chuck previous instance"` Fixes #2966
1 parent a525b59 commit 364ef93

File tree

7 files changed

+69
-21
lines changed

7 files changed

+69
-21
lines changed

cursorless-talon-dev/src/cursorless_test.talon

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ test api extract decorated marks <user.cursorless_target>:
3232
test api alternate highlight nothing:
3333
user.private_cursorless_test_alternate_highlight_nothing()
3434

35-
test api parsed: user.cursorless_x_custom_command("chuck block")
35+
test api parsed chuck block: user.cursorless_x_custom_command("chuck block")
36+
test api parsed take next token: user.cursorless_x_custom_command("take next token")
37+
test api parsed change next instance: user.cursorless_x_custom_command("change next instance")
3638
test api parsed <user.cursorless_target>:
3739
user.cursorless_x_custom_command("chuck block <target>", cursorless_target)
3840
test api parsed <user.cursorless_target> plus <user.cursorless_target>:

packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ function id(d: any[]): any { return d[0]; }
66
declare var simpleActionName: any;
77
declare var bringMove: any;
88
declare var insertionMode: any;
9+
declare var direction: any;
910
declare var simpleScopeTypeType: any;
1011
declare var pairedDelimiter: any;
1112
declare var simpleMarkType: any;
@@ -16,9 +17,10 @@ import { lexer } from "../lexer";
1617
import {
1718
bringMoveActionDescriptor,
1819
containingScopeModifier,
19-
partialPrimitiveTargetDescriptor,
2020
createPlaceholderTarget,
21+
partialPrimitiveTargetDescriptor,
2122
primitiveDestinationDescriptor,
23+
relativeScopeModifier,
2224
simpleActionDescriptor,
2325
simplePartialMark,
2426
simpleScopeType,
@@ -80,12 +82,12 @@ const grammar: Grammar = {
8082
{"name": "primitiveTarget", "symbols": ["primitiveTarget$ebnf$2", "mark"], "postprocess":
8183
([modifiers, mark]) => partialPrimitiveTargetDescriptor(modifiers, mark)
8284
},
83-
{"name": "modifier", "symbols": ["containingScopeModifier"], "postprocess":
84-
([containingScopeModifier]) => containingScopeModifier
85-
},
86-
{"name": "containingScopeModifier", "symbols": ["scopeType"], "postprocess":
85+
{"name": "modifier", "symbols": ["scopeType"], "postprocess":
8786
([scopeType]) => containingScopeModifier(scopeType)
8887
},
88+
{"name": "modifier", "symbols": [(lexer.has("direction") ? {type: "direction"} : direction), "scopeType"], "postprocess":
89+
([direction, scopeType]) => relativeScopeModifier(scopeType, direction)
90+
},
8991
{"name": "scopeType", "symbols": [(lexer.has("simpleScopeTypeType") ? {type: "simpleScopeTypeType"} : simpleScopeTypeType)], "postprocess":
9092
([simpleScopeTypeType]) => simpleScopeType(simpleScopeTypeType)
9193
},

packages/cursorless-engine/src/customCommandGrammar/grammar.ne

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ import { lexer } from "../lexer";
55
import {
66
bringMoveActionDescriptor,
77
containingScopeModifier,
8-
partialPrimitiveTargetDescriptor,
98
createPlaceholderTarget,
9+
partialPrimitiveTargetDescriptor,
1010
primitiveDestinationDescriptor,
11+
relativeScopeModifier,
1112
simpleActionDescriptor,
1213
simplePartialMark,
1314
simpleScopeType,
@@ -54,12 +55,12 @@ primitiveTarget -> modifier:+ mark {%
5455

5556
# --------------------------- Modifiers ---------------------------
5657

57-
modifier -> containingScopeModifier {%
58-
([containingScopeModifier]) => containingScopeModifier
58+
modifier -> scopeType {%
59+
([scopeType]) => containingScopeModifier(scopeType)
5960
%}
6061

61-
containingScopeModifier -> scopeType {%
62-
([scopeType]) => containingScopeModifier(scopeType)
62+
modifier -> %direction scopeType {%
63+
([direction, scopeType]) => relativeScopeModifier(scopeType, direction)
6364
%}
6465

6566
# --------------------------- Scope types ---------------------------

packages/cursorless-engine/src/customCommandGrammar/grammarUtil.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import type {
22
BringMoveActionDescriptor,
33
DestinationDescriptor,
4+
Direction,
45
InsertionMode,
56
PartialListTargetDescriptor,
67
PartialRangeTargetDescriptor,
78
PartialTargetMark,
89
PrimitiveDestinationDescriptor,
10+
RelativeScopeModifier,
911
} from "@cursorless/common";
1012
import {
1113
type ContainingScopeModifier,
@@ -75,6 +77,19 @@ export function containingScopeModifier(
7577
};
7678
}
7779

80+
export function relativeScopeModifier(
81+
scopeType: ScopeType,
82+
direction: Direction,
83+
): RelativeScopeModifier {
84+
return {
85+
type: "relativeScope",
86+
scopeType,
87+
offset: 1,
88+
length: 1,
89+
direction,
90+
};
91+
}
92+
7893
export function simpleScopeType(type: SimpleScopeTypeType): SimpleScopeType {
7994
return { type };
8095
}

packages/cursorless-engine/src/customCommandGrammar/lexer.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,22 @@ for (const [mark, spokenForm] of Object.entries(marks)) {
8989
}
9090
}
9191

92+
defaultSpokenFormMap.modifierExtra.next.spokenForms.forEach((spokenForm) => {
93+
tokens[spokenForm] = {
94+
type: "direction",
95+
value: "forward",
96+
};
97+
});
98+
99+
defaultSpokenFormMap.modifierExtra.previous.spokenForms.forEach(
100+
(spokenForm) => {
101+
tokens[spokenForm] = {
102+
type: "direction",
103+
value: "backward",
104+
};
105+
},
106+
);
107+
92108
export const lexer = new CommandLexer({
93109
ws: /[ \t]+/,
94110
placeholderTarget: {

packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,14 @@ function getTextAction(options: GetTextActionOptions): ActionDescriptor {
120120
};
121121
}
122122

123-
const parsedActionNoTargets: ActionDescriptor = {
124-
name: "parsed",
125-
content: "chuck block",
126-
arguments: [],
127-
};
123+
function parsedAction(content: string): ActionDescriptor {
124+
return {
125+
name: "parsed",
126+
content,
127+
arguments: [],
128+
};
129+
}
130+
128131
const parsedActionAir: ActionDescriptor = {
129132
name: "parsed",
130133
content: "chuck block <target>",
@@ -221,7 +224,15 @@ export const talonApiFixture = [
221224
"test api alternate highlight nothing",
222225
alternateHighlightNothingAction,
223226
),
224-
spokenFormTest("test api parsed", parsedActionNoTargets),
227+
spokenFormTest("test api parsed chuck block", parsedAction("chuck block")),
228+
spokenFormTest(
229+
"test api parsed take next token",
230+
parsedAction("take next token"),
231+
),
232+
spokenFormTest(
233+
"test api parsed change next instance",
234+
parsedAction("change next instance"),
235+
),
225236
spokenFormTest("test api parsed air and bat", parsedActionAir),
226237
spokenFormTest("test api parsed air plus bat", parsedActionAirPlusBat),
227238
];

packages/cursorless-engine/src/testUtil/TalonRepl.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import * as os from "node:os";
1+
import { isWindows } from "@cursorless/node-common";
22
import * as childProcess from "node:child_process";
3+
import * as os from "node:os";
34

45
const MAX_OUTPUT_TO_EAT = 20;
56

@@ -16,7 +17,7 @@ export class TalonRepl {
1617
start(): Promise<void> {
1718
return new Promise<void>((resolve, reject) => {
1819
const path = getReplPath();
19-
this.child = childProcess.spawn(path);
20+
this.child = childProcess.spawn(path, { shell: true });
2021

2122
if (!this.child.stdin) {
2223
reject("stdin is null");
@@ -86,7 +87,7 @@ export class TalonRepl {
8687
}
8788

8889
function getReplPath() {
89-
return os.platform() === "win32"
90-
? `${os.homedir()}\\AppData\\Roaming\\talon\\venv\\3.11\\Scripts\\repl.bat`
90+
return isWindows()
91+
? `${os.homedir()}\\AppData\\Roaming\\talon\\venv\\3.13\\Scripts\\repl.bat`
9192
: `${os.homedir()}/.talon/.venv/bin/repl`;
9293
}

0 commit comments

Comments
 (0)