Skip to content

Commit

Permalink
Support fully compositional modifiers
Browse files Browse the repository at this point in the history
Fixes #69
  • Loading branch information
pokey committed May 27, 2022
1 parent 972cab1 commit b6533c5
Show file tree
Hide file tree
Showing 250 changed files with 7,047 additions and 6,555 deletions.
2 changes: 1 addition & 1 deletion cursorless-talon/src/cheatsheet/sections/scopes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
def get_scopes():
return {
**get_lists(
["scope_type", "selection_type", "subtoken_scope_type"],
["scope_type", "subtoken_scope_type"],
{"argumentOrParameter": "Argument"},
),
"<P>": "Paired delimiter",
Expand Down
8 changes: 5 additions & 3 deletions cursorless-talon/src/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,11 +127,13 @@ def construct_cursorless_command_argument(
use_pre_phrase_snapshot = False

return {
"version": 1,
"version": 2,
"spokenForm": get_spoken_form(),
"action": action,
"action": {
"name": action,
"args": args,
},
"targets": targets,
"extraArgs": args,
"usePrePhraseSnapshot": use_pre_phrase_snapshot,
}

Expand Down
12 changes: 6 additions & 6 deletions cursorless-talon/src/compound_targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,19 @@ def cursorless_range(m) -> str:
return primitive_targets[0]

if len(primitive_targets) == 1:
start = BASE_TARGET.copy()
anchor = BASE_TARGET.copy()
else:
start = primitive_targets[0]
anchor = primitive_targets[0]

range_connective = range_connective_with_type["connective"]
range_type = range_connective_with_type["type"]

range = {
"type": "range",
"start": start,
"end": primitive_targets[-1],
"excludeStart": not is_anchor_included(range_connective),
"excludeEnd": not is_active_included(range_connective),
"anchor": anchor,
"active": primitive_targets[-1],
"excludeAnchor": not is_anchor_included(range_connective),
"excludeActive": not is_active_included(range_connective),
}

if range_type:
Expand Down
14 changes: 6 additions & 8 deletions cursorless-talon/src/marks/lines_number.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass
from typing import Any, Callable

from talon import Context, Module

Expand All @@ -13,7 +14,7 @@ class CustomizableTerm:
defaultSpokenForm: str
cursorlessIdentifier: str
type: str
formatter: callable
formatter: Callable


# NOTE: Please do not change these dicts. Use the CSVs for customization.
Expand All @@ -33,18 +34,15 @@ class CustomizableTerm:


@mod.capture(rule="{user.cursorless_line_direction} <number_small>")
def cursorless_line_number(m) -> str:
def cursorless_line_number(m) -> dict[str, Any]:
direction = directions_map[m.cursorless_line_direction]
line_number = m.number_small
line = {
"lineNumber": direction.formatter(line_number),
"type": direction.type,
}
return {
"selectionType": "line",
"mark": {
"type": "lineNumber",
"anchor": line,
"active": line,
},
"type": "lineNumber",
"anchor": line,
"active": line,
}
20 changes: 9 additions & 11 deletions cursorless-talon/src/marks/mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,17 @@
@mod.capture(
rule="[{user.cursorless_hat_color}] [{user.cursorless_hat_shape}] <user.any_alphanumeric_key>"
)
def cursorless_decorated_symbol(m) -> dict[str, dict[str, Any]]:
def cursorless_decorated_symbol(m) -> dict[str, Any]:
"""A decorated symbol"""
hat_color = getattr(m, "cursorless_hat_color", "default")
try:
hat_style_name = f"{hat_color}-{m.cursorless_hat_shape}"
except AttributeError:
hat_style_name = hat_color
return {
"mark": {
"type": "decoratedSymbol",
"symbolColor": hat_style_name,
"character": m.any_alphanumeric_key,
}
"type": "decoratedSymbol",
"symbolColor": hat_style_name,
"character": m.any_alphanumeric_key,
}


Expand All @@ -69,10 +67,10 @@ class CustomizableTerm:
# NOTE: Please do not change these dicts. Use the CSVs for customization.
# See https://www.cursorless.org/docs/user/customization/
special_marks = [
CustomizableTerm("this", "currentSelection", {"mark": {"type": "cursor"}}),
CustomizableTerm("that", "previousTarget", {"mark": {"type": "that"}}),
CustomizableTerm("source", "previousSource", {"mark": {"type": "source"}}),
CustomizableTerm("nothing", "nothing", {"mark": {"type": "nothing"}}),
CustomizableTerm("this", "currentSelection", {"type": "cursor"}),
CustomizableTerm("that", "previousTarget", {"type": "that"}),
CustomizableTerm("source", "previousSource", {"type": "source"}),
CustomizableTerm("nothing", "nothing", {"type": "nothing"}),
# "last cursor": {"mark": {"type": "lastCursorPosition"}} # Not implemented
]

Expand All @@ -93,7 +91,7 @@ class CustomizableTerm:
"<user.cursorless_line_number>" # row (ie absolute mod 100), up, down
)
)
def cursorless_mark(m) -> str:
def cursorless_mark(m) -> dict[str, Any]:
try:
return m.cursorless_decorated_symbol
except AttributeError:
Expand Down
32 changes: 13 additions & 19 deletions cursorless-talon/src/modifiers/containing_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,7 @@
"tags": "xmlBothTags",
"start tag": "xmlStartTag",
"end tag": "xmlEndTag",
}


@mod.capture(rule="[every] {user.cursorless_scope_type}")
def cursorless_containing_scope(m) -> dict[str, dict[str, Any]]:
"""Expand to containing scope"""
return {
"modifier": {
"type": "containingScope",
"scopeType": m.cursorless_scope_type,
"includeSiblings": m[0] == "every",
}
}


# NOTE: Please do not change these dicts. Use the CSVs for customization.
# See https://www.cursorless.org/docs/user/customization/
selection_types = {
# Text-based scope types
"block": "paragraph",
"cell": "notebookCell",
"file": "document",
Expand All @@ -73,6 +56,18 @@ def cursorless_containing_scope(m) -> dict[str, dict[str, Any]]:
"token": "token",
}


@mod.capture(rule="[every] {user.cursorless_scope_type}")
def cursorless_containing_scope(m) -> dict[str, Any]:
"""Expand to containing scope"""
return {
"type": "everyScope" if m[0] == "every" else "containingScope",
"scopeType": {
"type": m.cursorless_scope_type,
},
}


# NOTE: Please do not change these dicts. Use the CSVs for customization.
# See https://www.cursorless.org/docs/user/customization/
subtoken_scope_types = {
Expand All @@ -90,7 +85,6 @@ def cursorless_containing_scope(m) -> dict[str, dict[str, Any]]:

default_values = {
"scope_type": scope_types,
"selection_type": selection_types,
"subtoken_scope_type": subtoken_scope_types,
"surrounding_pair_scope_type": surrounding_pair_scope_types,
}
Expand Down
7 changes: 3 additions & 4 deletions cursorless-talon/src/modifiers/head_tail.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from dataclasses import dataclass
from typing import Any

from talon import Module

Expand All @@ -22,9 +23,7 @@ class HeadTail:


@mod.capture(rule="{user.cursorless_head_tail}")
def cursorless_head_tail(m) -> dict:
def cursorless_head_tail(m) -> dict[str, Any]:
return {
"modifier": {
"type": head_tail_map[m.cursorless_head_tail],
}
"type": head_tail_map[m.cursorless_head_tail],
}
16 changes: 16 additions & 0 deletions cursorless-talon/src/modifiers/interior_boundary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from talon import Module

mod = Module()

mod.list(
"cursorless_delimiter_inclusion",
desc="Inside or boundary delimiter inclusion",
)


@mod.capture(rule="{user.cursorless_delimiter_inclusion}")
def cursorless_delimiter_inclusion(m) -> dict[str, str]:
"""Inside or boundary delimiter inclusion"""
return {
"type": m.cursorless_delimiter_inclusion,
}
12 changes: 7 additions & 5 deletions cursorless-talon/src/modifiers/position.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from typing import Any

from talon import Context, Module

mod = Module()
ctx = Context()


positions = {
"after": {"position": "after"},
"before": {"position": "before"},
"start of": {"position": "before", "insideOutsideType": "inside"},
"end of": {"position": "after", "insideOutsideType": "inside"},
"after": {"position": "after"},
"start of": {"position": "start"},
"end of": {"position": "end"},
# Disabled for now because "below" can misrecognize with "blue" and we may move away from allowing positional modifiers in arbitrary places anyway
# "above": {"position": "before", **LINE.json_repr},
# "below": {"position": "after", **LINE.json_repr}
Expand All @@ -19,5 +21,5 @@


@mod.capture(rule="{user.cursorless_position}")
def cursorless_position(m) -> str:
return positions[m.cursorless_position]
def cursorless_position(m) -> dict[str, Any]:
return {"type": "position", **positions[m.cursorless_position]}
11 changes: 0 additions & 11 deletions cursorless-talon/src/modifiers/selection_type.py

This file was deleted.

15 changes: 8 additions & 7 deletions cursorless-talon/src/modifiers/sub_token.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any

from talon import Module

from ..compound_targets import is_active_included, is_anchor_included
Expand Down Expand Up @@ -47,21 +49,20 @@ def cursorless_first_last_range(m) -> str:

@mod.capture(
rule=(
"(<user.cursorless_ordinal_range> | <user.cursorless_first_last_range>)"
"(<user.cursorless_ordinal_range> | <user.cursorless_first_last_range>) "
"{user.cursorless_subtoken_scope_type}"
)
)
def cursorless_subtoken_scope(m) -> str:
def cursorless_subtoken_scope(m) -> dict[str, Any]:
"""Subtoken ranges such as subwords or characters"""
try:
range = m.cursorless_ordinal_range
except AttributeError:
range = m.cursorless_first_last_range
return {
"selectionType": "token",
"modifier": {
"type": "subpiece",
"pieceType": m.cursorless_subtoken_scope_type,
**range,
"type": "ordinalRange",
"scopeType": {
"type": m.cursorless_subtoken_scope_type,
},
**range,
}
63 changes: 25 additions & 38 deletions cursorless-talon/src/modifiers/surrounding_pair.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
from contextlib import suppress
from typing import Any

from talon import Context, Module

from ..paired_delimiter import paired_delimiters_map
Expand All @@ -6,11 +9,6 @@
ctx = Context()


mod.list(
"cursorless_delimiter_inclusion",
desc="Whether to include delimiters in surrounding range",
)

mod.list(
"cursorless_delimiter_force_direction",
desc="Can be used to force an ambiguous delimiter to extend in one direction",
Expand All @@ -20,8 +18,6 @@
"right",
]

# NB: This is a hack until we support having inside and outside on arbitrary
# scope types
mod.list(
"cursorless_surrounding_pair_scope_type",
desc="Scope types that can function as surrounding pairs",
Expand All @@ -30,48 +26,39 @@

@mod.capture(
rule=(
"[{user.cursorless_delimiter_inclusion}] [{user.cursorless_delimiter_force_direction}] <user.cursorless_surrounding_pair_scope_type> | "
"{user.cursorless_delimiter_inclusion} [{user.cursorless_delimiter_force_direction}]"
"<user.cursorless_selectable_paired_delimiter> |"
"{user.cursorless_surrounding_pair_scope_type}"
)
)
def cursorless_surrounding_pair(m) -> str:
"""Surrounding pair modifier"""
def cursorless_surrounding_pair_scope_type(m) -> str:
"""Surrounding pair scope type"""
try:
return m.cursorless_surrounding_pair_scope_type
except AttributeError:
return paired_delimiters_map[
m.cursorless_selectable_paired_delimiter
].cursorlessIdentifier


@mod.capture(
rule="[{user.cursorless_delimiter_force_direction}] <user.cursorless_surrounding_pair_scope_type>"
)
def cursorless_surrounding_pair(m) -> dict[str, Any]:
"""Expand to containing surrounding pair"""
try:
surrounding_pair_scope_type = m.cursorless_surrounding_pair_scope_type
except AttributeError:
surrounding_pair_scope_type = "any"

modifier = {
scope_type = {
"type": "surroundingPair",
"delimiter": surrounding_pair_scope_type,
}

try:
modifier["delimiterInclusion"] = m.cursorless_delimiter_inclusion
except AttributeError:
pass

try:
modifier["forceDirection"] = m.cursorless_delimiter_force_direction
except AttributeError:
pass
with suppress(AttributeError):
scope_type["forceDirection"] = m.cursorless_delimiter_force_direction

return {
"modifier": modifier,
"type": "containingScope",
"scopeType": scope_type,
}


@mod.capture(
rule=(
"<user.cursorless_selectable_paired_delimiter> |"
"{user.cursorless_surrounding_pair_scope_type}"
)
)
def cursorless_surrounding_pair_scope_type(m) -> str:
"""Surrounding pair scope type"""
try:
return m.cursorless_surrounding_pair_scope_type
except AttributeError:
return paired_delimiters_map[
m.cursorless_selectable_paired_delimiter
].cursorlessIdentifier
Loading

0 comments on commit b6533c5

Please sign in to comment.