cursorless icon indicating copy to clipboard operation
cursorless copied to clipboard

Destinations as first-class objects

Open pokey opened this issue 3 years ago • 0 comments

The problem

Today, our handling of positions is inconsistent. This problem manifests itself in multiple ways:

  • We allow "end of" to appear in places that expect a position target, which means that "paste end of air" is a valid command, making it nearly impossible to get Talon to hear "take air past end of item bat". It instead hears two commands: "take air" then "paste end of item bat"
  • We need to have the edit action on regular targets, and position targets need all the methods from regular targets that they don't actually support
  • Targets have no strongly typed way of indicating that they expect a position
  • Position targets are hacked to set delimiter to "" when position is "end of" or "start of"

The solution

We introduce a new type called a Destination, both on the Talon and extension side. This type is distinct from a Target, and will be used everywhere we expect a PositionTarget today (eg "paste", second target of "bring", etc).

Destination will be have the following interface:

type InsertionMode = "before" | "after" | "to";

interface Destination {
  insertionMode: InsertionMode;
  target: Target;
}

It will exist in the following places:

  • As a capture Talon side
  • As a new descriptor type, similar to today's TargetDescriptor
  • As a new post-processTargets type, like today's Target

Talon side

On the Talon side, we would like to do the following:

1. Make "before", "after" and "to" into "insertion modes"

We'll have a new list called cursorless_insertion_mode, which will consist of "before", "after" and "to".

These terms should also be removed from both cursorless_position and cursorless_source_destination_connective.

Note that "end of" and "start of" remain as position modifiers.

2. Add new capture for destinations

Replace the <cursorless_positional_target> capture with the following:

@mod.capture(
    rule=(
        "{user.cursorless_insertion_mode} <user.cursorless_target> "
        "({user.cursorless_list_connective} {user.cursorless_insertion_mode} <user.cursorless_target>)*"
    )
)
def cursorless_destination(m) -> list[dict]:
    return {
        "type": "destinationList",
        "destinations": [
            {
                "type": "destination",
                "insertionMode": insertion_mode,
                "target": target,
            }
            for insertion_mode, target in zip(m.cursorless_insertion_mode, m.cursorless_target)
        ]
    }

Note that we are using the cursorless_target capture as part of this capture, which can contain its own "and"s. That's how we support "after air and bat and before cap".

We would then use this new cursorless_destination capture wherever we use cursorless_positional_target today.

Extension side

  • Handle destination list by processing targets for each destination, then wrapping each output in a destination with proper insertion mode
  • Change type signature of run for actions that expect a destination to use Destination
  • In the case of "swap", it expects two actual targets (not destinations), but internally converts them to destinations when using them as such.
  • Change PositionTarget to be Destination and stop implementing Target for this type, removing all unnecessary attributes
  • Remove edit from Target

Problems

  • We already have "before" and "after" in positions.csv, and "to" in connectives.csv, but ideally we'd move them into the same file as each other

pokey avatar Jun 29 '22 18:06 pokey