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/customCommandGrammar/generated/grammar.ts b/packages/cursorless-engine/src/customCommandGrammar/generated/grammar.ts index 4b7062b59e..49c2df7f61 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; @@ -16,9 +17,10 @@ import { lexer } from "../lexer"; import { bringMoveActionDescriptor, containingScopeModifier, - partialPrimitiveTargetDescriptor, createPlaceholderTarget, + partialPrimitiveTargetDescriptor, primitiveDestinationDescriptor, + relativeScopeModifier, simpleActionDescriptor, simplePartialMark, simpleScopeType, @@ -80,12 +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": "containingScopeModifier", "symbols": ["scopeType"], "postprocess": + {"name": "modifier", "symbols": ["scopeType"], "postprocess": ([scopeType]) => containingScopeModifier(scopeType) }, + {"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": ([simpleScopeTypeType]) => simpleScopeType(simpleScopeTypeType) }, diff --git a/packages/cursorless-engine/src/customCommandGrammar/grammar.ne b/packages/cursorless-engine/src/customCommandGrammar/grammar.ne index 7db0e84d7a..554e9cc5a7 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, @@ -54,12 +55,12 @@ primitiveTarget -> modifier:+ mark {% # --------------------------- Modifiers --------------------------- -modifier -> containingScopeModifier {% - ([containingScopeModifier]) => containingScopeModifier +modifier -> scopeType {% + ([scopeType]) => containingScopeModifier(scopeType) %} -containingScopeModifier -> scopeType {% - ([scopeType]) => containingScopeModifier(scopeType) +modifier -> %direction scopeType {% + ([direction, scopeType]) => relativeScopeModifier(scopeType, direction) %} # --------------------------- Scope types --------------------------- 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 }; } 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: { 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), ]; 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`; }