Skip to content

Commit

Permalink
Added spread support for relative and ordinal scopes (#2254)
Browse files Browse the repository at this point in the history
`"change every two tokens"`

Fixes #1514

## Checklist

- [x] I have added
[tests](https://www.cursorless.org/docs/contributing/test-case-recorder/)
- [x] I have updated the
[cheatsheet](https://github.com/cursorless-dev/cursorless/tree/main/cursorless-talon/src/cheatsheet)
- [x] I have updated the
[docs](https://github.com/cursorless-dev/cursorless/tree/main/docs)
- [x] I have not broken the cheatsheet

---------

Co-authored-by: Pokey Rule <755842+pokey@users.noreply.github.com>
  • Loading branch information
AndreasArvidsson and pokey authored Mar 25, 2024
1 parent cabdcdf commit e96f445
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 52 deletions.
81 changes: 57 additions & 24 deletions src/cheatsheet/sections/modifiers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
from itertools import chain
from typing import TypedDict

from ..get_list import get_raw_list, make_dict_readable

MODIFIER_LIST_NAMES = [
"simple_modifier",
"interior_modifier",
"head_tail_modifier",
"simple_scope_modifier",
"every_scope_modifier",
"ancestor_scope_modifier",
"first_modifier",
"last_modifier",
"previous_next_modifier",
Expand Down Expand Up @@ -132,10 +136,6 @@ def get_modifiers():
"spokenForm": f"<ordinal> {complex_modifiers['next']} <scope>",
"description": "<ordinal> instance of <scope> after target",
},
{
"spokenForm": f"{complex_modifiers['previous']} <number> <scope>s",
"description": "previous <number> instances of <scope>",
},
{
"spokenForm": f"<scope> {complex_modifiers['backward']}",
"description": "single instance of <scope> including target, going backwards",
Expand All @@ -144,18 +144,25 @@ def get_modifiers():
"spokenForm": f"<scope> {complex_modifiers['forward']}",
"description": "single instance of <scope> including target, going forwards",
},
{
"spokenForm": f"<number> <scope>s {complex_modifiers['backward']}",
"description": "<number> instances of <scope> including target, going backwards",
},
{
"spokenForm": "<number> <scope>s",
"description": "<number> instances of <scope> including target, going forwards",
},
{
"spokenForm": f"{complex_modifiers['next']} <number> <scope>s",
"description": "next <number> instances of <scope>",
},
*generateOptionalEvery(
complex_modifiers["every"],
{
"spokenForm": f"<number> <scope>s {complex_modifiers['backward']}",
"description": "<number> instances of <scope> including target, going backwards",
},
{
"spokenForm": "<number> <scope>s",
"description": "<number> instances of <scope> including target, going forwards",
},
{
"spokenForm": f"{complex_modifiers['previous']} <number> <scope>s",
"description": "previous <number> instances of <scope>",
},
{
"spokenForm": f"{complex_modifiers['next']} <number> <scope>s",
"description": "next <number> instances of <scope>",
},
),
],
},
{
Expand All @@ -170,14 +177,40 @@ def get_modifiers():
"spokenForm": f"<ordinal> {complex_modifiers['last']} <scope>",
"description": "<ordinal>-to-last instance of <scope> in iteration scope",
},
*generateOptionalEvery(
complex_modifiers["every"],
{
"spokenForm": f"{complex_modifiers['first']} <number> <scope>s",
"description": "first <number> instances of <scope> in iteration scope",
},
{
"spokenForm": f"{complex_modifiers['last']} <number> <scope>s",
"description": "last <number> instances of <scope> in iteration scope",
},
),
],
},
]


class Entry(TypedDict):
spokenForm: str
description: str


def generateOptionalEvery(every: str, *entries: Entry) -> list[Entry]:
return list(
chain.from_iterable(
[
{
"spokenForm": f"{complex_modifiers['first']} <number> <scope>s",
"description": "First <number> instances of <scope> in iteration scope",
"spokenForm": entry["spokenForm"],
"description": f"{entry['description']}, as contiguous range",
},
{
"spokenForm": f"{complex_modifiers['last']} <number> <scope>s",
"description": "Last <number> instances of <scope> in iteration scope",
"spokenForm": f"{every} {entry['spokenForm']}",
"description": f"{entry['description']}, as individual targets",
},
],
},
]
]
for entry in entries
)
)
27 changes: 22 additions & 5 deletions src/modifiers/ordinal_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,27 @@ def cursorless_ordinal_range(m) -> dict[str, Any]:


@mod.capture(
rule="({user.cursorless_first_modifier} | {user.cursorless_last_modifier}) <user.private_cursorless_number_small> <user.cursorless_scope_type_plural>"
rule=(
"[{user.cursorless_every_scope_modifier}] "
"({user.cursorless_first_modifier} | {user.cursorless_last_modifier}) "
"<user.private_cursorless_number_small> <user.cursorless_scope_type_plural>"
),
)
def cursorless_first_last(m) -> dict[str, Any]:
"""First/last `n` scopes; eg "first three funks"""
if m[0] == "first":
is_every = hasattr(m, "cursorless_every_scope_modifier")
if hasattr(m, "cursorless_first_modifier"):
return create_ordinal_scope_modifier(
m.cursorless_scope_type_plural, 0, m.private_cursorless_number_small
m.cursorless_scope_type_plural,
0,
m.private_cursorless_number_small,
is_every,
)
return create_ordinal_scope_modifier(
m.cursorless_scope_type_plural,
-m.private_cursorless_number_small,
m.private_cursorless_number_small,
is_every,
)


Expand All @@ -65,10 +74,18 @@ def cursorless_ordinal_scope(m) -> dict[str, Any]:
return m[0]


def create_ordinal_scope_modifier(scope_type: dict, start: int, length: int = 1):
return {
def create_ordinal_scope_modifier(
scope_type: dict,
start: int,
length: int = 1,
is_every: bool = False,
):
res = {
"type": "ordinalScope",
"scopeType": scope_type,
"start": start,
"length": length,
}
if is_every:
res["isEvery"] = True
return res
19 changes: 15 additions & 4 deletions src/modifiers/relative_scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ def cursorless_relative_scope_singular(m) -> dict[str, Any]:
getattr(m, "ordinals_small", 1),
1,
m.cursorless_relative_direction,
False,
)


@mod.capture(
rule="<user.cursorless_relative_direction> <user.private_cursorless_number_small> <user.cursorless_scope_type_plural>"
rule="[{user.cursorless_every_scope_modifier}] <user.cursorless_relative_direction> <user.private_cursorless_number_small> <user.cursorless_scope_type_plural>"
)
def cursorless_relative_scope_plural(m) -> dict[str, Any]:
"""Relative previous/next plural scope. `next three funks`"""
Expand All @@ -39,11 +40,12 @@ def cursorless_relative_scope_plural(m) -> dict[str, Any]:
1,
m.private_cursorless_number_small,
m.cursorless_relative_direction,
hasattr(m, "cursorless_every_scope_modifier"),
)


@mod.capture(
rule="<user.private_cursorless_number_small> <user.cursorless_scope_type_plural> [{user.cursorless_forward_backward_modifier}]"
rule="[{user.cursorless_every_scope_modifier}] <user.private_cursorless_number_small> <user.cursorless_scope_type_plural> [{user.cursorless_forward_backward_modifier}]"
)
def cursorless_relative_scope_count(m) -> dict[str, Any]:
"""Relative count scope. `three funks`"""
Expand All @@ -52,6 +54,7 @@ def cursorless_relative_scope_count(m) -> dict[str, Any]:
0,
m.private_cursorless_number_small,
getattr(m, "cursorless_forward_backward_modifier", "forward"),
hasattr(m, "cursorless_every_scope_modifier"),
)


Expand All @@ -65,6 +68,7 @@ def cursorless_relative_scope_one_backward(m) -> dict[str, Any]:
0,
1,
m.cursorless_forward_backward_modifier,
False,
)


Expand All @@ -82,12 +86,19 @@ def cursorless_relative_scope(m) -> dict[str, Any]:


def create_relative_scope_modifier(
scope_type: dict, offset: int, length: int, direction: str
scope_type: dict,
offset: int,
length: int,
direction: str,
is_every: bool,
) -> dict[str, Any]:
return {
res = {
"type": "relativeScope",
"scopeType": scope_type,
"offset": offset,
"length": length,
"direction": direction,
}
if is_every:
res["isEvery"] = True
return res
40 changes: 22 additions & 18 deletions src/modifiers/simple_scope_modifier.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,35 @@
mod = Module()

mod.list(
"cursorless_simple_scope_modifier",
desc='Cursorless simple scope modifiers, eg "every"',
"cursorless_every_scope_modifier",
desc="Cursorless every scope modifiers",
)
mod.list(
"cursorless_ancestor_scope_modifier",
desc="Cursorless ancestor scope modifiers",
)


@mod.capture(
rule="[{user.cursorless_simple_scope_modifier}] <user.cursorless_scope_type>"
rule=(
"[{user.cursorless_every_scope_modifier} | {user.cursorless_ancestor_scope_modifier}] "
"<user.cursorless_scope_type>"
),
)
def cursorless_simple_scope_modifier(m) -> dict[str, Any]:
"""Containing scope, every scope, etc"""
if hasattr(m, "cursorless_simple_scope_modifier"):
modifier = m.cursorless_simple_scope_modifier

if modifier == "every":
return {
"type": "everyScope",
"scopeType": m.cursorless_scope_type,
}

if modifier == "ancestor":
return {
"type": "containingScope",
"scopeType": m.cursorless_scope_type,
"ancestorIndex": 1,
}
if hasattr(m, "cursorless_every_scope_modifier"):
return {
"type": "everyScope",
"scopeType": m.cursorless_scope_type,
}

if hasattr(m, "cursorless_ancestor_scope_modifier"):
return {
"type": "containingScope",
"scopeType": m.cursorless_scope_type,
"ancestorIndex": 1,
}

return {
"type": "containingScope",
Expand Down
3 changes: 2 additions & 1 deletion src/spoken_forms.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@
"its": "inferPreviousMark",
"visible": "visible"
},
"simple_scope_modifier": { "every": "every", "grand": "ancestor" },
"every_scope_modifier": { "every": "every" },
"ancestor_scope_modifier": { "grand": "ancestor" },
"interior_modifier": {
"inside": "interiorOnly"
},
Expand Down

0 comments on commit e96f445

Please sign in to comment.