Skip to content

Commit 8f6dfd1

Browse files
pokeyfidgetingbits
authored andcommitted
Add "access" scope type (cursorless-dev#1519)
- 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
1 parent 96bef49 commit 8f6dfd1

File tree

63 files changed

+1909
-40
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+1909
-40
lines changed

cursorless-talon/src/cheatsheet/get_list.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,12 @@ def make_dict_readable(
5959

6060

6161
def make_readable(text: str) -> str:
62+
text, is_private = (
63+
(text[8:], True) if text.startswith("private.") else (text, False)
64+
)
6265
text = text.replace(".", " ")
63-
return de_camel(text).lower().capitalize()
66+
text = de_camel(text).lower().capitalize()
67+
return f"{text} (PRIVATE)" if is_private else text
6468

6569

6670
def de_camel(text: str) -> str:

cursorless-talon/src/csv_overrides.py

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,13 @@ def init_csv_and_watch_changes(
3535
filename: str,
3636
default_values: dict[str, dict[str, str]],
3737
extra_ignored_values: Optional[list[str]] = None,
38+
extra_allowed_values: Optional[list[str]] = None,
3839
allow_unknown_values: bool = False,
3940
default_list_name: Optional[str] = None,
4041
headers: list[str] = [SPOKEN_FORM_HEADER, CURSORLESS_IDENTIFIER_HEADER],
4142
ctx: Context = Context(),
4243
no_update_file: bool = False,
43-
pluralize_lists: Optional[list[str]] = [],
44+
pluralize_lists: Optional[list[str]] = None,
4445
):
4546
"""
4647
Initialize a cursorless settings csv, creating it if necessary, and watch
@@ -69,8 +70,21 @@ def init_csv_and_watch_changes(
6970
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
7071
pluralize_lists: Create plural version of given lists
7172
"""
73+
# Don't allow both `extra_allowed_values` and `allow_unknown_values`
74+
assert not (extra_allowed_values and allow_unknown_values)
75+
76+
# If `extra_allowed_values` or `allow_unknown_values` is given, we need a
77+
# `default_list_name` to put unknown values in
78+
assert not (
79+
(extra_allowed_values or allow_unknown_values) and not default_list_name
80+
)
81+
7282
if extra_ignored_values is None:
7383
extra_ignored_values = []
84+
if extra_allowed_values is None:
85+
extra_allowed_values = []
86+
if pluralize_lists is None:
87+
pluralize_lists = []
7488

7589
file_path = get_full_path(filename)
7690
super_default_values = get_super_values(default_values)
@@ -83,53 +97,58 @@ def init_csv_and_watch_changes(
8397
def on_watch(path, flags):
8498
if file_path.match(path):
8599
current_values, has_errors = read_file(
86-
file_path,
87-
headers,
88-
super_default_values.values(),
89-
extra_ignored_values,
90-
allow_unknown_values,
100+
path=file_path,
101+
headers=headers,
102+
default_identifiers=super_default_values.values(),
103+
extra_ignored_values=extra_ignored_values,
104+
extra_allowed_values=extra_allowed_values,
105+
allow_unknown_values=allow_unknown_values,
91106
)
92107
update_dicts(
93-
default_values,
94-
current_values,
95-
extra_ignored_values,
96-
allow_unknown_values,
97-
default_list_name,
98-
pluralize_lists,
99-
ctx,
108+
default_values=default_values,
109+
current_values=current_values,
110+
extra_ignored_values=extra_ignored_values,
111+
extra_allowed_values=extra_allowed_values,
112+
allow_unknown_values=allow_unknown_values,
113+
default_list_name=default_list_name,
114+
pluralize_lists=pluralize_lists,
115+
ctx=ctx,
100116
)
101117

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

104120
if file_path.is_file():
105121
current_values = update_file(
106-
file_path,
107-
headers,
108-
super_default_values,
109-
extra_ignored_values,
110-
allow_unknown_values,
111-
no_update_file,
122+
path=file_path,
123+
headers=headers,
124+
default_values=super_default_values,
125+
extra_ignored_values=extra_ignored_values,
126+
extra_allowed_values=extra_allowed_values,
127+
allow_unknown_values=allow_unknown_values,
128+
no_update_file=no_update_file,
112129
)
113130
update_dicts(
114-
default_values,
115-
current_values,
116-
extra_ignored_values,
117-
allow_unknown_values,
118-
default_list_name,
119-
pluralize_lists,
120-
ctx,
131+
default_values=default_values,
132+
current_values=current_values,
133+
extra_ignored_values=extra_ignored_values,
134+
extra_allowed_values=extra_allowed_values,
135+
allow_unknown_values=allow_unknown_values,
136+
default_list_name=default_list_name,
137+
pluralize_lists=pluralize_lists,
138+
ctx=ctx,
121139
)
122140
else:
123141
if not no_update_file:
124142
create_file(file_path, headers, super_default_values)
125143
update_dicts(
126-
default_values,
127-
super_default_values,
128-
extra_ignored_values,
129-
allow_unknown_values,
130-
default_list_name,
131-
pluralize_lists,
132-
ctx,
144+
default_values=default_values,
145+
current_values=super_default_values,
146+
extra_ignored_values=extra_ignored_values,
147+
extra_allowed_values=extra_allowed_values,
148+
allow_unknown_values=allow_unknown_values,
149+
default_list_name=default_list_name,
150+
pluralize_lists=pluralize_lists,
151+
ctx=ctx,
133152
)
134153

135154
def unsubscribe():
@@ -172,6 +191,7 @@ def update_dicts(
172191
default_values: dict[str, dict],
173192
current_values: dict,
174193
extra_ignored_values: list[str],
194+
extra_allowed_values: list[str],
175195
allow_unknown_values: bool,
176196
default_list_name: Optional[str],
177197
pluralize_lists: list[str],
@@ -190,7 +210,7 @@ def update_dicts(
190210
except KeyError:
191211
if value in extra_ignored_values:
192212
pass
193-
elif allow_unknown_values:
213+
elif allow_unknown_values or value in extra_allowed_values:
194214
results_map[value] = {
195215
"key": key,
196216
"value": value,
@@ -238,15 +258,17 @@ def update_file(
238258
headers: list[str],
239259
default_values: dict[str, str],
240260
extra_ignored_values: list[str],
261+
extra_allowed_values: list[str],
241262
allow_unknown_values: bool,
242263
no_update_file: bool,
243264
):
244265
current_values, has_errors = read_file(
245-
path,
246-
headers,
247-
default_values.values(),
248-
extra_ignored_values,
249-
allow_unknown_values,
266+
path=path,
267+
headers=headers,
268+
default_identifiers=default_values.values(),
269+
extra_ignored_values=extra_ignored_values,
270+
extra_allowed_values=extra_allowed_values,
271+
allow_unknown_values=allow_unknown_values,
250272
)
251273
current_identifiers = current_values.values()
252274

@@ -311,6 +333,7 @@ def read_file(
311333
headers: list[str],
312334
default_identifiers: Container[str],
313335
extra_ignored_values: list[str],
336+
extra_allowed_values: list[str],
314337
allow_unknown_values: bool,
315338
):
316339
with open(path) as csv_file:
@@ -353,6 +376,7 @@ def read_file(
353376
if (
354377
value not in default_identifiers
355378
and value not in extra_ignored_values
379+
and value not in extra_allowed_values
356380
and not allow_unknown_values
357381
):
358382
has_errors = True

cursorless-talon/src/spoken_forms.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ def update():
7272
handle_csv(
7373
"modifier_scope_types.csv",
7474
pluralize_lists=["scope_type"],
75+
extra_allowed_values=["private.fieldAccess"],
76+
default_list_name="scope_type",
7577
),
7678
handle_csv(
7779
"experimental/wrapper_snippets.csv",

packages/common/src/types/command/PartialTargetDescriptor.types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export type SimpleScopeTypeType =
105105
| "collectionItem"
106106
| "collectionKey"
107107
| "comment"
108+
| "private.fieldAccess"
108109
| "functionCall"
109110
| "functionCallee"
110111
| "functionName"

packages/cursorless-engine/src/generateSpokenForm/defaultSpokenForms/modifiers.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ export const scopeSpokenForms = {
101101
notebookCell: "cell",
102102

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

106107
type ExtendedSurroundingPairName = SurroundingPairName | "whitespace";

packages/cursorless-engine/src/generateSpokenForm/primitiveTargetToSpokenForm.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ function scopeTypeToSpokenForm(scopeType: ScopeType): string {
196196
case "oneOf":
197197
case "customRegex":
198198
case "switchStatementSubject":
199+
case "private.fieldAccess":
199200
case "string":
200201
throw new NoSpokenFormError(`Scope type '${scopeType.type}'`);
201202
case "surroundingPair": {
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
languageId: python
2+
command:
3+
version: 5
4+
spokenForm: clear access air
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: containingScope
10+
scopeType: {type: private.fieldAccess}
11+
mark: {type: decoratedSymbol, symbolColor: default, character: a}
12+
usePrePhraseSnapshot: true
13+
spokenFormError: Scope type 'private.fieldAccess'
14+
initialState:
15+
documentContents: |
16+
aaa().bbb.ccc().ddd
17+
selections:
18+
- anchor: {line: 1, character: 0}
19+
active: {line: 1, character: 0}
20+
marks:
21+
default.a:
22+
start: {line: 0, character: 0}
23+
end: {line: 0, character: 3}
24+
finalState:
25+
documentContents: |
26+
.bbb.ccc().ddd
27+
selections:
28+
- anchor: {line: 0, character: 0}
29+
active: {line: 0, character: 0}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
languageId: python
2+
command:
3+
version: 5
4+
spokenForm: clear access bat
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: containingScope
10+
scopeType: {type: private.fieldAccess}
11+
mark: {type: decoratedSymbol, symbolColor: default, character: b}
12+
usePrePhraseSnapshot: true
13+
spokenFormError: Scope type 'private.fieldAccess'
14+
initialState:
15+
documentContents: |
16+
aaa().bbb.ccc().ddd
17+
selections:
18+
- anchor: {line: 1, character: 0}
19+
active: {line: 1, character: 0}
20+
marks:
21+
default.b:
22+
start: {line: 0, character: 6}
23+
end: {line: 0, character: 9}
24+
finalState:
25+
documentContents: |
26+
aaa().ccc().ddd
27+
selections:
28+
- anchor: {line: 0, character: 5}
29+
active: {line: 0, character: 5}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
languageId: python
2+
command:
3+
version: 5
4+
spokenForm: clear access bat
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: containingScope
10+
scopeType: {type: private.fieldAccess}
11+
mark: {type: decoratedSymbol, symbolColor: default, character: b}
12+
usePrePhraseSnapshot: true
13+
spokenFormError: Scope type 'private.fieldAccess'
14+
initialState:
15+
documentContents: |
16+
a[0].b[c.d]
17+
selections:
18+
- anchor: {line: 1, character: 0}
19+
active: {line: 1, character: 0}
20+
marks:
21+
default.b:
22+
start: {line: 0, character: 5}
23+
end: {line: 0, character: 6}
24+
finalState:
25+
documentContents: |
26+
a[0]
27+
selections:
28+
- anchor: {line: 0, character: 4}
29+
active: {line: 0, character: 4}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
languageId: python
2+
command:
3+
version: 5
4+
spokenForm: clear access bat
5+
action: {name: clearAndSetSelection}
6+
targets:
7+
- type: primitive
8+
modifiers:
9+
- type: containingScope
10+
scopeType: {type: private.fieldAccess}
11+
mark: {type: decoratedSymbol, symbolColor: default, character: b}
12+
usePrePhraseSnapshot: true
13+
spokenFormError: Scope type 'private.fieldAccess'
14+
initialState:
15+
documentContents: |
16+
aaa.bbb(
17+
ccc + 5
18+
)
19+
selections:
20+
- anchor: {line: 3, character: 0}
21+
active: {line: 3, character: 0}
22+
marks:
23+
default.b:
24+
start: {line: 0, character: 4}
25+
end: {line: 0, character: 7}
26+
finalState:
27+
documentContents: |
28+
aaa
29+
selections:
30+
- anchor: {line: 0, character: 3}
31+
active: {line: 0, character: 3}

0 commit comments

Comments
 (0)