From 61e5e3adaf2721d01abcbe91e16a03404f08dd6a Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 10 Jun 2025 12:00:51 +0200 Subject: [PATCH 1/6] Started working on custom relative command --- .../customCommandGrammar/generated/grammar.ts | 11 ++++++++++- .../src/customCommandGrammar/grammar.ne | 17 ++++++++++++++++- .../src/customCommandGrammar/grammarUtil.ts | 15 +++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts b/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts index 4b7062b59e..eb7b3cde9d 100644 --- a/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts +++ b/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts @@ -16,9 +16,10 @@ import { lexer } from "../lexer"; import { bringMoveActionDescriptor, containingScopeModifier, - partialPrimitiveTargetDescriptor, createPlaceholderTarget, + partialPrimitiveTargetDescriptor, primitiveDestinationDescriptor, + relativeScopeModifier, simpleActionDescriptor, simplePartialMark, simpleScopeType, @@ -83,15 +84,23 @@ const grammar: Grammar = { {"name": "modifier", "symbols": ["containingScopeModifier"], "postprocess": ([containingScopeModifier]) => containingScopeModifier }, + {"name": "modifier", "symbols": ["relativeScopeModifier"], "postprocess": + ([relativeScopeModifier]) => relativeScopeModifier + }, {"name": "containingScopeModifier", "symbols": ["scopeType"], "postprocess": ([scopeType]) => containingScopeModifier(scopeType) }, + {"name": "relativeScopeModifier", "symbols": ["direction", "scopeType"], "postprocess": + ([direction, scopeType]) => relativeScopeModifier(scopeType, direction) + }, {"name": "scopeType", "symbols": [(lexer.has("simpleScopeTypeType") ? {type: "simpleScopeTypeType"} : simpleScopeTypeType)], "postprocess": ([simpleScopeTypeType]) => simpleScopeType(simpleScopeTypeType) }, {"name": "scopeType", "symbols": [(lexer.has("pairedDelimiter") ? {type: "pairedDelimiter"} : pairedDelimiter)], "postprocess": ([delimiter]) => surroundingPairScopeType(delimiter) }, + {"name": "direction", "symbols": [{"literal":"next"}], "postprocess": ([]) => "forward"}, + {"name": "direction", "symbols": [{"literal":"previous"}], "postprocess": ([]) => "backward"}, {"name": "mark", "symbols": [(lexer.has("simpleMarkType") ? {type: "simpleMarkType"} : simpleMarkType)], "postprocess": ([simpleMarkType]) => simplePartialMark(simpleMarkType) }, diff --git a/packages/cursorless-engine/src/customCommandGrammar/grammar.ne b/packages/cursorless-engine/src/customCommandGrammar/grammar.ne index 7db0e84d7a..599fa06bcb 100644 --- a/packages/cursorless-engine/src/customCommandGrammar/grammar.ne +++ b/packages/cursorless-engine/src/customCommandGrammar/grammar.ne @@ -5,9 +5,10 @@ import { lexer } from "../lexer"; import { bringMoveActionDescriptor, containingScopeModifier, - partialPrimitiveTargetDescriptor, createPlaceholderTarget, + partialPrimitiveTargetDescriptor, primitiveDestinationDescriptor, + relativeScopeModifier, simpleActionDescriptor, simplePartialMark, simpleScopeType, @@ -58,10 +59,18 @@ modifier -> containingScopeModifier {% ([containingScopeModifier]) => containingScopeModifier %} +modifier -> relativeScopeModifier {% + ([relativeScopeModifier]) => relativeScopeModifier +%} + containingScopeModifier -> scopeType {% ([scopeType]) => containingScopeModifier(scopeType) %} +relativeScopeModifier -> direction scopeType {% + ([direction, scopeType]) => relativeScopeModifier(scopeType, direction) +%} + # --------------------------- Scope types --------------------------- scopeType -> %simpleScopeTypeType {% @@ -72,6 +81,12 @@ scopeType -> %pairedDelimiter {% ([delimiter]) => surroundingPairScopeType(delimiter) %} +# --------------------------- Directions --------------------------- + +direction -> "next" {% ([]) => "forward" %} + +direction -> "previous" {% ([]) => "backward" %} + # --------------------------- Marks --------------------------- mark -> %simpleMarkType {% diff --git a/packages/cursorless-engine/src/customCommandGrammar/grammarUtil.ts b/packages/cursorless-engine/src/customCommandGrammar/grammarUtil.ts index 58b7798b79..87af64d1a6 100644 --- a/packages/cursorless-engine/src/customCommandGrammar/grammarUtil.ts +++ b/packages/cursorless-engine/src/customCommandGrammar/grammarUtil.ts @@ -1,11 +1,13 @@ import type { BringMoveActionDescriptor, DestinationDescriptor, + Direction, InsertionMode, PartialListTargetDescriptor, PartialRangeTargetDescriptor, PartialTargetMark, PrimitiveDestinationDescriptor, + RelativeScopeModifier, } from "@cursorless/common"; import { type ContainingScopeModifier, @@ -75,6 +77,19 @@ export function containingScopeModifier( }; } +export function relativeScopeModifier( + scopeType: ScopeType, + direction: Direction, +): RelativeScopeModifier { + return { + type: "relativeScope", + scopeType, + offset: 1, + length: 1, + direction, + }; +} + export function simpleScopeType(type: SimpleScopeTypeType): SimpleScopeType { return { type }; } From e8ef58138b6d4c2a079662cc19d66b2840892119 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 10 Jun 2025 14:57:36 +0200 Subject: [PATCH 2/6] Add directions --- .../customCommandGrammar/generated/grammar.ts | 13 ++++--------- .../src/customCommandGrammar/grammar.ne | 15 +++------------ .../src/customCommandGrammar/lexer.ts | 16 ++++++++++++++++ 3 files changed, 23 insertions(+), 21 deletions(-) diff --git a/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts b/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts index eb7b3cde9d..6451b9c4d2 100644 --- a/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts +++ b/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts @@ -6,6 +6,7 @@ function id(d: any[]): any { return d[0]; } declare var simpleActionName: any; declare var bringMove: any; declare var insertionMode: any; +declare var direction: any; declare var simpleScopeTypeType: any; declare var pairedDelimiter: any; declare var simpleMarkType: any; @@ -81,16 +82,12 @@ const grammar: Grammar = { {"name": "primitiveTarget", "symbols": ["primitiveTarget$ebnf$2", "mark"], "postprocess": ([modifiers, mark]) => partialPrimitiveTargetDescriptor(modifiers, mark) }, - {"name": "modifier", "symbols": ["containingScopeModifier"], "postprocess": - ([containingScopeModifier]) => containingScopeModifier - }, - {"name": "modifier", "symbols": ["relativeScopeModifier"], "postprocess": - ([relativeScopeModifier]) => relativeScopeModifier - }, + {"name": "modifier", "symbols": ["containingScopeModifier"], "postprocess": id}, + {"name": "modifier", "symbols": ["relativeScopeModifier"], "postprocess": id}, {"name": "containingScopeModifier", "symbols": ["scopeType"], "postprocess": ([scopeType]) => containingScopeModifier(scopeType) }, - {"name": "relativeScopeModifier", "symbols": ["direction", "scopeType"], "postprocess": + {"name": "relativeScopeModifier", "symbols": [(lexer.has("direction") ? {type: "direction"} : direction), "scopeType"], "postprocess": ([direction, scopeType]) => relativeScopeModifier(scopeType, direction) }, {"name": "scopeType", "symbols": [(lexer.has("simpleScopeTypeType") ? {type: "simpleScopeTypeType"} : simpleScopeTypeType)], "postprocess": @@ -99,8 +96,6 @@ const grammar: Grammar = { {"name": "scopeType", "symbols": [(lexer.has("pairedDelimiter") ? {type: "pairedDelimiter"} : pairedDelimiter)], "postprocess": ([delimiter]) => surroundingPairScopeType(delimiter) }, - {"name": "direction", "symbols": [{"literal":"next"}], "postprocess": ([]) => "forward"}, - {"name": "direction", "symbols": [{"literal":"previous"}], "postprocess": ([]) => "backward"}, {"name": "mark", "symbols": [(lexer.has("simpleMarkType") ? {type: "simpleMarkType"} : simpleMarkType)], "postprocess": ([simpleMarkType]) => simplePartialMark(simpleMarkType) }, diff --git a/packages/cursorless-engine/src/customCommandGrammar/grammar.ne b/packages/cursorless-engine/src/customCommandGrammar/grammar.ne index 599fa06bcb..1e48a37f35 100644 --- a/packages/cursorless-engine/src/customCommandGrammar/grammar.ne +++ b/packages/cursorless-engine/src/customCommandGrammar/grammar.ne @@ -55,19 +55,15 @@ primitiveTarget -> modifier:+ mark {% # --------------------------- Modifiers --------------------------- -modifier -> containingScopeModifier {% - ([containingScopeModifier]) => containingScopeModifier -%} +modifier -> containingScopeModifier {% id %} -modifier -> relativeScopeModifier {% - ([relativeScopeModifier]) => relativeScopeModifier -%} +modifier -> relativeScopeModifier {% id %} containingScopeModifier -> scopeType {% ([scopeType]) => containingScopeModifier(scopeType) %} -relativeScopeModifier -> direction scopeType {% +relativeScopeModifier -> %direction scopeType {% ([direction, scopeType]) => relativeScopeModifier(scopeType, direction) %} @@ -81,11 +77,6 @@ scopeType -> %pairedDelimiter {% ([delimiter]) => surroundingPairScopeType(delimiter) %} -# --------------------------- Directions --------------------------- - -direction -> "next" {% ([]) => "forward" %} - -direction -> "previous" {% ([]) => "backward" %} # --------------------------- Marks --------------------------- diff --git a/packages/cursorless-engine/src/customCommandGrammar/lexer.ts b/packages/cursorless-engine/src/customCommandGrammar/lexer.ts index 72130db146..43d3a3c8e5 100644 --- a/packages/cursorless-engine/src/customCommandGrammar/lexer.ts +++ b/packages/cursorless-engine/src/customCommandGrammar/lexer.ts @@ -89,6 +89,22 @@ for (const [mark, spokenForm] of Object.entries(marks)) { } } +defaultSpokenFormMap.modifierExtra.next.spokenForms.forEach((spokenForm) => { + tokens[spokenForm] = { + type: "direction", + value: "forward", + }; +}); + +defaultSpokenFormMap.modifierExtra.previous.spokenForms.forEach( + (spokenForm) => { + tokens[spokenForm] = { + type: "direction", + value: "backward", + }; + }, +); + export const lexer = new CommandLexer({ ws: /[ \t]+/, placeholderTarget: { From 73b243784f6a25f2f94dc77c8a510e2181bfcba4 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 10 Jun 2025 15:49:50 +0200 Subject: [PATCH 3/6] Added more tests --- .../src/cursorless_test.talon | 4 +++- .../src/test/fixtures/talonApi.fixture.ts | 23 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/cursorless-talon-dev/src/cursorless_test.talon b/cursorless-talon-dev/src/cursorless_test.talon index 914f7d2aea..3035640ca9 100644 --- a/cursorless-talon-dev/src/cursorless_test.talon +++ b/cursorless-talon-dev/src/cursorless_test.talon @@ -32,7 +32,9 @@ test api extract decorated marks : test api alternate highlight nothing: user.private_cursorless_test_alternate_highlight_nothing() -test api parsed: user.cursorless_x_custom_command("chuck block") +test api parsed chuck block: user.cursorless_x_custom_command("chuck block") +test api parsed take next token: user.cursorless_x_custom_command("take next token") +test api parsed change next instance: user.cursorless_x_custom_command("change next instance") test api parsed : user.cursorless_x_custom_command("chuck block ", cursorless_target) test api parsed plus : diff --git a/packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts b/packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts index 71a8c17d57..640eb90e85 100644 --- a/packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts +++ b/packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts @@ -120,11 +120,14 @@ function getTextAction(options: GetTextActionOptions): ActionDescriptor { }; } -const parsedActionNoTargets: ActionDescriptor = { - name: "parsed", - content: "chuck block", - arguments: [], -}; +function parsedAction(content: string): ActionDescriptor { + return { + name: "parsed", + content, + arguments: [], + }; +} + const parsedActionAir: ActionDescriptor = { name: "parsed", content: "chuck block ", @@ -221,7 +224,15 @@ export const talonApiFixture = [ "test api alternate highlight nothing", alternateHighlightNothingAction, ), - spokenFormTest("test api parsed", parsedActionNoTargets), + spokenFormTest("test api parsed chuck block", parsedAction("chuck block")), + spokenFormTest( + "test api parsed take next token", + parsedAction("take next token"), + ), + spokenFormTest( + "test api parsed change next instance", + parsedAction("change next instance"), + ), spokenFormTest("test api parsed air and bat", parsedActionAir), spokenFormTest("test api parsed air plus bat", parsedActionAirPlusBat), ]; From 1e31ac93a1d52737def25f9be2c82569acd30fa8 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 10 Jun 2025 15:52:16 +0200 Subject: [PATCH 4/6] Clean up --- .../src/customCommandGrammar/generated/grammar.ts | 6 ++---- .../cursorless-engine/src/customCommandGrammar/grammar.ne | 8 ++------ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts b/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts index 6451b9c4d2..49c2df7f61 100644 --- a/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts +++ b/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts @@ -82,12 +82,10 @@ const grammar: Grammar = { {"name": "primitiveTarget", "symbols": ["primitiveTarget$ebnf$2", "mark"], "postprocess": ([modifiers, mark]) => partialPrimitiveTargetDescriptor(modifiers, mark) }, - {"name": "modifier", "symbols": ["containingScopeModifier"], "postprocess": id}, - {"name": "modifier", "symbols": ["relativeScopeModifier"], "postprocess": id}, - {"name": "containingScopeModifier", "symbols": ["scopeType"], "postprocess": + {"name": "modifier", "symbols": ["scopeType"], "postprocess": ([scopeType]) => containingScopeModifier(scopeType) }, - {"name": "relativeScopeModifier", "symbols": [(lexer.has("direction") ? {type: "direction"} : direction), "scopeType"], "postprocess": + {"name": "modifier", "symbols": [(lexer.has("direction") ? {type: "direction"} : direction), "scopeType"], "postprocess": ([direction, scopeType]) => relativeScopeModifier(scopeType, direction) }, {"name": "scopeType", "symbols": [(lexer.has("simpleScopeTypeType") ? {type: "simpleScopeTypeType"} : simpleScopeTypeType)], "postprocess": diff --git a/packages/cursorless-engine/src/customCommandGrammar/grammar.ne b/packages/cursorless-engine/src/customCommandGrammar/grammar.ne index 1e48a37f35..eb153c89ac 100644 --- a/packages/cursorless-engine/src/customCommandGrammar/grammar.ne +++ b/packages/cursorless-engine/src/customCommandGrammar/grammar.ne @@ -55,15 +55,11 @@ primitiveTarget -> modifier:+ mark {% # --------------------------- Modifiers --------------------------- -modifier -> containingScopeModifier {% id %} - -modifier -> relativeScopeModifier {% id %} - -containingScopeModifier -> scopeType {% +modifier -> scopeType {% ([scopeType]) => containingScopeModifier(scopeType) %} -relativeScopeModifier -> %direction scopeType {% +modifier -> %direction scopeType {% ([direction, scopeType]) => relativeScopeModifier(scopeType, direction) %} From e682c54a9f097414db26dfb4279039f9dae94458 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 10 Jun 2025 15:54:28 +0200 Subject: [PATCH 5/6] More clean up --- packages/cursorless-engine/src/customCommandGrammar/grammar.ne | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cursorless-engine/src/customCommandGrammar/grammar.ne b/packages/cursorless-engine/src/customCommandGrammar/grammar.ne index eb153c89ac..554e9cc5a7 100644 --- a/packages/cursorless-engine/src/customCommandGrammar/grammar.ne +++ b/packages/cursorless-engine/src/customCommandGrammar/grammar.ne @@ -73,7 +73,6 @@ scopeType -> %pairedDelimiter {% ([delimiter]) => surroundingPairScopeType(delimiter) %} - # --------------------------- Marks --------------------------- mark -> %simpleMarkType {% From f53a1f073e4fe2a21af3b67992c66ef6c649e8d5 Mon Sep 17 00:00:00 2001 From: Andreas Arvidsson Date: Tue, 10 Jun 2025 16:03:33 +0200 Subject: [PATCH 6/6] Fixed Talon grammar tests --- packages/cursorless-engine/src/testUtil/TalonRepl.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/cursorless-engine/src/testUtil/TalonRepl.ts b/packages/cursorless-engine/src/testUtil/TalonRepl.ts index bbb10e6bf4..03f2727235 100644 --- a/packages/cursorless-engine/src/testUtil/TalonRepl.ts +++ b/packages/cursorless-engine/src/testUtil/TalonRepl.ts @@ -1,5 +1,6 @@ -import * as os from "node:os"; +import { isWindows } from "@cursorless/node-common"; import * as childProcess from "node:child_process"; +import * as os from "node:os"; const MAX_OUTPUT_TO_EAT = 20; @@ -16,7 +17,7 @@ export class TalonRepl { start(): Promise { return new Promise((resolve, reject) => { const path = getReplPath(); - this.child = childProcess.spawn(path); + this.child = childProcess.spawn(path, { shell: true }); if (!this.child.stdin) { reject("stdin is null"); @@ -86,7 +87,7 @@ export class TalonRepl { } function getReplPath() { - return os.platform() === "win32" - ? `${os.homedir()}\\AppData\\Roaming\\talon\\venv\\3.11\\Scripts\\repl.bat` + return isWindows() + ? `${os.homedir()}\\AppData\\Roaming\\talon\\venv\\3.13\\Scripts\\repl.bat` : `${os.homedir()}/.talon/.venv/bin/repl`; }