Skip to content

Commit d0f01ef

Browse files
Added text insertion action to Cursorless public api (#1875)
Fixes #1754 ## Checklist - [x] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [x] 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 --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 065f98a commit d0f01ef

File tree

7 files changed

+73
-11
lines changed

7 files changed

+73
-11
lines changed

cursorless-talon-dev/src/cursorless_test.talon

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ test api command <user.cursorless_target>:
77
user.cursorless_command("setSelection", cursorless_target)
88
test api command bring <user.cursorless_target>:
99
user.cursorless_command("replaceWithTarget", cursorless_target)
10+
test api insert <user.word> <user.cursorless_destination>:
11+
user.cursorless_insert(cursorless_destination, word)
12+
test api insert <user.word> and <user.word> <user.cursorless_destination>:
13+
user.cursorless_insert(cursorless_destination, word_list)
1014
test api insert snippet:
1115
user.cursorless_insert_snippet("Hello, $foo! My name is $bar!")
1216
test api insert snippet by name:

cursorless-talon/src/actions/actions.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
from typing import Union
2+
13
from talon import Module, actions
24

3-
from ..targets.target_types import CursorlessTarget, ImplicitDestination
5+
from ..targets.target_types import (
6+
CursorlessDestination,
7+
CursorlessTarget,
8+
ImplicitDestination,
9+
)
410
from .bring_move import BringMoveTargets
511
from .call import cursorless_call_action
612
from .execute_command import cursorless_execute_command_action
713
from .homophones import cursorless_homophones_action
14+
from .replace import cursorless_replace_action
815

916
mod = Module()
1017

@@ -111,6 +118,14 @@ def cursorless_ide_command(command_id: str, target: CursorlessTarget):
111118
"""Perform ide command on cursorless target"""
112119
return cursorless_execute_command_action(command_id, target)
113120

121+
def cursorless_insert(
122+
destination: CursorlessDestination, text: Union[str, list[str]]
123+
):
124+
"""Perform text insertion on Cursorless destination"""
125+
if isinstance(text, str):
126+
text = [text]
127+
cursorless_replace_action(destination, text)
128+
114129
def private_cursorless_action_or_ide_command(
115130
instruction: dict, target: CursorlessTarget
116131
):

cursorless-talon/src/actions/homophones.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from talon import actions, app
22

3+
from ..targets.target_types import PrimitiveDestination
34
from .get_text import cursorless_get_text_action
45
from .replace import cursorless_replace_action
56

@@ -12,7 +13,8 @@ def cursorless_homophones_action(target: dict):
1213
except LookupError as e:
1314
app.notify(str(e))
1415
return
15-
cursorless_replace_action(target, updated_texts)
16+
destination = PrimitiveDestination("to", target)
17+
cursorless_replace_action(destination, updated_texts)
1618

1719

1820
def get_next_homophone(word: str):

cursorless-talon/src/actions/reformat.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from talon import Module, actions
22

3-
from ..targets.target_types import CursorlessTarget
3+
from ..targets.target_types import CursorlessTarget, PrimitiveDestination
44
from .get_text import cursorless_get_text_action
55
from .replace import cursorless_replace_action
66

@@ -15,4 +15,5 @@ def private_cursorless_reformat(target: CursorlessTarget, formatters: str):
1515
"""Execute Cursorless reformat action. Reformat target with formatter"""
1616
texts = cursorless_get_text_action(target, show_decorations=False)
1717
updated_texts = [actions.user.reformat_text(text, formatters) for text in texts]
18-
cursorless_replace_action(target, updated_texts)
18+
destination = PrimitiveDestination("to", target)
19+
cursorless_replace_action(destination, updated_texts)
Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
from talon import actions
22

3-
from ..targets.target_types import CursorlessTarget, PrimitiveDestination
3+
from ..targets.target_types import CursorlessDestination
44

55

6-
def cursorless_replace_action(target: CursorlessTarget, replace_with: list[str]):
6+
def cursorless_replace_action(
7+
destination: CursorlessDestination, replace_with: list[str]
8+
):
79
"""Execute Cursorless replace action. Replace targets with texts"""
810
actions.user.private_cursorless_command_and_wait(
911
{
1012
"name": "replace",
1113
"replaceWith": replace_with,
12-
"destination": PrimitiveDestination("to", target),
14+
"destination": destination,
1315
}
1416
)

docs/user/customization.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ Cursorless exposes a couple talon actions and captures that you can use to defin
118118
- `user.cursorless_ide_command(command_id: str, target: cursorless_target)`:
119119
Performs a built-in IDE command on the given target
120120
eg: `user.cursorless_ide_command("editor.action.addCommentLine", cursorless_target)`
121+
- `user.cursorless_insert(destination: CursorlessDestination, text: Union[str, List[str]])`:
122+
Insert text at destination.
123+
eg: `user.cursorless_insert(cursorless_destination, "hello")`
121124

122125
#### Snippet actions
123126

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

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
import { ActionDescriptor } from "@cursorless/common";
1+
import {
2+
ActionDescriptor,
3+
PartialPrimitiveTargetDescriptor,
4+
} from "@cursorless/common";
25
import { spokenFormTest } from "./spokenFormTest";
36

47
// See cursorless-talon-dev/src/cursorless_test.talon
@@ -11,11 +14,29 @@ const setSelectionAction: ActionDescriptor = {
1114
};
1215
const replaceWithTargetAction: ActionDescriptor = {
1316
name: "replaceWithTarget",
14-
source: {
17+
source: decoratedPrimitiveTarget("a"),
18+
destination: { type: "implicit" },
19+
};
20+
const insertSingleWordTargetAction: ActionDescriptor = {
21+
name: "replace",
22+
destination: {
1523
type: "primitive",
16-
mark: { type: "decoratedSymbol", symbolColor: "default", character: "a" },
24+
insertionMode: "to",
25+
target: decoratedPrimitiveTarget("a"),
1726
},
18-
destination: { type: "implicit" },
27+
replaceWith: ["hello"],
28+
};
29+
const insertMultipleWordsTargetAction: ActionDescriptor = {
30+
name: "replace",
31+
destination: {
32+
type: "primitive",
33+
insertionMode: "after",
34+
target: {
35+
type: "list",
36+
elements: [decoratedPrimitiveTarget("a"), decoratedPrimitiveTarget("b")],
37+
},
38+
},
39+
replaceWith: ["hello", "world"],
1940
};
2041
const insertSnippetAction: ActionDescriptor = {
2142
name: "insertSnippet",
@@ -66,6 +87,11 @@ const wrapWithSnippetByNameAction: ActionDescriptor = {
6687
export const talonApiFixture = [
6788
spokenFormTest("test api command this", setSelectionAction),
6889
spokenFormTest("test api command bring air", replaceWithTargetAction),
90+
spokenFormTest("test api insert hello to air", insertSingleWordTargetAction),
91+
spokenFormTest(
92+
"test api insert hello and world after air and bat",
93+
insertMultipleWordsTargetAction,
94+
),
6995
spokenFormTest("test api insert snippet", insertSnippetAction),
7096
spokenFormTest("test api insert snippet by name", insertSnippetByNameAction),
7197
spokenFormTest("test api wrap with snippet this", wrapWithSnippetAction),
@@ -74,3 +100,12 @@ export const talonApiFixture = [
74100
wrapWithSnippetByNameAction,
75101
),
76102
];
103+
104+
function decoratedPrimitiveTarget(
105+
character: string,
106+
): PartialPrimitiveTargetDescriptor {
107+
return {
108+
type: "primitive",
109+
mark: { type: "decoratedSymbol", symbolColor: "default", character },
110+
};
111+
}

0 commit comments

Comments
 (0)