Skip to content

Commit

Permalink
Experimental private Cursorless Talon api (#1784)
Browse files Browse the repository at this point in the history
- 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>
  • Loading branch information
pokey and pre-commit-ci-lite[bot] authored Dec 19, 2023
1 parent fff171f commit 297ec43
Show file tree
Hide file tree
Showing 5 changed files with 182 additions and 1 deletion.
4 changes: 4 additions & 0 deletions cursorless-talon-dev/src/cursorless_test.talon
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ test api wrap with snippet <user.cursorless_target>:
user.cursorless_wrap_with_snippet("Hello, $foo! My name is $bar!", cursorless_target, "foo", "statement")
test api wrap with snippet by name <user.cursorless_target>:
user.cursorless_wrap_with_snippet_by_name("functionDeclaration", "body", cursorless_target)
test api extract decorated marks <user.cursorless_target>:
user.private_cursorless_test_extract_decorated_marks(cursorless_target)
test api alternate highlight nothing:
user.private_cursorless_test_alternate_highlight_nothing()
19 changes: 19 additions & 0 deletions cursorless-talon-dev/src/spoken_form_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,25 @@ def private_cursorless_spoken_form_test(
except Exception as e:
print(f"{e.__class__.__name__}: {e}")

def private_cursorless_test_extract_decorated_marks(target: Any):
"""Run test for Cursorless private extract decorated marks api"""
marks = actions.user.cursorless_private_extract_decorated_marks(target)
all_decorated_marks_target = actions.user.cursorless_private_build_list_target(
[
actions.user.cursorless_private_build_primitive_target([], mark)
for mark in marks
]
)
actions.user.cursorless_private_action_highlight(
all_decorated_marks_target, "highlight1"
)

def private_cursorless_test_alternate_highlight_nothing():
"""Run test for Cursorless private highlight nothing api"""
actions.user.cursorless_private_action_highlight(
actions.user.cursorless_private_target_nothing(), "highlight1"
)


def enable_modes():
for mode in saved_modes:
Expand Down
47 changes: 47 additions & 0 deletions cursorless-talon/src/private_api/extract_decorated_marks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from typing import Any

from ..actions.bring_move import BringMoveTargets
from ..actions.swap import SwapTargets
from ..targets.target_types import (
ImplicitDestination,
ImplicitTarget,
ListDestination,
ListTarget,
PrimitiveDestination,
PrimitiveTarget,
RangeTarget,
)


def extract_decorated_marks(capture: Any) -> list[Any]:
match capture:
case PrimitiveTarget(mark=mark):
if mark is None or mark["type"] != "decoratedSymbol":
return []
return [mark]
case ImplicitTarget():
return []
case RangeTarget(anchor=anchor, active=active):
return extract_decorated_marks(anchor) + extract_decorated_marks(active)
case ListTarget(elements=elements):
return [
mark for target in elements for mark in extract_decorated_marks(target)
]
case PrimitiveDestination(target=target):
return extract_decorated_marks(target)
case ImplicitDestination():
return []
case ListDestination(destinations=destinations):
return [
mark
for destination in destinations
for mark in extract_decorated_marks(destination)
]
case BringMoveTargets(source=source, destination=destination):
return extract_decorated_marks(source) + extract_decorated_marks(
destination
)
case SwapTargets(target1=target1, target2=target2):
return extract_decorated_marks(target1) + extract_decorated_marks(target2)
case _:
raise TypeError(f"Unknown capture type: {type(capture)}")
66 changes: 66 additions & 0 deletions cursorless-talon/src/private_api/private_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
from typing import Any, Optional, Union

from talon import Module, actions

from ..targets.target_types import (
CursorlessTarget,
ListTarget,
PrimitiveTarget,
RangeTarget,
)
from .extract_decorated_marks import extract_decorated_marks

mod = Module()


@mod.action_class
class MiscActions:
def cursorless_private_extract_decorated_marks(capture: Any) -> list[dict]:
"""Cursorless private api: Extract all decorated marks from a Talon capture"""
return extract_decorated_marks(capture)


@mod.action_class
class TargetBuilderActions:
"""Cursorless private api low-level target builder actions"""

def cursorless_private_build_primitive_target(
modifiers: list[dict], mark: Optional[dict]
) -> PrimitiveTarget:
"""Cursorless private api low-level target builder: Create a primitive target"""
return PrimitiveTarget(mark, modifiers)

def cursorless_private_build_list_target(
elements: list[Union[PrimitiveTarget, RangeTarget]]
) -> Union[PrimitiveTarget, ListTarget]:
"""Cursorless private api low-level target builder: Create a list target"""
if len(elements) == 1:
return elements[0]

return ListTarget(elements)


@mod.action_class
class TargetActions:
def cursorless_private_target_nothing() -> PrimitiveTarget:
"""Cursorless private api: Creates the "nothing" target"""
return PrimitiveTarget({"type": "nothing"}, [])


@mod.action_class
class ActionActions:
def cursorless_private_action_highlight(
target: CursorlessTarget, highlightId: Optional[str] = None
) -> None:
"""Cursorless private api: Highlights a target"""
payload = {
"name": "highlight",
"target": target,
}

if highlightId is not None:
payload["highlightId"] = highlightId

actions.user.private_cursorless_command_and_wait(
payload,
)
47 changes: 46 additions & 1 deletion packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,44 @@ const parseTreeAction: ActionDescriptor = {
name: "private.showParseTree",
target: decoratedPrimitiveTarget("a"),
};
const alternateHighlightAirAndBatAction: ActionDescriptor = {
name: "highlight",
target: {
type: "list",
elements: [
{
type: "primitive",
mark: {
type: "decoratedSymbol",
symbolColor: "default",
character: "a",
},
modifiers: [],
},
{
type: "primitive",
mark: {
type: "decoratedSymbol",
symbolColor: "default",
character: "b",
},
modifiers: [],
},
],
},
highlightId: "highlight1",
};
const alternateHighlightNothingAction: ActionDescriptor = {
name: "highlight",
target: {
type: "primitive",
mark: {
type: "nothing",
},
modifiers: [],
},
highlightId: "highlight1",
};

/**
* These test our Talon api using dummy spoken forms defined in
Expand All @@ -120,7 +158,14 @@ export const talonApiFixture = [
"test api wrap with snippet by name this",
wrapWithSnippetByNameAction,
),
spokenFormTest("parse tree air", parseTreeAction),
spokenFormTest(
"test api extract decorated marks air past bat",
alternateHighlightAirAndBatAction,
),
spokenFormTest(
"test api alternate highlight nothing",
alternateHighlightNothingAction,
),
];

function decoratedPrimitiveTarget(
Expand Down

0 comments on commit 297ec43

Please sign in to comment.