Skip to content

Commit 6b45a52

Browse files
AndreasArvidssonpokeypre-commit-ci-lite[bot]
authored
Added insertions scope for insert snippet action (#1879)
`snip funk after air` ## Checklist - [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 - [x] Add changelog file --------- Co-authored-by: Pokey Rule <[email protected]> Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
1 parent 4538aba commit 6b45a52

File tree

9 files changed

+107
-21
lines changed

9 files changed

+107
-21
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
tags: [enhancement, talon]
3+
pullRequest: 1879
4+
---
5+
6+
- Added `destination: CursorlessDestination` and `scope_type: Optional[Union[str, list[str]]]` arguments to the public Talon api action `user.cursorless_insert_snippet`. See the [talon-side api docs](https://www.cursorless.org/docs/user/customization/#public-talon-actions) for more.

cursorless-talon-dev/src/cursorless_test.talon

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ test api insert <user.word> and <user.word> <user.cursorless_destination>:
1313
user.cursorless_insert(cursorless_destination, word_list)
1414
test api insert snippet:
1515
user.cursorless_insert_snippet("Hello, $foo! My name is $bar!")
16+
test api insert snippet <user.cursorless_destination> :
17+
user.cursorless_insert_snippet("Hello, $foo! My name is $bar!", cursorless_destination, "statement")
1618
test api insert snippet by name:
1719
user.cursorless_insert_snippet_by_name("functionDeclaration")
1820
test api wrap with snippet <user.cursorless_target>:

cursorless-talon/src/snippets.py

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import dataclass
2-
from typing import Any, Optional
2+
from typing import Any, Optional, Union
33

44
from talon import Module, actions
55

@@ -90,14 +90,20 @@ def insert_named_snippet(
9090
insert_snippet(snippet, destination)
9191

9292

93-
def insert_custom_snippet(body: str, destination: CursorlessDestination):
94-
insert_snippet(
95-
{
96-
"type": "custom",
97-
"body": body,
98-
},
99-
destination,
100-
)
93+
def insert_custom_snippet(
94+
body: str,
95+
destination: CursorlessDestination,
96+
scope_types: Optional[list[dict]] = None,
97+
):
98+
snippet = {
99+
"type": "custom",
100+
"body": body,
101+
}
102+
103+
if scope_types:
104+
snippet["scopeTypes"] = scope_types
105+
106+
insert_snippet(snippet, destination)
101107

102108

103109
@mod.action_class
@@ -127,12 +133,21 @@ def cursorless_insert_snippet_by_name(name: str):
127133
ImplicitDestination(),
128134
)
129135

130-
def cursorless_insert_snippet(body: str):
136+
def cursorless_insert_snippet(
137+
body: str,
138+
destination: Optional[CursorlessDestination] = ImplicitDestination(),
139+
scope_type: Optional[Union[str, list[str]]] = None,
140+
):
131141
"""Cursorless: Insert custom snippet <body>"""
132-
insert_custom_snippet(
133-
body,
134-
ImplicitDestination(),
135-
)
142+
if isinstance(scope_type, str):
143+
scope_type = [scope_type]
144+
145+
if scope_type is not None:
146+
scope_types = [{"type": st} for st in scope_type]
147+
else:
148+
scope_types = None
149+
150+
insert_custom_snippet(body, destination, scope_types)
136151

137152
def cursorless_wrap_with_snippet_by_name(
138153
name: str, variable_name: str, target: CursorlessTarget

docs/user/customization.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,8 @@ Cursorless exposes a couple talon actions and captures that you can use to defin
126126
See [snippets](./experimental/snippets.md) for more information about Cursorless snippets.
127127

128128
- `user.cursorless_insert_snippet_by_name(name: str)`: Insert a snippet with the given name, eg `functionDeclaration`
129-
- `user.cursorless_insert_snippet(body: str)`: Insert a snippet with the given body defined using our snippet body syntax (see the [snippet format docs](./experimental/snippet-format.md)). The body should be a single string, which could contain newline `\n` characters, rather than a list of strings as is expected in our snippet json representation.
130-
- `user.cursorless_wrap_with_snippet_by_name(name: str, variable_name: str, target: dict)`: Wrap the given target with a snippet with the given name, eg `functionDeclaration`. Note that `variable_name` should be one of the variables defined in the named snippet. Eg, if the named snippet has a variable `$foo`, you can pass in `"foo"` for `variable_name`, and `target` will be inserted into the position of `$foo` in the given named snippet.
129+
- `user.cursorless_insert_snippet(body: str, destination: Optional[CursorlessDestination], scope_type: Optional[Union[str, list[str]]])`: Insert a snippet with the given body defined using our snippet body syntax (see the [snippet format docs](./experimental/snippet-format.md)). The body should be a single string, which could contain newline `\n` characters, rather than a list of strings as is expected in our snippet json representation. Destination is where the snippet will be inserted. If omitted will default to current selection. An optional scope type can be provided for the target to expand to. `"snip if after air"` for example could be desired to go after the statement containing `air` instead of the token.
130+
- `user.cursorless_wrap_with_snippet_by_name(name: str, variable_name: str, target: CursorlessTarget)`: Wrap the given target with a snippet with the given name, eg `functionDeclaration`. Note that `variable_name` should be one of the variables defined in the named snippet. Eg, if the named snippet has a variable `$foo`, you can pass in `"foo"` for `variable_name`, and `target` will be inserted into the position of `$foo` in the given named snippet.
131131
- `user.cursorless_wrap_with_snippet(body, target, variable_name, scope)`: Wrap the given target with a snippet with the given body defined using our snippet body syntax (see the [snippet format docs](./experimental/snippet-format.md)). The body should be a single string, which could contain newline `\n` characters, rather than a list of strings as is expected in our snippet json representation. Note that `variable_name` should be one of the variables defined in `body`. Eg, if `body` has a variable `$foo`, you can pass in `"foo"` for `variable_name`, and `target` will be inserted into the position of `$foo` in the given named snippet. The `scope` variable can be used to automatically expand the target to the given scope type, eg `"line"`.
132132

133133
### Example of combining capture and action

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ interface NamedInsertSnippetArg {
136136
interface CustomInsertSnippetArg {
137137
type: "custom";
138138
body: string;
139-
scopeType?: ScopeType;
139+
scopeTypes?: ScopeType[];
140140
substitutions?: Record<string, string>;
141141
}
142142
export type InsertSnippetArg = NamedInsertSnippetArg | CustomInsertSnippetArg;

packages/cursorless-engine/src/actions/InsertSnippet.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,7 @@ export default class InsertSnippet {
6767
type: scopeTypeType,
6868
}));
6969
} else {
70-
return snippetDescription.scopeType == null
71-
? []
72-
: [snippetDescription.scopeType];
70+
return snippetDescription.scopeTypes ?? [];
7371
}
7472
}
7573

packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,19 @@ const insertSnippetAction: ActionDescriptor = {
4646
body: "Hello, $foo! My name is $bar!",
4747
},
4848
};
49+
const insertSnippetWithScopeAction: ActionDescriptor = {
50+
name: "insertSnippet",
51+
destination: {
52+
type: "primitive",
53+
insertionMode: "after",
54+
target: decoratedPrimitiveTarget("a"),
55+
},
56+
snippetDescription: {
57+
type: "custom",
58+
body: "Hello, $foo! My name is $bar!",
59+
scopeTypes: [{ type: "statement" }],
60+
},
61+
};
4962
const insertSnippetByNameAction: ActionDescriptor = {
5063
name: "insertSnippet",
5164
destination: { type: "implicit" },
@@ -97,6 +110,10 @@ export const talonApiFixture = [
97110
insertMultipleWordsTargetAction,
98111
),
99112
spokenFormTest("test api insert snippet", insertSnippetAction),
113+
spokenFormTest(
114+
"test api insert snippet after air",
115+
insertSnippetWithScopeAction,
116+
),
100117
spokenFormTest("test api insert snippet by name", insertSnippetByNameAction),
101118
spokenFormTest("test api wrap with snippet this", wrapWithSnippetAction),
102119
spokenFormTest(

packages/cursorless-vscode-e2e/src/suite/fixtures/recorded/actions/snippets/customInsertAfterWhale2.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ command:
77
args:
88
- type: custom
99
body: "dummy snippet hole1: ($hole1), hole2: ($hole2)"
10-
scopeType: {type: line}
10+
scopeTypes:
11+
- {type: line}
1112
targets:
1213
- type: primitive
1314
mark: {type: decoratedSymbol, symbolColor: default, character: w}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
languageId: python
2+
command:
3+
version: 6
4+
spokenForm: snip print after pit
5+
action:
6+
name: insertSnippet
7+
snippetDescription:
8+
type: custom
9+
body: print($0)
10+
scopeTypes:
11+
- {type: namedFunction}
12+
- {type: statement}
13+
destination:
14+
type: primitive
15+
insertionMode: after
16+
target:
17+
type: primitive
18+
mark: {type: decoratedSymbol, symbolColor: default, character: p}
19+
usePrePhraseSnapshot: true
20+
spokenFormError: Custom insertion snippet
21+
initialState:
22+
documentContents: |
23+
def my_funk():
24+
print("allow")
25+
selections:
26+
- anchor: {line: 0, character: 0}
27+
active: {line: 0, character: 0}
28+
marks:
29+
default.p:
30+
start: {line: 1, character: 4}
31+
end: {line: 1, character: 9}
32+
finalState:
33+
documentContents: |
34+
def my_funk():
35+
print("allow")
36+
37+
print()
38+
selections:
39+
- anchor: {line: 3, character: 6}
40+
active: {line: 3, character: 6}
41+
thatMark:
42+
- type: UntypedTarget
43+
contentRange:
44+
start: {line: 3, character: 0}
45+
end: {line: 3, character: 7}
46+
isReversed: false
47+
hasExplicitRange: true

0 commit comments

Comments
 (0)