Skip to content

Commit

Permalink
Added insertions scope for insert snippet action (#1879)
Browse files Browse the repository at this point in the history
`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>
  • Loading branch information
3 people authored Nov 7, 2023
1 parent 4538aba commit 6b45a52
Show file tree
Hide file tree
Showing 9 changed files with 107 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
tags: [enhancement, talon]
pullRequest: 1879
---

- 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.
2 changes: 2 additions & 0 deletions cursorless-talon-dev/src/cursorless_test.talon
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ test api insert <user.word> and <user.word> <user.cursorless_destination>:
user.cursorless_insert(cursorless_destination, word_list)
test api insert snippet:
user.cursorless_insert_snippet("Hello, $foo! My name is $bar!")
test api insert snippet <user.cursorless_destination> :
user.cursorless_insert_snippet("Hello, $foo! My name is $bar!", cursorless_destination, "statement")
test api insert snippet by name:
user.cursorless_insert_snippet_by_name("functionDeclaration")
test api wrap with snippet <user.cursorless_target>:
Expand Down
43 changes: 29 additions & 14 deletions cursorless-talon/src/snippets.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Any, Optional
from typing import Any, Optional, Union

from talon import Module, actions

Expand Down Expand Up @@ -90,14 +90,20 @@ def insert_named_snippet(
insert_snippet(snippet, destination)


def insert_custom_snippet(body: str, destination: CursorlessDestination):
insert_snippet(
{
"type": "custom",
"body": body,
},
destination,
)
def insert_custom_snippet(
body: str,
destination: CursorlessDestination,
scope_types: Optional[list[dict]] = None,
):
snippet = {
"type": "custom",
"body": body,
}

if scope_types:
snippet["scopeTypes"] = scope_types

insert_snippet(snippet, destination)


@mod.action_class
Expand Down Expand Up @@ -127,12 +133,21 @@ def cursorless_insert_snippet_by_name(name: str):
ImplicitDestination(),
)

def cursorless_insert_snippet(body: str):
def cursorless_insert_snippet(
body: str,
destination: Optional[CursorlessDestination] = ImplicitDestination(),
scope_type: Optional[Union[str, list[str]]] = None,
):
"""Cursorless: Insert custom snippet <body>"""
insert_custom_snippet(
body,
ImplicitDestination(),
)
if isinstance(scope_type, str):
scope_type = [scope_type]

if scope_type is not None:
scope_types = [{"type": st} for st in scope_type]
else:
scope_types = None

insert_custom_snippet(body, destination, scope_types)

def cursorless_wrap_with_snippet_by_name(
name: str, variable_name: str, target: CursorlessTarget
Expand Down
4 changes: 2 additions & 2 deletions docs/user/customization.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ Cursorless exposes a couple talon actions and captures that you can use to defin
See [snippets](./experimental/snippets.md) for more information about Cursorless snippets.

- `user.cursorless_insert_snippet_by_name(name: str)`: Insert a snippet with the given name, eg `functionDeclaration`
- `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.
- `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.
- `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.
- `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.
- `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"`.

### Example of combining capture and action
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/types/command/ActionDescriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ interface NamedInsertSnippetArg {
interface CustomInsertSnippetArg {
type: "custom";
body: string;
scopeType?: ScopeType;
scopeTypes?: ScopeType[];
substitutions?: Record<string, string>;
}
export type InsertSnippetArg = NamedInsertSnippetArg | CustomInsertSnippetArg;
Expand Down
4 changes: 1 addition & 3 deletions packages/cursorless-engine/src/actions/InsertSnippet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,7 @@ export default class InsertSnippet {
type: scopeTypeType,
}));
} else {
return snippetDescription.scopeType == null
? []
: [snippetDescription.scopeType];
return snippetDescription.scopeTypes ?? [];
}
}

Expand Down
17 changes: 17 additions & 0 deletions packages/cursorless-engine/src/test/fixtures/talonApi.fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@ const insertSnippetAction: ActionDescriptor = {
body: "Hello, $foo! My name is $bar!",
},
};
const insertSnippetWithScopeAction: ActionDescriptor = {
name: "insertSnippet",
destination: {
type: "primitive",
insertionMode: "after",
target: decoratedPrimitiveTarget("a"),
},
snippetDescription: {
type: "custom",
body: "Hello, $foo! My name is $bar!",
scopeTypes: [{ type: "statement" }],
},
};
const insertSnippetByNameAction: ActionDescriptor = {
name: "insertSnippet",
destination: { type: "implicit" },
Expand Down Expand Up @@ -97,6 +110,10 @@ export const talonApiFixture = [
insertMultipleWordsTargetAction,
),
spokenFormTest("test api insert snippet", insertSnippetAction),
spokenFormTest(
"test api insert snippet after air",
insertSnippetWithScopeAction,
),
spokenFormTest("test api insert snippet by name", insertSnippetByNameAction),
spokenFormTest("test api wrap with snippet this", wrapWithSnippetAction),
spokenFormTest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ command:
args:
- type: custom
body: "dummy snippet hole1: ($hole1), hole2: ($hole2)"
scopeType: {type: line}
scopeTypes:
- {type: line}
targets:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: w}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
languageId: python
command:
version: 6
spokenForm: snip print after pit
action:
name: insertSnippet
snippetDescription:
type: custom
body: print($0)
scopeTypes:
- {type: namedFunction}
- {type: statement}
destination:
type: primitive
insertionMode: after
target:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: p}
usePrePhraseSnapshot: true
spokenFormError: Custom insertion snippet
initialState:
documentContents: |
def my_funk():
print("allow")
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
marks:
default.p:
start: {line: 1, character: 4}
end: {line: 1, character: 9}
finalState:
documentContents: |
def my_funk():
print("allow")
print()
selections:
- anchor: {line: 3, character: 6}
active: {line: 3, character: 6}
thatMark:
- type: UntypedTarget
contentRange:
start: {line: 3, character: 0}
end: {line: 3, character: 7}
isReversed: false
hasExplicitRange: true

0 comments on commit 6b45a52

Please sign in to comment.