Skip to content

Commit

Permalink
Add cursorless_create_destination action to talon api (#2402)
Browse files Browse the repository at this point in the history
This came up during a pairing session. I wanted to do something like:

```talon
transform <user.cursorless_target>:
    old = user.cursorless_get_text(cursorless_target, 1)
    new = some_transformation(old)
    destination = user.cursorless_create_destination(cursorless_target)
    user.cursorless_insert(destination, new)
```

Notice I can't use our existing `user.cursorless_insert` out of the box
because it expects a destination, so we need a way to make a destination
from a target

## Checklist

- [-] 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)
- [x] I have not broken the cheatsheet
  • Loading branch information
pokey authored Jun 14, 2024
1 parent 3a0d9c8 commit 9251a02
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 1 deletion.
6 changes: 6 additions & 0 deletions changelog/2024-06-addCreateDestination.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
tags: [enhancement, talon]
pullRequest: 2402
---

- Add `user.cursorless_create_destination` to the public API. See the [Talon-side api docs](../docs/user/customization.md#cursorless-public-api) and the example code in the [How do I run a custom Python transformation on a target?](../docs/user/how-to.md#how-do-i-run-a-custom-python-transformation-on-a-target) section for more information.
22 changes: 22 additions & 0 deletions cursorless-talon/src/public_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from talon import Module

from .targets.target_types import (
CursorlessDestination,
InsertionMode,
ListTarget,
PrimitiveDestination,
PrimitiveTarget,
RangeTarget,
)

mod = Module()


@mod.action_class
class Actions:
def cursorless_create_destination(
target: ListTarget | RangeTarget | PrimitiveTarget, # pyright: ignore [reportGeneralTypeIssues]
insertion_mode: InsertionMode = "to",
) -> CursorlessDestination:
"""Cursorless: Create destination from target"""
return PrimitiveDestination(insertion_mode, target)
4 changes: 3 additions & 1 deletion cursorless-talon/src/targets/target_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,13 @@ class ListTarget:
PrimitiveTarget,
]

InsertionMode = Literal["to", "before", "after"]


@dataclass
class PrimitiveDestination:
type = "primitive"
insertionMode: Literal["to", "before", "after"]
insertionMode: InsertionMode
target: Union[ListTarget, RangeTarget, PrimitiveTarget]


Expand Down
5 changes: 5 additions & 0 deletions docs/user/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,9 @@ Cursorless exposes a couple talon actions and captures that you can use to defin
- `<user.cursorless_target>`
Represents a cursorless target, such as `"air"`, `"this"`, `"air past bat"`, `"air and bat"`, `"funk air past token bat and class cap"`, etc

- `<user.cursorless_destination>`
Represents a cursorless destination, such as `"to air"`, `"before this"`, `"after air and bat"`, etc

### Public Talon actions

- `user.cursorless_command(action_id: str, target: cursorless_target)`:
Expand All @@ -158,6 +161,8 @@ Cursorless exposes a couple talon actions and captures that you can use to defin
- `user.cursorless_insert(destination: CursorlessDestination, text: Union[str, List[str]])`:
Insert text at destination.
eg: `user.cursorless_insert(cursorless_destination, "hello")`
- `user.cursorless_create_destination(target: CursorlessTarget, insertion_mode: Literal["to", "before", "after"] = "to") -> CursorlessDestination`:
Create a destination from a target. The insertion mode can be `to`, `before`, or `after`, and defaults to `to`, which will replace the target. See [How do I run a custom Python transformation on a target?](./how-to.md#how-do-i-run-a-custom-python-transformation-on-a-target) for example usage.
- `user.cursorless_reformat(target: CursorlessTarget, formatters: str)`
Reformat target with specified formatters.
eg: `user.cursorless_reformat(cursorless_target, "ALL_CAPS")`
Expand Down
41 changes: 41 additions & 0 deletions docs/user/how-to.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,44 @@ You can now say eg `"echo air past bat"`.
See the [Talon-side api docs](./customization.md#cursorless-public-api) for more on creating custom Cursorless commands

:::

## How do I run a custom Python transformation on a target?

1. Add the transformation to a Python file in your Talon user directory:

```python
from talon import Module

mod = Module()


@mod.action_class
class Actions:
def hello(text: str) -> str:
"""Returns a greeting for the given text."""
return f"Hello, {text}!"
```

2. Add a spoken form to your `vscode.talon`:

```talon
hello <user.cursorless_target>:
old = user.cursorless_get_text(cursorless_target, true)
new = user.hello(old)
destination = user.cursorless_create_destination(cursorless_target)
user.cursorless_insert(destination, new)
```

Now, for example if you have a target `aardvark` with a hat over the `a`, you can say `"hello air"` to replace it with `Hello, aardvark!`.

:::info

See the [Talon-side api docs](./customization.md#cursorless-public-api) for more on creating custom Cursorless commands

:::

:::info

Notice how we use `cursorless_create_destination` to create a destination for the result of the transformation. The `cursorless_insert` action expects a destination, so that it knows whether to replace, insert before, or insert after the target. See [Destinations](reference/destinations.md) for more information.

:::

0 comments on commit 9251a02

Please sign in to comment.