Skip to content

Scope support tree view #1663

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 36 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9157c4b
Add tree view to indicate scope support
pokey Sep 27, 2023
05c71a6
Fixes
pokey Sep 27, 2023
ba7ac81
Almost working
pokey Sep 29, 2023
fa3164e
Initial working version
pokey Sep 29, 2023
f358dca
Cleanup
pokey Sep 29, 2023
cea0fbe
Remove undesirable pairs
pokey Sep 29, 2023
1d9fe17
Fixes; support custom regex spoken forms
pokey Oct 4, 2023
23483e1
More fixes
pokey Oct 4, 2023
8855b3d
Add icons
pokey Oct 4, 2023
e80dfec
move unspeakable to end
pokey Oct 4, 2023
961dafe
Fix package.json
pokey Oct 4, 2023
8be3ab7
Use code icons for language specific scopes
pokey Oct 5, 2023
16caab3
Add sidebar view
pokey Oct 5, 2023
98d3aee
`cursorlessScopeSupport` => `cursorless.scopeSupport`
pokey Oct 5, 2023
6a17506
tweak typing
pokey Oct 5, 2023
cd200bc
Detect which scope is being visualized
pokey Oct 5, 2023
664ec80
More sophisticated custom spoken forms
pokey Oct 6, 2023
21046a5
Remove TODO
pokey Oct 6, 2023
824c942
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Oct 6, 2023
0e6d71a
simplify icon
pokey Oct 9, 2023
534c172
Fix custom regexes
pokey Oct 10, 2023
ccde215
Move talon spoken forms json to its own file
pokey Oct 10, 2023
3dec077
more stuff
pokey Oct 10, 2023
62e56c5
Rename
pokey Oct 10, 2023
92b36e4
Tweaks
pokey Oct 10, 2023
0408fa4
Migrate ScopeProvider type to cursorless common
pokey Oct 11, 2023
4e19004
Tweaks
pokey Oct 11, 2023
1e74640
Fix spoken forms bug
pokey Oct 11, 2023
f4776fb
Revisualize on custom regex change
pokey Oct 11, 2023
736c01b
Remove some debouncing
pokey Oct 11, 2023
2b340cf
Initial working tests
pokey Oct 11, 2023
d232c30
[pre-commit.ci lite] apply automatic fixes
pre-commit-ci-lite[bot] Oct 11, 2023
f4be037
Working custom regex test
pokey Oct 11, 2023
00a10ae
Tweak
pokey Oct 11, 2023
f1daa55
Working custom spoken form tests
pokey Oct 11, 2023
357e57d
Cleanup
pokey Oct 12, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,23 @@
"!**/node_modules/**"
]
},
{
"name": "Update fixtures, unit tests only",
"type": "node",
"request": "launch",
"program": "${workspaceFolder}/packages/test-harness/out/scripts/runUnitTestsOnly",
"env": {
"CURSORLESS_TEST": "true",
"CURSORLESS_TEST_UPDATE_FIXTURES": "true",
"CURSORLESS_REPO_ROOT": "${workspaceFolder}"
},
"outFiles": ["${workspaceFolder}/**/out/**/*.js"],
"preLaunchTask": "${defaultBuildTask}",
"resolveSourceMapLocations": [
"${workspaceFolder}/**",
"!**/node_modules/**"
]
},
{
"name": "Docusaurus start",
"type": "node",
Expand Down
6 changes: 6 additions & 0 deletions cursorless-talon/src/apps/cursorless_vscode.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,9 @@ def private_cursorless_show_settings_in_ide():
)
actions.sleep("250ms")
actions.insert("cursorless")

def private_cursorless_show_sidebar():
"""Show Cursorless sidebar"""
actions.user.private_cursorless_run_rpc_command_and_wait(
"workbench.view.extension.cursorless"
)
128 changes: 91 additions & 37 deletions cursorless-talon/src/csv_overrides.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import csv
from collections.abc import Container
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Optional
from typing import Callable, Optional, TypedDict

from talon import Context, Module, actions, app, fs

Expand All @@ -25,49 +26,73 @@
desc="The directory to use for cursorless settings csvs relative to talon user directory",
)

default_ctx = Context()
default_ctx.matches = r"""
# The global context we use for our lists
ctx = Context()

# A context that contains default vocabulary, for use in testing
normalized_ctx = Context()
normalized_ctx.matches = r"""
tag: user.cursorless_default_vocabulary
"""


# Maps from Talon list name to a map from spoken form to value
ListToSpokenForms = dict[str, dict[str, str]]


@dataclass
class SpokenFormEntry:
list_name: str
id: str
spoken_forms: list[str]


def init_csv_and_watch_changes(
filename: str,
default_values: dict[str, dict[str, str]],
default_values: ListToSpokenForms,
handle_new_values: Optional[Callable[[list[SpokenFormEntry]], None]] = None,
extra_ignored_values: Optional[list[str]] = None,
allow_unknown_values: bool = False,
default_list_name: Optional[str] = None,
headers: list[str] = [SPOKEN_FORM_HEADER, CURSORLESS_IDENTIFIER_HEADER],
ctx: Context = Context(),
no_update_file: bool = False,
pluralize_lists: Optional[list[str]] = [],
pluralize_lists: list[str] = [],
):
"""
Initialize a cursorless settings csv, creating it if necessary, and watch
for changes to the csv. Talon lists will be generated based on the keys of
`default_values`. For example, if there is a key `foo`, there will be a
list created called `user.cursorless_foo` that will contain entries from
the original dict at the key `foo`, updated according to customization in
the csv at
list created called `user.cursorless_foo` that will contain entries from the
original dict at the key `foo`, updated according to customization in the
csv at

actions.path.talon_user() / "cursorless-settings" / filename
```
actions.path.talon_user() / "cursorless-settings" / filename
```

Note that the settings directory location can be customized using the
`user.cursorless_settings_directory` setting.

Args:
filename (str): The name of the csv file to be placed in
`cursorles-settings` dir
default_values (dict[str, dict]): The default values for the lists to
be customized in the given csv
extra_ignored_values list[str]: Don't throw an exception if any of
these appear as values; just ignore them and don't add them to any list
allow_unknown_values bool: If unknown values appear, just put them in the list
default_list_name Optional[str]: If unknown values are allowed, put any
unknown values in this list
no_update_file Optional[bool]: Set this to `TRUE` to indicate that we should
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
pluralize_lists: Create plural version of given lists
`cursorles-settings` dir
default_values (ListToSpokenForms): The default values for the lists to
be customized in the given csv
handle_new_values (Optional[Callable[[list[SpokenFormEntry]], None]]): A
callback to be called when the lists are updated
extra_ignored_values (Optional[list[str]]): Don't throw an exception if
any of these appear as values; just ignore them and don't add them
to any list
allow_unknown_values (bool): If unknown values appear, just put them in
the list
default_list_name (Optional[str]): If unknown values are
allowed, put any unknown values in this list
headers (list[str]): The headers to use for the csv
no_update_file (bool): Set this to `True` to indicate that we should 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
pluralize_lists (list[str]): Create plural version of given lists
"""
if extra_ignored_values is None:
extra_ignored_values = []
Expand Down Expand Up @@ -96,7 +121,7 @@ def on_watch(path, flags):
allow_unknown_values,
default_list_name,
pluralize_lists,
ctx,
handle_new_values,
)

fs.watch(str(file_path.parent), on_watch)
Expand All @@ -117,7 +142,7 @@ def on_watch(path, flags):
allow_unknown_values,
default_list_name,
pluralize_lists,
ctx,
handle_new_values,
)
else:
if not no_update_file:
Expand All @@ -129,7 +154,7 @@ def on_watch(path, flags):
allow_unknown_values,
default_list_name,
pluralize_lists,
ctx,
handle_new_values,
)

def unsubscribe():
Expand Down Expand Up @@ -165,22 +190,22 @@ def create_default_vocabulary_dicts(
if active_key:
updated_dict[active_key] = value2
default_values_updated[key] = updated_dict
assign_lists_to_context(default_ctx, default_values_updated, pluralize_lists)
assign_lists_to_context(normalized_ctx, default_values_updated, pluralize_lists)


def update_dicts(
default_values: dict[str, dict],
current_values: dict,
default_values: ListToSpokenForms,
current_values: dict[str, str],
extra_ignored_values: list[str],
allow_unknown_values: bool,
default_list_name: Optional[str],
pluralize_lists: list[str],
ctx: Context,
handle_new_values: Optional[Callable[[list[SpokenFormEntry]], None]],
):
# Create map with all default values
results_map = {}
for list_name, dict in default_values.items():
for key, value in dict.items():
results_map: dict[str, ResultsListEntry] = {}
for list_name, obj in default_values.items():
for key, value in obj.items():
results_map[value] = {"key": key, "value": value, "list": list_name}

# Update result with current values
Expand All @@ -190,7 +215,7 @@ def update_dicts(
except KeyError:
if value in extra_ignored_values:
pass
elif allow_unknown_values:
elif allow_unknown_values and default_list_name is not None:
results_map[value] = {
"key": key,
"value": value,
Expand All @@ -201,9 +226,35 @@ def update_dicts(

# Convert result map back to result list
results = {res["list"]: {} for res in results_map.values()}
for obj in results_map.values():
values: list[SpokenFormEntry] = []
for list_name, id, spoken_forms in generate_spoken_forms(
list(results_map.values())
):
for spoken_form in spoken_forms:
results[list_name][spoken_form] = id
values.append(
SpokenFormEntry(list_name=list_name, id=id, spoken_forms=spoken_forms)
)

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

if handle_new_values is not None:
handle_new_values(values)


class ResultsListEntry(TypedDict):
key: str
value: str
list: str


def generate_spoken_forms(results_list: list[ResultsListEntry]):
for obj in results_list:
value = obj["value"]
key = obj["key"]

spoken = []
if not is_removed(key):
for k in key.split("|"):
if value == "pasteFromClipboard" and k.endswith(" to"):
Expand All @@ -214,10 +265,13 @@ def update_dicts(
# cursorless before this change would have "paste to" as
# their spoken form and so would need to say "paste to to".
k = k[:-3]
results[obj["list"]][k.strip()] = value
spoken.append(k.strip())

# Assign result to talon context list
assign_lists_to_context(ctx, results, pluralize_lists)
yield (
obj["list"],
value,
spoken,
)


def assign_lists_to_context(
Expand Down Expand Up @@ -386,7 +440,7 @@ def get_full_path(filename: str):
return (settings_directory / filename).resolve()


def get_super_values(values: dict[str, dict[str, str]]):
def get_super_values(values: ListToSpokenForms):
result: dict[str, str] = {}
for value_dict in values.values():
result.update(value_dict)
Expand Down
3 changes: 3 additions & 0 deletions cursorless-talon/src/cursorless.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,6 @@
class Actions:
def private_cursorless_show_settings_in_ide():
"""Show Cursorless-specific settings in ide"""

def private_cursorless_show_sidebar():
"""Show Cursorless-specific settings in ide"""
3 changes: 3 additions & 0 deletions cursorless-talon/src/cursorless.talon
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,6 @@ tag: user.cursorless

{user.cursorless_homophone} settings:
user.private_cursorless_show_settings_in_ide()

bar {user.cursorless_homophone}:
user.private_cursorless_show_sidebar()
2 changes: 1 addition & 1 deletion cursorless-talon/src/marks/decorated_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ def setup_hat_styles_csv(hat_colors: dict[str, str], hat_shapes: dict[str, str])
"hat_color": active_hat_colors,
"hat_shape": active_hat_shapes,
},
[*hat_colors.values(), *hat_shapes.values()],
extra_ignored_values=[*hat_colors.values(), *hat_shapes.values()],
no_update_file=is_shape_error or is_color_error,
)

Expand Down
Loading