Skip to content

Commit 4588044

Browse files
Refactored decorated marks into separate file (#1762)
## 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) - [x] I have not broken the cheatsheet --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 6d85977 commit 4588044

File tree

5 files changed

+206
-198
lines changed

5 files changed

+206
-198
lines changed
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
from pathlib import Path
2+
from typing import Any
3+
4+
from talon import Module, actions, cron, fs
5+
6+
from ..csv_overrides import init_csv_and_watch_changes
7+
8+
mod = Module()
9+
10+
mod.list("cursorless_hat_color", desc="Supported hat colors for cursorless")
11+
mod.list("cursorless_hat_shape", desc="Supported hat shapes for cursorless")
12+
mod.list(
13+
"cursorless_unknown_symbol",
14+
"This list contains the term that is used to refer to any unknown symbol",
15+
)
16+
17+
18+
@mod.capture(rule="<user.any_alphanumeric_key> | {user.cursorless_unknown_symbol}")
19+
def cursorless_grapheme(m) -> str:
20+
try:
21+
return m.any_alphanumeric_key
22+
except AttributeError:
23+
# NB: This represents unknown char in Unicode. It will be translated
24+
# to "[unk]" by Cursorless extension.
25+
return "\uFFFD"
26+
27+
28+
@mod.capture(
29+
rule="[{user.cursorless_hat_color} | gray] [{user.cursorless_hat_shape}] <user.cursorless_grapheme>"
30+
)
31+
def cursorless_decorated_symbol(m) -> dict[str, Any]:
32+
"""A decorated symbol"""
33+
if m[0] == "gray":
34+
actions.app.notify(
35+
"The color 'gray' is the default and doesn't need to be spoken out loud. Just say eg 'take air' instead of 'take gray air'"
36+
)
37+
hat_color = getattr(m, "cursorless_hat_color", "default")
38+
try:
39+
hat_style_name = f"{hat_color}-{m.cursorless_hat_shape}"
40+
except AttributeError:
41+
hat_style_name = hat_color
42+
return {
43+
"type": "decoratedSymbol",
44+
"symbolColor": hat_style_name,
45+
"character": m.cursorless_grapheme,
46+
}
47+
48+
49+
DEFAULT_COLOR_ENABLEMENT = {
50+
"blue": True,
51+
"green": True,
52+
"red": True,
53+
"pink": True,
54+
"yellow": True,
55+
"userColor1": False,
56+
"userColor2": False,
57+
}
58+
59+
DEFAULT_SHAPE_ENABLEMENT = {
60+
"ex": False,
61+
"fox": False,
62+
"wing": False,
63+
"hole": False,
64+
"frame": False,
65+
"curve": False,
66+
"eye": False,
67+
"play": False,
68+
"bolt": False,
69+
"crosshairs": False,
70+
}
71+
72+
# Fall back to full enablement in case of error reading settings file
73+
# NB: This won't actually enable all the shapes and colors extension-side.
74+
# It'll just make it so that the user can say them whether or not they are enabled
75+
FALLBACK_SHAPE_ENABLEMENT = {
76+
"ex": True,
77+
"fox": True,
78+
"wing": True,
79+
"hole": True,
80+
"frame": True,
81+
"curve": True,
82+
"eye": True,
83+
"play": True,
84+
"bolt": True,
85+
"crosshairs": True,
86+
}
87+
FALLBACK_COLOR_ENABLEMENT = DEFAULT_COLOR_ENABLEMENT
88+
89+
unsubscribe_hat_styles = None
90+
91+
92+
def setup_hat_styles_csv(hat_colors: dict, hat_shapes: dict):
93+
global unsubscribe_hat_styles
94+
95+
(
96+
color_enablement_settings,
97+
is_color_error,
98+
) = actions.user.vscode_get_setting_with_fallback(
99+
"cursorless.hatEnablement.colors",
100+
default_value={},
101+
fallback_value=FALLBACK_COLOR_ENABLEMENT,
102+
fallback_message="Error finding color enablement; falling back to full enablement",
103+
)
104+
105+
(
106+
shape_enablement_settings,
107+
is_shape_error,
108+
) = actions.user.vscode_get_setting_with_fallback(
109+
"cursorless.hatEnablement.shapes",
110+
default_value={},
111+
fallback_value=FALLBACK_SHAPE_ENABLEMENT,
112+
fallback_message="Error finding shape enablement; falling back to full enablement",
113+
)
114+
115+
color_enablement = {
116+
**DEFAULT_COLOR_ENABLEMENT,
117+
**color_enablement_settings,
118+
}
119+
shape_enablement = {
120+
**DEFAULT_SHAPE_ENABLEMENT,
121+
**shape_enablement_settings,
122+
}
123+
124+
active_hat_colors = {
125+
spoken_form: value
126+
for spoken_form, value in hat_colors.items()
127+
if color_enablement[value]
128+
}
129+
active_hat_shapes = {
130+
spoken_form: value
131+
for spoken_form, value in hat_shapes.items()
132+
if shape_enablement[value]
133+
}
134+
135+
if unsubscribe_hat_styles is not None:
136+
unsubscribe_hat_styles()
137+
138+
unsubscribe_hat_styles = init_csv_and_watch_changes(
139+
"hat_styles.csv",
140+
{
141+
"hat_color": active_hat_colors,
142+
"hat_shape": active_hat_shapes,
143+
},
144+
[*hat_colors.values(), *hat_shapes.values()],
145+
no_update_file=is_shape_error or is_color_error,
146+
)
147+
148+
if is_shape_error or is_color_error:
149+
actions.app.notify("Error reading vscode settings. Restart talon; see log")
150+
151+
152+
fast_reload_job = None
153+
slow_reload_job = None
154+
155+
156+
def init_hats(hat_colors: dict, hat_shapes: dict):
157+
setup_hat_styles_csv(hat_colors, hat_shapes)
158+
159+
vscode_settings_path: Path = actions.user.vscode_settings_path().resolve()
160+
161+
def on_watch(path, flags):
162+
global fast_reload_job, slow_reload_job
163+
cron.cancel(fast_reload_job)
164+
cron.cancel(slow_reload_job)
165+
fast_reload_job = cron.after(
166+
"500ms", lambda: setup_hat_styles_csv(hat_colors, hat_shapes)
167+
)
168+
slow_reload_job = cron.after(
169+
"10s", lambda: setup_hat_styles_csv(hat_colors, hat_shapes)
170+
)
171+
172+
fs.watch(str(vscode_settings_path), on_watch)
173+
174+
def unsubscribe():
175+
fs.unwatch(str(vscode_settings_path), on_watch)
176+
if unsubscribe_hat_styles is not None:
177+
unsubscribe_hat_styles()
178+
179+
return unsubscribe

cursorless-talon/src/marks/mark.py

Lines changed: 3 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -1,208 +1,16 @@
1-
from pathlib import Path
21
from typing import Any
32

4-
from talon import Module, actions, cron, fs
5-
6-
from ..csv_overrides import init_csv_and_watch_changes
3+
from talon import Module
74

85
mod = Module()
96

10-
mod.list("cursorless_hat_color", desc="Supported hat colors for cursorless")
11-
mod.list("cursorless_hat_shape", desc="Supported hat shapes for cursorless")
12-
mod.list("cursorless_special_mark", desc="Cursorless special marks")
13-
mod.list(
14-
"cursorless_unknown_symbol",
15-
"This list contains the term that is used to refer to any unknown symbol",
16-
)
17-
18-
# Maps from the id we use in the spoken form csv to the modifier type
19-
# expected by Cursorless extension
20-
special_marks = {
21-
"currentSelection": "cursor",
22-
"previousTarget": "that",
23-
"previousSource": "source",
24-
"nothing": "nothing",
25-
}
26-
27-
28-
@mod.capture(rule="<user.any_alphanumeric_key> | {user.cursorless_unknown_symbol}")
29-
def cursorless_grapheme(m) -> str:
30-
try:
31-
return m.any_alphanumeric_key
32-
except AttributeError:
33-
# NB: This represents unknown char in Unicode. It will be translated
34-
# to "[unk]" by Cursorless extension.
35-
return "\uFFFD"
36-
37-
38-
@mod.capture(
39-
rule="[{user.cursorless_hat_color} | gray] [{user.cursorless_hat_shape}] <user.cursorless_grapheme>"
40-
)
41-
def cursorless_decorated_symbol(m) -> dict[str, Any]:
42-
"""A decorated symbol"""
43-
if m[0] == "gray":
44-
actions.app.notify(
45-
"The color 'gray' is the default and doesn't need to be spoken out loud. Just say eg 'take air' instead of 'take gray air'"
46-
)
47-
hat_color = getattr(m, "cursorless_hat_color", "default")
48-
try:
49-
hat_style_name = f"{hat_color}-{m.cursorless_hat_shape}"
50-
except AttributeError:
51-
hat_style_name = hat_color
52-
return {
53-
"type": "decoratedSymbol",
54-
"symbolColor": hat_style_name,
55-
"character": m.cursorless_grapheme,
56-
}
57-
587

598
@mod.capture(
609
rule=(
6110
"<user.cursorless_decorated_symbol> | "
62-
"{user.cursorless_special_mark} |"
11+
"<user.cursorless_simple_mark> |"
6312
"<user.cursorless_line_number>" # row (ie absolute mod 100), up, down
6413
)
6514
)
6615
def cursorless_mark(m) -> dict[str, Any]:
67-
try:
68-
return m.cursorless_decorated_symbol
69-
except AttributeError:
70-
pass
71-
try:
72-
return {"type": special_marks[m.cursorless_special_mark]}
73-
except AttributeError:
74-
pass
75-
return m.cursorless_line_number
76-
77-
78-
DEFAULT_COLOR_ENABLEMENT = {
79-
"blue": True,
80-
"green": True,
81-
"red": True,
82-
"pink": True,
83-
"yellow": True,
84-
"userColor1": False,
85-
"userColor2": False,
86-
}
87-
88-
DEFAULT_SHAPE_ENABLEMENT = {
89-
"ex": False,
90-
"fox": False,
91-
"wing": False,
92-
"hole": False,
93-
"frame": False,
94-
"curve": False,
95-
"eye": False,
96-
"play": False,
97-
"bolt": False,
98-
"crosshairs": False,
99-
}
100-
101-
# Fall back to full enablement in case of error reading settings file
102-
# NB: This won't actually enable all the shapes and colors extension-side.
103-
# It'll just make it so that the user can say them whether or not they are enabled
104-
FALLBACK_SHAPE_ENABLEMENT = {
105-
"ex": True,
106-
"fox": True,
107-
"wing": True,
108-
"hole": True,
109-
"frame": True,
110-
"curve": True,
111-
"eye": True,
112-
"play": True,
113-
"bolt": True,
114-
"crosshairs": True,
115-
}
116-
FALLBACK_COLOR_ENABLEMENT = DEFAULT_COLOR_ENABLEMENT
117-
118-
unsubscribe_hat_styles = None
119-
120-
121-
def setup_hat_styles_csv(hat_colors: dict, hat_shapes: dict):
122-
global unsubscribe_hat_styles
123-
124-
(
125-
color_enablement_settings,
126-
is_color_error,
127-
) = actions.user.vscode_get_setting_with_fallback(
128-
"cursorless.hatEnablement.colors",
129-
default_value={},
130-
fallback_value=FALLBACK_COLOR_ENABLEMENT,
131-
fallback_message="Error finding color enablement; falling back to full enablement",
132-
)
133-
134-
(
135-
shape_enablement_settings,
136-
is_shape_error,
137-
) = actions.user.vscode_get_setting_with_fallback(
138-
"cursorless.hatEnablement.shapes",
139-
default_value={},
140-
fallback_value=FALLBACK_SHAPE_ENABLEMENT,
141-
fallback_message="Error finding shape enablement; falling back to full enablement",
142-
)
143-
144-
color_enablement = {
145-
**DEFAULT_COLOR_ENABLEMENT,
146-
**color_enablement_settings,
147-
}
148-
shape_enablement = {
149-
**DEFAULT_SHAPE_ENABLEMENT,
150-
**shape_enablement_settings,
151-
}
152-
153-
active_hat_colors = {
154-
spoken_form: value
155-
for spoken_form, value in hat_colors.items()
156-
if color_enablement[value]
157-
}
158-
active_hat_shapes = {
159-
spoken_form: value
160-
for spoken_form, value in hat_shapes.items()
161-
if shape_enablement[value]
162-
}
163-
164-
if unsubscribe_hat_styles is not None:
165-
unsubscribe_hat_styles()
166-
167-
unsubscribe_hat_styles = init_csv_and_watch_changes(
168-
"hat_styles.csv",
169-
{
170-
"hat_color": active_hat_colors,
171-
"hat_shape": active_hat_shapes,
172-
},
173-
[*hat_colors.values(), *hat_shapes.values()],
174-
no_update_file=is_shape_error or is_color_error,
175-
)
176-
177-
if is_shape_error or is_color_error:
178-
actions.app.notify("Error reading vscode settings. Restart talon; see log")
179-
180-
181-
fast_reload_job = None
182-
slow_reload_job = None
183-
184-
185-
def init_marks(hat_colors: dict, hat_shapes: dict):
186-
setup_hat_styles_csv(hat_colors, hat_shapes)
187-
188-
vscode_settings_path: Path = actions.user.vscode_settings_path().resolve()
189-
190-
def on_watch(path, flags):
191-
global fast_reload_job, slow_reload_job
192-
cron.cancel(fast_reload_job)
193-
cron.cancel(slow_reload_job)
194-
fast_reload_job = cron.after(
195-
"500ms", lambda: setup_hat_styles_csv(hat_colors, hat_shapes)
196-
)
197-
slow_reload_job = cron.after(
198-
"10s", lambda: setup_hat_styles_csv(hat_colors, hat_shapes)
199-
)
200-
201-
fs.watch(str(vscode_settings_path), on_watch)
202-
203-
def unsubscribe():
204-
fs.unwatch(str(vscode_settings_path), on_watch)
205-
if unsubscribe_hat_styles is not None:
206-
unsubscribe_hat_styles()
207-
208-
return unsubscribe
16+
return m[0]

0 commit comments

Comments
 (0)