Skip to content

Commit

Permalink
Make cheat sheet handle missing spoken forms (#2760)
Browse files Browse the repository at this point in the history
Today the cheat sheet generation crashes if the user has disabled
certain spoken forms

Fixes #2647

## Checklist

- [/] 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)
- [/] I have not broken the cheatsheet
  • Loading branch information
AndreasArvidsson authored Jan 28, 2025
1 parent be00aa9 commit 11b94f0
Show file tree
Hide file tree
Showing 9 changed files with 423 additions and 308 deletions.
11 changes: 8 additions & 3 deletions src/cheatsheet/get_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,23 @@ def get_raw_list(name: str) -> Mapping[str, str]:
return typing.cast(dict[str, str], registry.lists[cursorless_list_name][0]).copy()


def get_spoken_form_from_list(list_name: str, value: str) -> str:
def get_spoken_form_from_list(list_name: str, value: str) -> str | None:
"""Get the spoken form of a value from a list.
Args:
list_name (str): The name of the list.
value (str): The value to look up.
Returns:
str: The spoken form of the value.
str: The spoken form of the value if found, otherwise None.
"""
return next(
spoken_form for spoken_form, v in get_raw_list(list_name).items() if v == value
(
spoken_form
for spoken_form, v in get_raw_list(list_name).items()
if v == value
),
None,
)


Expand Down
208 changes: 101 additions & 107 deletions src/cheatsheet/sections/actions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from typing import Callable

from ...actions.actions import ACTION_LIST_NAMES
from ..get_list import get_raw_list, make_dict_readable
from ..get_list import ListItemDescriptor, get_raw_list, make_dict_readable


def get_actions():
def get_actions() -> list[ListItemDescriptor]:
all_actions = {}
for name in ACTION_LIST_NAMES:
all_actions.update(get_raw_list(name))
Expand All @@ -28,111 +30,103 @@ def get_actions():
if value in multiple_target_action_names
}

swap_connective = list(get_raw_list("swap_connective").keys())[0]
swap_connectives = list(get_raw_list("swap_connective").keys())
swap_connective = swap_connectives[0] if swap_connectives else None

return [
*make_dict_readable(
"action",
simple_actions,
{
"editNewLineAfter": "Edit new line/scope after",
"editNewLineBefore": "Edit new line/scope before",
},
),
{
"id": "replaceWithTarget",
"type": "action",
"variations": [
{
"spokenForm": f"{complex_actions['replaceWithTarget']} <target> <destination>",
"description": "Copy <target> to <destination>",
},
{
"spokenForm": f"{complex_actions['replaceWithTarget']} <target>",
"description": "Insert copy of <target> at cursor",
},
],
},
{
"id": "pasteFromClipboard",
"type": "action",
"variations": [
{
"spokenForm": f"{complex_actions['pasteFromClipboard']} <destination>",
"description": "Paste from clipboard at <destination>",
}
],
},
{
"id": "moveToTarget",
"type": "action",
"variations": [
{
"spokenForm": f"{complex_actions['moveToTarget']} <target> <destination>",
"description": "Move <target> to <destination>",
},
{
"spokenForm": f"{complex_actions['moveToTarget']} <target>",
"description": "Move <target> to cursor position",
},
],
},
{
"id": "swapTargets",
"type": "action",
"variations": [
{
"spokenForm": f"{complex_actions['swapTargets']} <target 1> {swap_connective} <target 2>",
"description": "Swap <target 1> with <target 2>",
},
{
"spokenForm": f"{complex_actions['swapTargets']} {swap_connective} <target>",
"description": "Swap selection with <target>",
},
],
},
{
"id": "applyFormatter",
"type": "action",
"variations": [
{
"spokenForm": f"{complex_actions['applyFormatter']} <formatter> at <target>",
"description": "Reformat <target> as <formatter>",
}
],
},
items = make_dict_readable(
"action",
simple_actions,
{
"id": "callAsFunction",
"type": "action",
"variations": [
{
"spokenForm": f"{complex_actions['callAsFunction']} <target>",
"description": "Call <target> on selection",
},
{
"spokenForm": f"{complex_actions['callAsFunction']} <target 1> on <target 2>",
"description": "Call <target 1> on <target 2>",
},
],
"editNewLineAfter": "Edit new line/scope after",
"editNewLineBefore": "Edit new line/scope before",
},
{
"id": "wrapWithPairedDelimiter",
"type": "action",
"variations": [
{
"spokenForm": f"<pair> {complex_actions['wrapWithPairedDelimiter']} <target>",
"description": "Wrap <target> with <pair>",
}
],
},
{
"id": "rewrap",
"type": "action",
"variations": [
{
"spokenForm": f"<pair> {complex_actions['rewrap']} <target>",
"description": "Rewrap <target> with <pair>",
}
],
},
]
)

fixtures: dict[str, list[tuple[Callable, str]]] = {
"replaceWithTarget": [
(
lambda value: f"{value} <target> <destination>",
"Copy <target> to <destination>",
),
(
lambda value: f"{value} <target>",
"Insert copy of <target> at cursor",
),
],
"pasteFromClipboard": [
(
lambda value: f"{value} <destination>",
"Paste from clipboard at <destination>",
)
],
"moveToTarget": [
(
lambda value: f"{value} <target> <destination>",
"Move <target> to <destination>",
),
(
lambda value: f"{value} <target>",
"Move <target> to cursor position",
),
],
"applyFormatter": [
(
lambda value: f"{value} <formatter> at <target>",
"Reformat <target> as <formatter>",
)
],
"callAsFunction": [
(
lambda value: f"{value} <target>",
"Call <target> on selection",
),
(
lambda value: f"{value} <target 1> on <target 2>",
"Call <target 1> on <target 2>",
),
],
"wrapWithPairedDelimiter": [
(
lambda value: f"<pair> {value} <target>",
"Wrap <target> with <pair>",
)
],
"rewrap": [
(
lambda value: f"<pair> {value} <target>",
"Rewrap <target> with <pair>",
)
],
}

if swap_connective:
fixtures["swapTargets"] = [
(
lambda value: f"{value} <target 1> {swap_connective} <target 2>",
"Swap <target 1> with <target 2>",
),
(
lambda value: f"{value} {swap_connective} <target>",
"Swap selection with <target>",
),
]

for action_id, variations in fixtures.items():
if action_id not in complex_actions:
continue
action = complex_actions[action_id]
items.append(
{
"id": action_id,
"type": "action",
"variations": [
{
"spokenForm": callback(action),
"description": description,
}
for callback, description in variations
],
}
)

return items
46 changes: 28 additions & 18 deletions src/cheatsheet/sections/compound_targets.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from ..get_list import get_raw_list, get_spoken_form_from_list
from ..get_list import ListItemDescriptor, get_raw_list, get_spoken_form_from_list

FORMATTERS = {
"rangeExclusive": lambda start, end: f"between {start} and {end}",
Expand All @@ -9,32 +9,42 @@
}


def get_compound_targets():
def get_compound_targets() -> list[ListItemDescriptor]:
list_connective_term = get_spoken_form_from_list(
"list_connective", "listConnective"
)
vertical_range_term = get_spoken_form_from_list("range_type", "verticalRange")

return [
{
"id": "listConnective",
"type": "compoundTargetConnective",
"variations": [
{
"spokenForm": f"<target 1> {list_connective_term} <target 2>",
"description": "<target 1> and <target 2>",
},
],
},
*[
items: list[ListItemDescriptor] = []

if list_connective_term:
items.append(
{
"id": "listConnective",
"type": "compoundTargetConnective",
"variations": [
{
"spokenForm": f"<target 1> {list_connective_term} <target 2>",
"description": "<target 1> and <target 2>",
},
],
}
)

items.extend(
[
get_entry(spoken_form, id)
for spoken_form, id in get_raw_list("range_connective").items()
],
get_entry(vertical_range_term, "verticalRange"),
]
]
)

if vertical_range_term:
items.append(get_entry(vertical_range_term, "verticalRange"))

return items


def get_entry(spoken_form, id):
def get_entry(spoken_form, id) -> ListItemDescriptor:
formatter = FORMATTERS[id]

return {
Expand Down
4 changes: 2 additions & 2 deletions src/cheatsheet/sections/destinations.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from ..get_list import get_raw_list
from ..get_list import ListItemDescriptor, get_raw_list


def get_destinations():
def get_destinations() -> list[ListItemDescriptor]:
insertion_modes = {
**{p: "to" for p in get_raw_list("insertion_mode_to")},
**get_raw_list("insertion_mode_before_after"),
Expand Down
54 changes: 32 additions & 22 deletions src/cheatsheet/sections/get_scope_visualizer.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,37 @@
from ..get_list import get_list, get_raw_list, make_readable
from ..get_list import ListItemDescriptor, get_list, get_raw_list, make_readable


def get_scope_visualizer():
show_scope_visualizer = list(get_raw_list("show_scope_visualizer").keys())[0]
def get_scope_visualizer() -> list[ListItemDescriptor]:
show_scope_visualizers = list(get_raw_list("show_scope_visualizer").keys())
show_scope_visualizer = (
show_scope_visualizers[0] if show_scope_visualizers else None
)
visualization_types = get_raw_list("visualization_type")

return [
*get_list("hide_scope_visualizer", "command"),
{
"id": "show_scope_visualizer",
"type": "command",
"variations": [
{
"spokenForm": f"{show_scope_visualizer} <scope>",
"description": "Visualize <scope>",
},
*[
items = get_list("hide_scope_visualizer", "command")

if show_scope_visualizer:
items.append(
{
"id": "show_scope_visualizer",
"type": "command",
"variations": [
{
"spokenForm": f"{show_scope_visualizer} <scope> {spoken_form}",
"description": f"Visualize <scope> {make_readable(id).lower()} range",
}
for spoken_form, id in visualization_types.items()
"spokenForm": f"{show_scope_visualizer} <scope>",
"description": "Visualize <scope>",
},
*[
{
"spokenForm": f"{show_scope_visualizer} <scope> {spoken_form}",
"description": f"Visualize <scope> {make_readable(id).lower()} range",
}
for spoken_form, id in visualization_types.items()
],
],
],
},
}
)

items.append(
{
"id": "show_scope_sidebar",
"type": "command",
Expand All @@ -33,5 +41,7 @@ def get_scope_visualizer():
"description": "Show cursorless sidebar",
},
],
},
]
}
)

return items
Loading

0 comments on commit 11b94f0

Please sign in to comment.