Skip to content

Commit 297ec43

Browse files
Experimental private Cursorless Talon api (#1784)
- Private experimental api; one version of #492 - Currently used by [`wax_talon`](https://github.com/pokey/wax_talon); see pokey/wax_talon@4bd7d9b for example usage - Depends on #1880 for Python 3.10 `match` statements ## 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 --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent fff171f commit 297ec43

File tree

5 files changed

+182
-1
lines changed

5 files changed

+182
-1
lines changed

cursorless-talon-dev/src/cursorless_test.talon

+4
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ test api wrap with snippet <user.cursorless_target>:
2121
user.cursorless_wrap_with_snippet("Hello, $foo! My name is $bar!", cursorless_target, "foo", "statement")
2222
test api wrap with snippet by name <user.cursorless_target>:
2323
user.cursorless_wrap_with_snippet_by_name("functionDeclaration", "body", cursorless_target)
24+
test api extract decorated marks <user.cursorless_target>:
25+
user.private_cursorless_test_extract_decorated_marks(cursorless_target)
26+
test api alternate highlight nothing:
27+
user.private_cursorless_test_alternate_highlight_nothing()

cursorless-talon-dev/src/spoken_form_test.py

+19
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,25 @@ def private_cursorless_spoken_form_test(
9797
except Exception as e:
9898
print(f"{e.__class__.__name__}: {e}")
9999

100+
def private_cursorless_test_extract_decorated_marks(target: Any):
101+
"""Run test for Cursorless private extract decorated marks api"""
102+
marks = actions.user.cursorless_private_extract_decorated_marks(target)
103+
all_decorated_marks_target = actions.user.cursorless_private_build_list_target(
104+
[
105+
actions.user.cursorless_private_build_primitive_target([], mark)
106+
for mark in marks
107+
]
108+
)
109+
actions.user.cursorless_private_action_highlight(
110+
all_decorated_marks_target, "highlight1"
111+
)
112+
113+
def private_cursorless_test_alternate_highlight_nothing():
114+
"""Run test for Cursorless private highlight nothing api"""
115+
actions.user.cursorless_private_action_highlight(
116+
actions.user.cursorless_private_target_nothing(), "highlight1"
117+
)
118+
100119

101120
def enable_modes():
102121
for mode in saved_modes:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from typing import Any
2+
3+
from ..actions.bring_move import BringMoveTargets
4+
from ..actions.swap import SwapTargets
5+
from ..targets.target_types import (
6+
ImplicitDestination,
7+
ImplicitTarget,
8+
ListDestination,
9+
ListTarget,
10+
PrimitiveDestination,
11+
PrimitiveTarget,
12+
RangeTarget,
13+
)
14+
15+
16+
def extract_decorated_marks(capture: Any) -> list[Any]:
17+
match capture:
18+
case PrimitiveTarget(mark=mark):
19+
if mark is None or mark["type"] != "decoratedSymbol":
20+
return []
21+
return [mark]
22+
case ImplicitTarget():
23+
return []
24+
case RangeTarget(anchor=anchor, active=active):
25+
return extract_decorated_marks(anchor) + extract_decorated_marks(active)
26+
case ListTarget(elements=elements):
27+
return [
28+
mark for target in elements for mark in extract_decorated_marks(target)
29+
]
30+
case PrimitiveDestination(target=target):
31+
return extract_decorated_marks(target)
32+
case ImplicitDestination():
33+
return []
34+
case ListDestination(destinations=destinations):
35+
return [
36+
mark
37+
for destination in destinations
38+
for mark in extract_decorated_marks(destination)
39+
]
40+
case BringMoveTargets(source=source, destination=destination):
41+
return extract_decorated_marks(source) + extract_decorated_marks(
42+
destination
43+
)
44+
case SwapTargets(target1=target1, target2=target2):
45+
return extract_decorated_marks(target1) + extract_decorated_marks(target2)
46+
case _:
47+
raise TypeError(f"Unknown capture type: {type(capture)}")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from typing import Any, Optional, Union
2+
3+
from talon import Module, actions
4+
5+
from ..targets.target_types import (
6+
CursorlessTarget,
7+
ListTarget,
8+
PrimitiveTarget,
9+
RangeTarget,
10+
)
11+
from .extract_decorated_marks import extract_decorated_marks
12+
13+
mod = Module()
14+
15+
16+
@mod.action_class
17+
class MiscActions:
18+
def cursorless_private_extract_decorated_marks(capture: Any) -> list[dict]:
19+
"""Cursorless private api: Extract all decorated marks from a Talon capture"""
20+
return extract_decorated_marks(capture)
21+
22+
23+
@mod.action_class
24+
class TargetBuilderActions:
25+
"""Cursorless private api low-level target builder actions"""
26+
27+
def cursorless_private_build_primitive_target(
28+
modifiers: list[dict], mark: Optional[dict]
29+
) -> PrimitiveTarget:
30+
"""Cursorless private api low-level target builder: Create a primitive target"""
31+
return PrimitiveTarget(mark, modifiers)
32+
33+
def cursorless_private_build_list_target(
34+
elements: list[Union[PrimitiveTarget, RangeTarget]]
35+
) -> Union[PrimitiveTarget, ListTarget]:
36+
"""Cursorless private api low-level target builder: Create a list target"""
37+
if len(elements) == 1:
38+
return elements[0]
39+
40+
return ListTarget(elements)
41+
42+
43+
@mod.action_class
44+
class TargetActions:
45+
def cursorless_private_target_nothing() -> PrimitiveTarget:
46+
"""Cursorless private api: Creates the "nothing" target"""
47+
return PrimitiveTarget({"type": "nothing"}, [])
48+
49+
50+
@mod.action_class
51+
class ActionActions:
52+
def cursorless_private_action_highlight(
53+
target: CursorlessTarget, highlightId: Optional[str] = None
54+
) -> None:
55+
"""Cursorless private api: Highlights a target"""
56+
payload = {
57+
"name": "highlight",
58+
"target": target,
59+
}
60+
61+
if highlightId is not None:
62+
payload["highlightId"] = highlightId
63+
64+
actions.user.private_cursorless_command_and_wait(
65+
payload,
66+
)

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

+46-1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,44 @@ const parseTreeAction: ActionDescriptor = {
9696
name: "private.showParseTree",
9797
target: decoratedPrimitiveTarget("a"),
9898
};
99+
const alternateHighlightAirAndBatAction: ActionDescriptor = {
100+
name: "highlight",
101+
target: {
102+
type: "list",
103+
elements: [
104+
{
105+
type: "primitive",
106+
mark: {
107+
type: "decoratedSymbol",
108+
symbolColor: "default",
109+
character: "a",
110+
},
111+
modifiers: [],
112+
},
113+
{
114+
type: "primitive",
115+
mark: {
116+
type: "decoratedSymbol",
117+
symbolColor: "default",
118+
character: "b",
119+
},
120+
modifiers: [],
121+
},
122+
],
123+
},
124+
highlightId: "highlight1",
125+
};
126+
const alternateHighlightNothingAction: ActionDescriptor = {
127+
name: "highlight",
128+
target: {
129+
type: "primitive",
130+
mark: {
131+
type: "nothing",
132+
},
133+
modifiers: [],
134+
},
135+
highlightId: "highlight1",
136+
};
99137

100138
/**
101139
* These test our Talon api using dummy spoken forms defined in
@@ -120,7 +158,14 @@ export const talonApiFixture = [
120158
"test api wrap with snippet by name this",
121159
wrapWithSnippetByNameAction,
122160
),
123-
spokenFormTest("parse tree air", parseTreeAction),
161+
spokenFormTest(
162+
"test api extract decorated marks air past bat",
163+
alternateHighlightAirAndBatAction,
164+
),
165+
spokenFormTest(
166+
"test api alternate highlight nothing",
167+
alternateHighlightNothingAction,
168+
),
124169
];
125170

126171
function decoratedPrimitiveTarget(

0 commit comments

Comments
 (0)