Skip to content

Commit

Permalink
Support custom spoken forms for actions (#2334)
Browse files Browse the repository at this point in the history
## 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)
- [x] I have not broken the cheatsheet
- [x] I have run Talon-side tests
  • Loading branch information
pokey committed Jul 12, 2024
1 parent bc50059 commit 95ae15f
Show file tree
Hide file tree
Showing 10 changed files with 162 additions and 106 deletions.
7 changes: 7 additions & 0 deletions cursorless-talon/src/spoken_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from talon import app, fs

from .actions.actions import ACTION_LIST_NAMES
from .csv_overrides import (
SPOKEN_FORM_HEADER,
ListToSpokenForms,
Expand Down Expand Up @@ -70,6 +71,12 @@ def ret(filename: str, *args: P.args, **kwargs: P.kwargs) -> R:
"scope_type": "simpleScopeTypeType",
"glyph_scope_type": "complexScopeTypeType",
"custom_regex_scope_type": "customRegex",
**{
action_list_name: "action"
for action_list_name in ACTION_LIST_NAMES
if action_list_name != "custom_action"
},
"custom_action": "customAction",
}


Expand Down
3 changes: 3 additions & 0 deletions data/fixtures/recorded/actions/parseTreeFile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ command:
- type: containingScope
scopeType: {type: document}
usePrePhraseSnapshot: true
spokenFormError: >-
action with id private.showParseTree; this is a private spoken form currently
only for internal experimentation
initialState:
documentContents: const value = 2;
selections:
Expand Down
9 changes: 4 additions & 5 deletions packages/cursorless-engine/src/customCommandGrammar/lexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
simpleScopeTypeTypes,
surroundingPairNames,
} from "@cursorless/common";
import { actions } from "../generateSpokenForm/defaultSpokenForms/actions";
import { marks } from "../generateSpokenForm/defaultSpokenForms/marks";
import { defaultSpokenFormMap } from "../spokenForms/defaultSpokenFormMap";
import { connectives } from "../generateSpokenForm/defaultSpokenForms/connectives";
Expand All @@ -21,8 +20,8 @@ const tokens: Record<string, Token> = {};
// FIXME: Remove the duplication below?

for (const simpleActionName of simpleActionNames) {
const spokenForm = actions[simpleActionName];
if (spokenForm != null) {
const { spokenForms } = defaultSpokenFormMap.action[simpleActionName];
for (const spokenForm of spokenForms) {
tokens[spokenForm] = {
type: "simpleActionName",
value: simpleActionName,
Expand All @@ -36,8 +35,8 @@ const bringMoveActionNames: BringMoveActionDescriptor["name"][] = [
];

for (const bringMoveActionName of bringMoveActionNames) {
const spokenForm = actions[bringMoveActionName];
if (spokenForm != null) {
const { spokenForms } = defaultSpokenFormMap.action[bringMoveActionName];
for (const spokenForm of spokenForms) {
tokens[spokenForm] = {
type: "bringMove",
value: bringMoveActionName,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import assert from "node:assert";
import { CustomSpokenFormGeneratorImpl } from "./CustomSpokenFormGeneratorImpl";
import { asyncSafety } from "@cursorless/common";
import { LATEST_VERSION, asyncSafety } from "@cursorless/common";

suite("CustomSpokenFormGeneratorImpl", async function () {
test(
"glyph",
"basic",
asyncSafety(async () => {
const generator = new CustomSpokenFormGeneratorImpl({
async getSpokenFormEntries() {
Expand All @@ -14,22 +14,47 @@ suite("CustomSpokenFormGeneratorImpl", async function () {
id: "glyph",
spokenForms: ["foo"],
},
{
type: "action",
id: "setSelection",
spokenForms: ["bar"],
},
];
},
onDidChange: () => ({ dispose() {} }),
});

await generator.customSpokenFormsInitialized;

const spokenForm = generator.scopeTypeToSpokenForm({
type: "glyph",
character: "a",
});

assert.deepStrictEqual(spokenForm, {
type: "success",
spokenForms: ["foo air"],
});
assert.deepStrictEqual(
generator.scopeTypeToSpokenForm({
type: "glyph",
character: "a",
}),
{
type: "success",
spokenForms: ["foo air"],
},
);
assert.deepStrictEqual(
generator.commandToSpokenForm({
version: LATEST_VERSION,
action: {
name: "setSelection",
target: {
type: "primitive",
mark: {
type: "cursor",
},
},
},
usePrePhraseSnapshot: false,
}),
{
type: "success",
spokenForms: ["bar this"],
},
);
}),
);
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ActionType,
CommandComplete,
Disposable,
Listener,
Expand Down Expand Up @@ -54,6 +55,10 @@ export class CustomSpokenFormGeneratorImpl
return this.spokenFormGenerator.processScopeType(scopeType);
}

actionIdToSpokenForm(actionId: ActionType) {
return this.customSpokenForms.spokenFormMap.action[actionId];
}

getCustomRegexScopeTypes() {
return this.customSpokenForms.getCustomRegexScopeTypes();
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
camelCaseToAllDown,
} from "@cursorless/common";
import { NoSpokenFormError } from "./NoSpokenFormError";
import { actions } from "./defaultSpokenForms/actions";
import { connectives } from "./defaultSpokenForms/connectives";
import { surroundingPairDelimitersToSpokenForm } from "./defaultSpokenForms/modifiers";
import {
Expand Down Expand Up @@ -111,25 +110,28 @@ export class SpokenFormGenerator {
case "replaceWithTarget":
case "moveToTarget":
return [
actions[action.name],
this.spokenFormMap.action[action.name],
this.handleTarget(action.source),
this.handleDestination(action.destination),
];

case "swapTargets":
return [
actions[action.name],
this.spokenFormMap.action[action.name],
this.handleTarget(action.target1),
connectives.swapConnective,
this.handleTarget(action.target2),
];

case "callAsFunction":
if (action.argument.type === "implicit") {
return [actions[action.name], this.handleTarget(action.callee)];
return [
this.spokenFormMap.action[action.name],
this.handleTarget(action.callee),
];
}
return [
actions[action.name],
this.spokenFormMap.action[action.name],
this.handleTarget(action.callee),
"on",
this.handleTarget(action.argument),
Expand All @@ -143,19 +145,19 @@ export class SpokenFormGenerator {
action.left,
action.right,
),
actions[action.name],
this.spokenFormMap.action[action.name],
this.handleTarget(action.target),
];

case "pasteFromClipboard":
return [
actions[action.name],
this.spokenFormMap.action[action.name],
this.handleDestination(action.destination),
];

case "insertSnippet":
return [
actions[action.name],
this.spokenFormMap.action[action.name],
insertionSnippetToSpokenForm(action.snippetDescription),
this.handleDestination(action.destination),
];
Expand All @@ -164,24 +166,33 @@ export class SpokenFormGenerator {
if (action.snippetName != null) {
throw new NoSpokenFormError(`${action.name}.snippetName`);
}
return [actions[action.name], this.handleTarget(action.target)];
return [
this.spokenFormMap.action[action.name],
this.handleTarget(action.target),
];

case "wrapWithSnippet":
return [
wrapperSnippetToSpokenForm(action.snippetDescription),
actions[action.name],
this.spokenFormMap.action[action.name],
this.handleTarget(action.target),
];

case "highlight": {
if (action.highlightId != null) {
throw new NoSpokenFormError(`${action.name}.highlightId`);
}
return [actions[action.name], this.handleTarget(action.target)];
return [
this.spokenFormMap.action[action.name],
this.handleTarget(action.target),
];
}

default: {
return [actions[action.name], this.handleTarget(action.target)];
return [
this.spokenFormMap.action[action.name],
this.handleTarget(action.target),
];
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const SUPPORTED_ENTRY_TYPES = [
"complexScopeTypeType",
"customRegex",
"pairedDelimiter",
"action",
"customAction",
] as const;

type SupportedEntryType = (typeof SUPPORTED_ENTRY_TYPES)[number];
Expand Down
9 changes: 9 additions & 0 deletions packages/cursorless-engine/src/spokenForms/SpokenFormType.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
ActionType,
ModifierType,
SimpleScopeTypeType,
SurroundingPairName,
Expand Down Expand Up @@ -33,6 +34,14 @@ export interface SpokenFormMapKeyTypes {
*/
modifierExtra: ModifierExtra;
customRegex: string;

action: ActionType;

/**
* These actions correspond to id's of app commands. Eg in VSCode, you can have
* custom actions corresponding to id's of VSCode commands.
*/
customAction: string;
}

/**
Expand Down
Loading

0 comments on commit 95ae15f

Please sign in to comment.