Skip to content

Commit 11b94f0

Browse files
Make cheat sheet handle missing spoken forms (#2760)
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
1 parent be00aa9 commit 11b94f0

File tree

9 files changed

+423
-308
lines changed

9 files changed

+423
-308
lines changed

src/cheatsheet/get_list.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,23 @@ def get_raw_list(name: str) -> Mapping[str, str]:
4141
return typing.cast(dict[str, str], registry.lists[cursorless_list_name][0]).copy()
4242

4343

44-
def get_spoken_form_from_list(list_name: str, value: str) -> str:
44+
def get_spoken_form_from_list(list_name: str, value: str) -> str | None:
4545
"""Get the spoken form of a value from a list.
4646
4747
Args:
4848
list_name (str): The name of the list.
4949
value (str): The value to look up.
5050
5151
Returns:
52-
str: The spoken form of the value.
52+
str: The spoken form of the value if found, otherwise None.
5353
"""
5454
return next(
55-
spoken_form for spoken_form, v in get_raw_list(list_name).items() if v == value
55+
(
56+
spoken_form
57+
for spoken_form, v in get_raw_list(list_name).items()
58+
if v == value
59+
),
60+
None,
5661
)
5762

5863

src/cheatsheet/sections/actions.py

Lines changed: 101 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
from typing import Callable
2+
13
from ...actions.actions import ACTION_LIST_NAMES
2-
from ..get_list import get_raw_list, make_dict_readable
4+
from ..get_list import ListItemDescriptor, get_raw_list, make_dict_readable
35

46

5-
def get_actions():
7+
def get_actions() -> list[ListItemDescriptor]:
68
all_actions = {}
79
for name in ACTION_LIST_NAMES:
810
all_actions.update(get_raw_list(name))
@@ -28,111 +30,103 @@ def get_actions():
2830
if value in multiple_target_action_names
2931
}
3032

31-
swap_connective = list(get_raw_list("swap_connective").keys())[0]
33+
swap_connectives = list(get_raw_list("swap_connective").keys())
34+
swap_connective = swap_connectives[0] if swap_connectives else None
3235

33-
return [
34-
*make_dict_readable(
35-
"action",
36-
simple_actions,
37-
{
38-
"editNewLineAfter": "Edit new line/scope after",
39-
"editNewLineBefore": "Edit new line/scope before",
40-
},
41-
),
42-
{
43-
"id": "replaceWithTarget",
44-
"type": "action",
45-
"variations": [
46-
{
47-
"spokenForm": f"{complex_actions['replaceWithTarget']} <target> <destination>",
48-
"description": "Copy <target> to <destination>",
49-
},
50-
{
51-
"spokenForm": f"{complex_actions['replaceWithTarget']} <target>",
52-
"description": "Insert copy of <target> at cursor",
53-
},
54-
],
55-
},
56-
{
57-
"id": "pasteFromClipboard",
58-
"type": "action",
59-
"variations": [
60-
{
61-
"spokenForm": f"{complex_actions['pasteFromClipboard']} <destination>",
62-
"description": "Paste from clipboard at <destination>",
63-
}
64-
],
65-
},
66-
{
67-
"id": "moveToTarget",
68-
"type": "action",
69-
"variations": [
70-
{
71-
"spokenForm": f"{complex_actions['moveToTarget']} <target> <destination>",
72-
"description": "Move <target> to <destination>",
73-
},
74-
{
75-
"spokenForm": f"{complex_actions['moveToTarget']} <target>",
76-
"description": "Move <target> to cursor position",
77-
},
78-
],
79-
},
80-
{
81-
"id": "swapTargets",
82-
"type": "action",
83-
"variations": [
84-
{
85-
"spokenForm": f"{complex_actions['swapTargets']} <target 1> {swap_connective} <target 2>",
86-
"description": "Swap <target 1> with <target 2>",
87-
},
88-
{
89-
"spokenForm": f"{complex_actions['swapTargets']} {swap_connective} <target>",
90-
"description": "Swap selection with <target>",
91-
},
92-
],
93-
},
94-
{
95-
"id": "applyFormatter",
96-
"type": "action",
97-
"variations": [
98-
{
99-
"spokenForm": f"{complex_actions['applyFormatter']} <formatter> at <target>",
100-
"description": "Reformat <target> as <formatter>",
101-
}
102-
],
103-
},
36+
items = make_dict_readable(
37+
"action",
38+
simple_actions,
10439
{
105-
"id": "callAsFunction",
106-
"type": "action",
107-
"variations": [
108-
{
109-
"spokenForm": f"{complex_actions['callAsFunction']} <target>",
110-
"description": "Call <target> on selection",
111-
},
112-
{
113-
"spokenForm": f"{complex_actions['callAsFunction']} <target 1> on <target 2>",
114-
"description": "Call <target 1> on <target 2>",
115-
},
116-
],
40+
"editNewLineAfter": "Edit new line/scope after",
41+
"editNewLineBefore": "Edit new line/scope before",
11742
},
118-
{
119-
"id": "wrapWithPairedDelimiter",
120-
"type": "action",
121-
"variations": [
122-
{
123-
"spokenForm": f"<pair> {complex_actions['wrapWithPairedDelimiter']} <target>",
124-
"description": "Wrap <target> with <pair>",
125-
}
126-
],
127-
},
128-
{
129-
"id": "rewrap",
130-
"type": "action",
131-
"variations": [
132-
{
133-
"spokenForm": f"<pair> {complex_actions['rewrap']} <target>",
134-
"description": "Rewrap <target> with <pair>",
135-
}
136-
],
137-
},
138-
]
43+
)
44+
45+
fixtures: dict[str, list[tuple[Callable, str]]] = {
46+
"replaceWithTarget": [
47+
(
48+
lambda value: f"{value} <target> <destination>",
49+
"Copy <target> to <destination>",
50+
),
51+
(
52+
lambda value: f"{value} <target>",
53+
"Insert copy of <target> at cursor",
54+
),
55+
],
56+
"pasteFromClipboard": [
57+
(
58+
lambda value: f"{value} <destination>",
59+
"Paste from clipboard at <destination>",
60+
)
61+
],
62+
"moveToTarget": [
63+
(
64+
lambda value: f"{value} <target> <destination>",
65+
"Move <target> to <destination>",
66+
),
67+
(
68+
lambda value: f"{value} <target>",
69+
"Move <target> to cursor position",
70+
),
71+
],
72+
"applyFormatter": [
73+
(
74+
lambda value: f"{value} <formatter> at <target>",
75+
"Reformat <target> as <formatter>",
76+
)
77+
],
78+
"callAsFunction": [
79+
(
80+
lambda value: f"{value} <target>",
81+
"Call <target> on selection",
82+
),
83+
(
84+
lambda value: f"{value} <target 1> on <target 2>",
85+
"Call <target 1> on <target 2>",
86+
),
87+
],
88+
"wrapWithPairedDelimiter": [
89+
(
90+
lambda value: f"<pair> {value} <target>",
91+
"Wrap <target> with <pair>",
92+
)
93+
],
94+
"rewrap": [
95+
(
96+
lambda value: f"<pair> {value} <target>",
97+
"Rewrap <target> with <pair>",
98+
)
99+
],
100+
}
101+
102+
if swap_connective:
103+
fixtures["swapTargets"] = [
104+
(
105+
lambda value: f"{value} <target 1> {swap_connective} <target 2>",
106+
"Swap <target 1> with <target 2>",
107+
),
108+
(
109+
lambda value: f"{value} {swap_connective} <target>",
110+
"Swap selection with <target>",
111+
),
112+
]
113+
114+
for action_id, variations in fixtures.items():
115+
if action_id not in complex_actions:
116+
continue
117+
action = complex_actions[action_id]
118+
items.append(
119+
{
120+
"id": action_id,
121+
"type": "action",
122+
"variations": [
123+
{
124+
"spokenForm": callback(action),
125+
"description": description,
126+
}
127+
for callback, description in variations
128+
],
129+
}
130+
)
131+
132+
return items

src/cheatsheet/sections/compound_targets.py

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from ..get_list import get_raw_list, get_spoken_form_from_list
1+
from ..get_list import ListItemDescriptor, get_raw_list, get_spoken_form_from_list
22

33
FORMATTERS = {
44
"rangeExclusive": lambda start, end: f"between {start} and {end}",
@@ -9,32 +9,42 @@
99
}
1010

1111

12-
def get_compound_targets():
12+
def get_compound_targets() -> list[ListItemDescriptor]:
1313
list_connective_term = get_spoken_form_from_list(
1414
"list_connective", "listConnective"
1515
)
1616
vertical_range_term = get_spoken_form_from_list("range_type", "verticalRange")
1717

18-
return [
19-
{
20-
"id": "listConnective",
21-
"type": "compoundTargetConnective",
22-
"variations": [
23-
{
24-
"spokenForm": f"<target 1> {list_connective_term} <target 2>",
25-
"description": "<target 1> and <target 2>",
26-
},
27-
],
28-
},
29-
*[
18+
items: list[ListItemDescriptor] = []
19+
20+
if list_connective_term:
21+
items.append(
22+
{
23+
"id": "listConnective",
24+
"type": "compoundTargetConnective",
25+
"variations": [
26+
{
27+
"spokenForm": f"<target 1> {list_connective_term} <target 2>",
28+
"description": "<target 1> and <target 2>",
29+
},
30+
],
31+
}
32+
)
33+
34+
items.extend(
35+
[
3036
get_entry(spoken_form, id)
3137
for spoken_form, id in get_raw_list("range_connective").items()
32-
],
33-
get_entry(vertical_range_term, "verticalRange"),
34-
]
38+
]
39+
)
40+
41+
if vertical_range_term:
42+
items.append(get_entry(vertical_range_term, "verticalRange"))
43+
44+
return items
3545

3646

37-
def get_entry(spoken_form, id):
47+
def get_entry(spoken_form, id) -> ListItemDescriptor:
3848
formatter = FORMATTERS[id]
3949

4050
return {

src/cheatsheet/sections/destinations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from ..get_list import get_raw_list
1+
from ..get_list import ListItemDescriptor, get_raw_list
22

33

4-
def get_destinations():
4+
def get_destinations() -> list[ListItemDescriptor]:
55
insertion_modes = {
66
**{p: "to" for p in get_raw_list("insertion_mode_to")},
77
**get_raw_list("insertion_mode_before_after"),
Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,37 @@
1-
from ..get_list import get_list, get_raw_list, make_readable
1+
from ..get_list import ListItemDescriptor, get_list, get_raw_list, make_readable
22

33

4-
def get_scope_visualizer():
5-
show_scope_visualizer = list(get_raw_list("show_scope_visualizer").keys())[0]
4+
def get_scope_visualizer() -> list[ListItemDescriptor]:
5+
show_scope_visualizers = list(get_raw_list("show_scope_visualizer").keys())
6+
show_scope_visualizer = (
7+
show_scope_visualizers[0] if show_scope_visualizers else None
8+
)
69
visualization_types = get_raw_list("visualization_type")
710

8-
return [
9-
*get_list("hide_scope_visualizer", "command"),
10-
{
11-
"id": "show_scope_visualizer",
12-
"type": "command",
13-
"variations": [
14-
{
15-
"spokenForm": f"{show_scope_visualizer} <scope>",
16-
"description": "Visualize <scope>",
17-
},
18-
*[
11+
items = get_list("hide_scope_visualizer", "command")
12+
13+
if show_scope_visualizer:
14+
items.append(
15+
{
16+
"id": "show_scope_visualizer",
17+
"type": "command",
18+
"variations": [
1919
{
20-
"spokenForm": f"{show_scope_visualizer} <scope> {spoken_form}",
21-
"description": f"Visualize <scope> {make_readable(id).lower()} range",
22-
}
23-
for spoken_form, id in visualization_types.items()
20+
"spokenForm": f"{show_scope_visualizer} <scope>",
21+
"description": "Visualize <scope>",
22+
},
23+
*[
24+
{
25+
"spokenForm": f"{show_scope_visualizer} <scope> {spoken_form}",
26+
"description": f"Visualize <scope> {make_readable(id).lower()} range",
27+
}
28+
for spoken_form, id in visualization_types.items()
29+
],
2430
],
25-
],
26-
},
31+
}
32+
)
33+
34+
items.append(
2735
{
2836
"id": "show_scope_sidebar",
2937
"type": "command",
@@ -33,5 +41,7 @@ def get_scope_visualizer():
3341
"description": "Show cursorless sidebar",
3442
},
3543
],
36-
},
37-
]
44+
}
45+
)
46+
47+
return items

0 commit comments

Comments
 (0)