Skip to content

Commit

Permalink
Add "access" scope type (cursorless-dev#1519)
Browse files Browse the repository at this point in the history
- Implements cursorless-dev#707 for Typescript
- Implements cursorless-dev#707 for Python

## Questions

- [ ] Unfortunately, both the queries and tests are almost identically
duplicated between Typescript and Python 😕.
- [ ] I wonder if we should support some kind of templating language, eg
handlebars, along with a meta-updater-style approach where we keep the
generated files in source control as well
- [ ] For the test cases, we could maybe support a list of language ids
in the recorded test yaml 🤷‍♂️
- [ ] Also, should the `.` be considered part of the content range, or
just the removal range?
- [ ] Should a function call on its own be considered `"access"`? eg
`foo()`. What about an identifier on its own, eg `foo`?

## Checklist

- [ ] Make it so eg `bar` or `foo()` on its own is an access? That might
cover a lot of things 🤔. Eg what happens with something in parens? That
could end up as part of access, eg `(a + b).c`
- [ ] What does happen with `(a + b).c`?
- [x] I have added
[tests](https://www.cursorless.org/docs/contributing/test-case-recorder/)
- [x] 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
  • Loading branch information
pokey authored and fidgetingbits committed Nov 3, 2023
1 parent 96bef49 commit 8f6dfd1
Show file tree
Hide file tree
Showing 63 changed files with 1,909 additions and 40 deletions.
6 changes: 5 additions & 1 deletion cursorless-talon/src/cheatsheet/get_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,12 @@ def make_dict_readable(


def make_readable(text: str) -> str:
text, is_private = (
(text[8:], True) if text.startswith("private.") else (text, False)
)
text = text.replace(".", " ")
return de_camel(text).lower().capitalize()
text = de_camel(text).lower().capitalize()
return f"{text} (PRIVATE)" if is_private else text


def de_camel(text: str) -> str:
Expand Down
102 changes: 63 additions & 39 deletions cursorless-talon/src/csv_overrides.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,13 @@ def init_csv_and_watch_changes(
filename: str,
default_values: dict[str, dict[str, str]],
extra_ignored_values: Optional[list[str]] = None,
extra_allowed_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: Optional[list[str]] = None,
):
"""
Initialize a cursorless settings csv, creating it if necessary, and watch
Expand Down Expand Up @@ -69,8 +70,21 @@ def init_csv_and_watch_changes(
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
"""
# Don't allow both `extra_allowed_values` and `allow_unknown_values`
assert not (extra_allowed_values and allow_unknown_values)

# If `extra_allowed_values` or `allow_unknown_values` is given, we need a
# `default_list_name` to put unknown values in
assert not (
(extra_allowed_values or allow_unknown_values) and not default_list_name
)

if extra_ignored_values is None:
extra_ignored_values = []
if extra_allowed_values is None:
extra_allowed_values = []
if pluralize_lists is None:
pluralize_lists = []

file_path = get_full_path(filename)
super_default_values = get_super_values(default_values)
Expand All @@ -83,53 +97,58 @@ def init_csv_and_watch_changes(
def on_watch(path, flags):
if file_path.match(path):
current_values, has_errors = read_file(
file_path,
headers,
super_default_values.values(),
extra_ignored_values,
allow_unknown_values,
path=file_path,
headers=headers,
default_identifiers=super_default_values.values(),
extra_ignored_values=extra_ignored_values,
extra_allowed_values=extra_allowed_values,
allow_unknown_values=allow_unknown_values,
)
update_dicts(
default_values,
current_values,
extra_ignored_values,
allow_unknown_values,
default_list_name,
pluralize_lists,
ctx,
default_values=default_values,
current_values=current_values,
extra_ignored_values=extra_ignored_values,
extra_allowed_values=extra_allowed_values,
allow_unknown_values=allow_unknown_values,
default_list_name=default_list_name,
pluralize_lists=pluralize_lists,
ctx=ctx,
)

fs.watch(str(file_path.parent), on_watch)

if file_path.is_file():
current_values = update_file(
file_path,
headers,
super_default_values,
extra_ignored_values,
allow_unknown_values,
no_update_file,
path=file_path,
headers=headers,
default_values=super_default_values,
extra_ignored_values=extra_ignored_values,
extra_allowed_values=extra_allowed_values,
allow_unknown_values=allow_unknown_values,
no_update_file=no_update_file,
)
update_dicts(
default_values,
current_values,
extra_ignored_values,
allow_unknown_values,
default_list_name,
pluralize_lists,
ctx,
default_values=default_values,
current_values=current_values,
extra_ignored_values=extra_ignored_values,
extra_allowed_values=extra_allowed_values,
allow_unknown_values=allow_unknown_values,
default_list_name=default_list_name,
pluralize_lists=pluralize_lists,
ctx=ctx,
)
else:
if not no_update_file:
create_file(file_path, headers, super_default_values)
update_dicts(
default_values,
super_default_values,
extra_ignored_values,
allow_unknown_values,
default_list_name,
pluralize_lists,
ctx,
default_values=default_values,
current_values=super_default_values,
extra_ignored_values=extra_ignored_values,
extra_allowed_values=extra_allowed_values,
allow_unknown_values=allow_unknown_values,
default_list_name=default_list_name,
pluralize_lists=pluralize_lists,
ctx=ctx,
)

def unsubscribe():
Expand Down Expand Up @@ -172,6 +191,7 @@ def update_dicts(
default_values: dict[str, dict],
current_values: dict,
extra_ignored_values: list[str],
extra_allowed_values: list[str],
allow_unknown_values: bool,
default_list_name: Optional[str],
pluralize_lists: list[str],
Expand All @@ -190,7 +210,7 @@ def update_dicts(
except KeyError:
if value in extra_ignored_values:
pass
elif allow_unknown_values:
elif allow_unknown_values or value in extra_allowed_values:
results_map[value] = {
"key": key,
"value": value,
Expand Down Expand Up @@ -238,15 +258,17 @@ def update_file(
headers: list[str],
default_values: dict[str, str],
extra_ignored_values: list[str],
extra_allowed_values: list[str],
allow_unknown_values: bool,
no_update_file: bool,
):
current_values, has_errors = read_file(
path,
headers,
default_values.values(),
extra_ignored_values,
allow_unknown_values,
path=path,
headers=headers,
default_identifiers=default_values.values(),
extra_ignored_values=extra_ignored_values,
extra_allowed_values=extra_allowed_values,
allow_unknown_values=allow_unknown_values,
)
current_identifiers = current_values.values()

Expand Down Expand Up @@ -311,6 +333,7 @@ def read_file(
headers: list[str],
default_identifiers: Container[str],
extra_ignored_values: list[str],
extra_allowed_values: list[str],
allow_unknown_values: bool,
):
with open(path) as csv_file:
Expand Down Expand Up @@ -353,6 +376,7 @@ def read_file(
if (
value not in default_identifiers
and value not in extra_ignored_values
and value not in extra_allowed_values
and not allow_unknown_values
):
has_errors = True
Expand Down
2 changes: 2 additions & 0 deletions cursorless-talon/src/spoken_forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,8 @@ def update():
handle_csv(
"modifier_scope_types.csv",
pluralize_lists=["scope_type"],
extra_allowed_values=["private.fieldAccess"],
default_list_name="scope_type",
),
handle_csv(
"experimental/wrapper_snippets.csv",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ export type SimpleScopeTypeType =
| "collectionItem"
| "collectionKey"
| "comment"
| "private.fieldAccess"
| "functionCall"
| "functionCallee"
| "functionName"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ export const scopeSpokenForms = {
notebookCell: "cell",

switchStatementSubject: null,
["private.fieldAccess"]: null,
} as const satisfies Record<SimpleScopeTypeType, string | null>;

type ExtendedSurroundingPairName = SurroundingPairName | "whitespace";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ function scopeTypeToSpokenForm(scopeType: ScopeType): string {
case "oneOf":
case "customRegex":
case "switchStatementSubject":
case "private.fieldAccess":
case "string":
throw new NoSpokenFormError(`Scope type '${scopeType.type}'`);
case "surroundingPair": {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
languageId: python
command:
version: 5
spokenForm: clear access air
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: containingScope
scopeType: {type: private.fieldAccess}
mark: {type: decoratedSymbol, symbolColor: default, character: a}
usePrePhraseSnapshot: true
spokenFormError: Scope type 'private.fieldAccess'
initialState:
documentContents: |
aaa().bbb.ccc().ddd
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
marks:
default.a:
start: {line: 0, character: 0}
end: {line: 0, character: 3}
finalState:
documentContents: |
.bbb.ccc().ddd
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
languageId: python
command:
version: 5
spokenForm: clear access bat
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: containingScope
scopeType: {type: private.fieldAccess}
mark: {type: decoratedSymbol, symbolColor: default, character: b}
usePrePhraseSnapshot: true
spokenFormError: Scope type 'private.fieldAccess'
initialState:
documentContents: |
aaa().bbb.ccc().ddd
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
marks:
default.b:
start: {line: 0, character: 6}
end: {line: 0, character: 9}
finalState:
documentContents: |
aaa().ccc().ddd
selections:
- anchor: {line: 0, character: 5}
active: {line: 0, character: 5}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
languageId: python
command:
version: 5
spokenForm: clear access bat
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: containingScope
scopeType: {type: private.fieldAccess}
mark: {type: decoratedSymbol, symbolColor: default, character: b}
usePrePhraseSnapshot: true
spokenFormError: Scope type 'private.fieldAccess'
initialState:
documentContents: |
a[0].b[c.d]
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
marks:
default.b:
start: {line: 0, character: 5}
end: {line: 0, character: 6}
finalState:
documentContents: |
a[0]
selections:
- anchor: {line: 0, character: 4}
active: {line: 0, character: 4}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
languageId: python
command:
version: 5
spokenForm: clear access bat
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: containingScope
scopeType: {type: private.fieldAccess}
mark: {type: decoratedSymbol, symbolColor: default, character: b}
usePrePhraseSnapshot: true
spokenFormError: Scope type 'private.fieldAccess'
initialState:
documentContents: |
aaa.bbb(
ccc + 5
)
selections:
- anchor: {line: 3, character: 0}
active: {line: 3, character: 0}
marks:
default.b:
start: {line: 0, character: 4}
end: {line: 0, character: 7}
finalState:
documentContents: |
aaa
selections:
- anchor: {line: 0, character: 3}
active: {line: 0, character: 3}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
languageId: python
command:
version: 5
spokenForm: clear access cap
action: {name: clearAndSetSelection}
targets:
- type: primitive
modifiers:
- type: containingScope
scopeType: {type: private.fieldAccess}
mark: {type: decoratedSymbol, symbolColor: default, character: c}
usePrePhraseSnapshot: true
spokenFormError: Scope type 'private.fieldAccess'
initialState:
documentContents: |
aaa().bbb.ccc().ddd
selections:
- anchor: {line: 1, character: 0}
active: {line: 1, character: 0}
marks:
default.c:
start: {line: 0, character: 10}
end: {line: 0, character: 13}
finalState:
documentContents: |
aaa().bbb.ddd
selections:
- anchor: {line: 0, character: 9}
active: {line: 0, character: 9}
Loading

0 comments on commit 8f6dfd1

Please sign in to comment.