Skip to content

Commit 4e0d562

Browse files
committed
Working spoken form sharing
1 parent 1e61ace commit 4e0d562

File tree

6 files changed

+163
-122
lines changed

6 files changed

+163
-122
lines changed

cursorless-talon/src/csv_overrides.py

Lines changed: 57 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import csv
2-
import json
32
from collections.abc import Container
43
from datetime import datetime
54
from pathlib import Path
@@ -26,73 +25,77 @@
2625
desc="The directory to use for cursorless settings csvs relative to talon user directory",
2726
)
2827

29-
default_ctx = Context()
30-
default_ctx.matches = r"""
28+
# The global context we use for our lists
29+
ctx = Context()
30+
31+
# A context that contains default vocabulary, for use in testing
32+
normalized_ctx = Context()
33+
normalized_ctx.matches = r"""
3134
tag: user.cursorless_default_vocabulary
3235
"""
3336

3437

35-
GetType = Callable[[str, str], str]
38+
ListToSpokenForms = dict[str, dict[str, str]]
3639

3740

3841
def init_csv_and_watch_changes(
3942
filename: str,
40-
default_values: dict[str, dict[str, str]],
43+
default_values: ListToSpokenForms,
44+
handle_new_values: Optional[Callable[[ListToSpokenForms], None]] = None,
4145
extra_ignored_values: Optional[list[str]] = None,
4246
allow_unknown_values: bool = False,
4347
default_list_name: Optional[str] = None,
4448
headers: list[str] = [SPOKEN_FORM_HEADER, CURSORLESS_IDENTIFIER_HEADER],
45-
ctx: Context = Context(),
4649
no_update_file: bool = False,
4750
pluralize_lists: list[str] = [],
48-
get_type: Optional[GetType] = None,
4951
):
5052
"""
5153
Initialize a cursorless settings csv, creating it if necessary, and watch
5254
for changes to the csv. Talon lists will be generated based on the keys of
5355
`default_values`. For example, if there is a key `foo`, there will be a
54-
list created called `user.cursorless_foo` that will contain entries from
55-
the original dict at the key `foo`, updated according to customization in
56-
the csv at
56+
list created called `user.cursorless_foo` that will contain entries from the
57+
original dict at the key `foo`, updated according to customization in the
58+
csv at
5759
58-
actions.path.talon_user() / "cursorless-settings" / filename
60+
```
61+
actions.path.talon_user() / "cursorless-settings" / filename
62+
```
5963
6064
Note that the settings directory location can be customized using the
6165
`user.cursorless_settings_directory` setting.
6266
6367
Args:
6468
filename (str): The name of the csv file to be placed in
65-
`cursorles-settings` dir
66-
default_values (dict[str, dict]): The default values for the lists to
67-
be customized in the given csv
68-
extra_ignored_values list[str]: Don't throw an exception if any of
69-
these appear as values; just ignore them and don't add them to any list
70-
allow_unknown_values bool: If unknown values appear, just put them in the list
71-
default_list_name Optional[str]: If unknown values are allowed, put any
72-
unknown values in this list
73-
no_update_file Optional[bool]: Set this to `TRUE` to indicate that we should
74-
not update the csv. This is used generally in case there was an issue coming up with the default set of values so we don't want to persist those to disk
75-
pluralize_lists: Create plural version of given lists
69+
`cursorles-settings` dir
70+
default_values (ListToSpokenForms): The default values for the lists to
71+
be customized in the given csv
72+
handle_new_values (Optional[Callable[[ListToSpokenForms], None]]): A
73+
callback to be called when the lists are updated
74+
extra_ignored_values (Optional[list[str]]): Don't throw an exception if
75+
any of these appear as values; just ignore them and don't add them
76+
to any list
77+
allow_unknown_values (bool): If unknown values appear, just put them in
78+
the list
79+
default_list_name (Optional[str]): If unknown values are
80+
allowed, put any unknown values in this list
81+
headers (list[str]): The headers to use for the csv
82+
no_update_file (bool): Set this to `True` to indicate that we should not
83+
update the csv. This is used generally in case there was an issue
84+
coming up with the default set of values so we don't want to persist
85+
those to disk
86+
pluralize_lists (list[str]): Create plural version of given lists
7687
"""
7788
if extra_ignored_values is None:
7889
extra_ignored_values = []
7990

8091
file_path = get_full_path(filename)
81-
output_file_path = get_output_path(filename)
8292
super_default_values = get_super_values(default_values)
8393

8494
file_path.parent.mkdir(parents=True, exist_ok=True)
8595

8696
check_for_duplicates(filename, default_values)
8797
create_default_vocabulary_dicts(default_values, pluralize_lists)
8898

89-
try:
90-
output_file_path.parent.mkdir(parents=True, exist_ok=True)
91-
except Exception:
92-
error_message = f"Error creating spoken form dir {output_file_path.parent}"
93-
print(error_message)
94-
app.notify(error_message)
95-
9699
def on_watch(path, flags):
97100
if file_path.match(path):
98101
current_values, has_errors = read_file(
@@ -109,9 +112,7 @@ def on_watch(path, flags):
109112
allow_unknown_values,
110113
default_list_name,
111114
pluralize_lists,
112-
output_file_path,
113-
get_type,
114-
ctx,
115+
handle_new_values,
115116
)
116117

117118
fs.watch(str(file_path.parent), on_watch)
@@ -132,9 +133,7 @@ def on_watch(path, flags):
132133
allow_unknown_values,
133134
default_list_name,
134135
pluralize_lists,
135-
output_file_path,
136-
get_type,
137-
ctx,
136+
handle_new_values,
138137
)
139138
else:
140139
if not no_update_file:
@@ -146,9 +145,7 @@ def on_watch(path, flags):
146145
allow_unknown_values,
147146
default_list_name,
148147
pluralize_lists,
149-
output_file_path,
150-
get_type,
151-
ctx,
148+
handle_new_values,
152149
)
153150

154151
def unsubscribe():
@@ -184,7 +181,7 @@ def create_default_vocabulary_dicts(
184181
if active_key:
185182
updated_dict[active_key] = value2
186183
default_values_updated[key] = updated_dict
187-
assign_lists_to_context(default_ctx, default_values_updated, pluralize_lists)
184+
assign_lists_to_context(normalized_ctx, default_values_updated, pluralize_lists)
188185

189186

190187
def update_dicts(
@@ -194,9 +191,7 @@ def update_dicts(
194191
allow_unknown_values: bool,
195192
default_list_name: Optional[str],
196193
pluralize_lists: list[str],
197-
output_file_path: Path,
198-
get_type: Optional[GetType],
199-
ctx: Context,
194+
handle_new_values: Optional[Callable[[ListToSpokenForms], None]],
200195
):
201196
# Create map with all default values
202197
results_map = {}
@@ -222,34 +217,32 @@ def update_dicts(
222217

223218
# Convert result map back to result list
224219
results = {res["list"]: {} for res in results_map.values()}
225-
output_file_entries = []
226220
for obj in results_map.values():
227221
value = obj["value"]
228222
key = obj["key"]
229-
spoken_forms = list(get_spoken_forms(value, key))
230-
for spoken_form in spoken_forms:
231-
results[obj["list"]][spoken_form] = value
232-
if get_type is not None and (entry_type := get_type(obj["list"], value)):
233-
output_file_entries.append(
234-
{"type": entry_type, "id": value, "spokenForms": spoken_forms}
235-
)
223+
if not is_removed(key):
224+
for k in key.split("|"):
225+
if value == "pasteFromClipboard" and k.endswith(" to"):
226+
# FIXME: This is a hack to work around the fact that the
227+
# spoken form of the `pasteFromClipboard` action used to be
228+
# "paste to", but now the spoken form is just "paste" and
229+
# the "to" is part of the positional target. Users who had
230+
# cursorless before this change would have "paste to" as
231+
# their spoken form and so would need to say "paste to to".
232+
k = k[:-3]
233+
results[obj["list"]][k.strip()] = value
236234

237235
# Assign result to talon context list
238236
assign_lists_to_context(ctx, results, pluralize_lists)
239237

240-
with open(output_file_path, "w") as out:
241-
try:
242-
out.write(json.dumps({"version": 0, "entries": output_file_entries}))
243-
except Exception:
244-
error_message = f"Error writing spoken form json {output_file_path}"
245-
print(error_message)
246-
app.notify(error_message)
238+
if handle_new_values is not None:
239+
handle_new_values(results)
247240

248241

249242
def assign_lists_to_context(
250243
ctx: Context,
251244
results: dict,
252-
pluralize_lists: Optional[list[str]],
245+
pluralize_lists: list[str],
253246
):
254247
for list_name, dict in results.items():
255248
list_singular_name = get_cursorless_list_name(list_name)
@@ -259,21 +252,6 @@ def assign_lists_to_context(
259252
ctx.lists[list_plural_name] = {pluralize(k): v for k, v in dict.items()}
260253

261254

262-
def get_spoken_forms(id: str, spoken_form: str):
263-
if not is_removed(spoken_form):
264-
for k in spoken_form.split("|"):
265-
if id == "pasteFromClipboard" and k.endswith(" to"):
266-
# FIXME: This is a hack to work around the fact that the
267-
# spoken form of the `pasteFromClipboard` action used to be
268-
# "paste to", but now the spoken form is just "paste" and
269-
# the "to" is part of the positional target. Users who had
270-
# cursorless before this change would have "paste to" as
271-
# their spoken form and so would need to say "paste to to".
272-
k = k[:-3]
273-
274-
yield k.strip()
275-
276-
277255
def update_file(
278256
path: Path,
279257
headers: list[str],
@@ -414,27 +392,20 @@ def read_file(
414392
return result, has_errors
415393

416394

417-
def get_full_path(output_file_path: str):
418-
if not output_file_path.endswith(".csv"):
419-
output_file_path = f"{output_file_path}.csv"
395+
def get_full_path(filename: str):
396+
if not filename.endswith(".csv"):
397+
filename = f"{filename}.csv"
420398

421399
user_dir: Path = actions.path.talon_user()
422400
settings_directory = Path(cursorless_settings_directory.get())
423401

424402
if not settings_directory.is_absolute():
425403
settings_directory = user_dir / settings_directory
426404

427-
return (settings_directory / output_file_path).resolve()
428-
429-
430-
def get_output_path(output_file_path: str):
431-
if output_file_path.endswith(".csv"):
432-
output_file_path = output_file_path[:-4]
433-
434-
return Path.home() / ".cursorless" / "spokenForms" / f"{output_file_path}.json"
405+
return (settings_directory / filename).resolve()
435406

436407

437-
def get_super_values(values: dict[str, dict[str, str]]):
408+
def get_super_values(values: ListToSpokenForms):
438409
result: dict[str, str] = {}
439410
for value_dict in values.values():
440411
result.update(value_dict)

cursorless-talon/src/marks/decorated_mark.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def setup_hat_styles_csv(hat_colors: dict, hat_shapes: dict):
137137
"hat_color": active_hat_colors,
138138
"hat_shape": active_hat_shapes,
139139
},
140-
[*hat_colors.values(), *hat_shapes.values()],
140+
extra_ignored_values=[*hat_colors.values(), *hat_shapes.values()],
141141
no_update_file=is_shape_error or is_color_error,
142142
)
143143

0 commit comments

Comments
 (0)