Skip to content

Commit 70a4ff9

Browse files
authored
Support inline snippets; add Talon snippet api (#1329)
- Fixes #1324 - Fixes #1325 Note that I created a Talon-side insertion api that supports the full set of options, including substitutions, targets, and scopes, but decided to leave it out for now because I'm not sure exactly how it should look and we don't need it for the mathfly support we're planning to use it for. In particular, we should probably figure out #803 before we implement the more complex api because snippets really want a destination, not a target. I did use the complex Talon-side api to record some tests of the extension api, though Here is the complex Talon snippet insertion api in case useful at some point <details><summary>Complex talon api</summary> ```diff From e39e03e3a06a6db4f1edb245a5225037d0ea08d3 Mon Sep 17 00:00:00 2001 From: Pokey Rule <[email protected]> Date: Fri, 24 Mar 2023 14:22:40 +0000 Subject: [PATCH] Complex insert snippet Talon api --- cursorless-talon/src/snippets.py | 35 ++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/cursorless-talon/src/snippets.py b/cursorless-talon/src/snippets.py index a9f100f..409512011 100644 --- a/cursorless-talon/src/snippets.py +++ b/cursorless-talon/src/snippets.py @@ -91,15 +91,34 @@ class Actions: }, ) - def cursorless_insert_snippet(body: str): + def cursorless_insert_snippet( + body: str, + target: Optional[dict] = None, + scope: Optional[str] = None, + snippet_variable: Optional[str] = None, + text: Optional[str] = None, + ): """Inserts a custom snippet""" - actions.user.cursorless_implicit_target_command( - "insertSnippet", - { - "type": "custom", - "body": body, - }, - ) + snippet_arg: dict[str, Any] = { + "type": "custom", + "body": body, + } + if scope: + snippet_arg["scopeType"] = {"type": scope} + if snippet_variable: + snippet_arg["substitutions"] = {snippet_variable: text} + + if target: + actions.user.cursorless_single_target_command_with_arg_list( + "insertSnippet", + target, + [snippet_arg], + ) + else: + actions.user.cursorless_implicit_target_command( + "insertSnippet", + snippet_arg, + ) def cursorless_wrap_with_snippet_by_name( name: str, variable_name: str, target: dict -- 2.39.2 ``` </details> ## 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
1 parent a6a8f53 commit 70a4ff9

File tree

4 files changed

+95
-11
lines changed

4 files changed

+95
-11
lines changed

src/actions/wrap.py

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from dataclasses import dataclass
2-
from typing import Literal
2+
from typing import Literal, Union
33

44
from talon import Module, actions
55

@@ -9,7 +9,7 @@
99
@dataclass
1010
class Wrapper:
1111
type: Literal["pairedDelimiter", "snippet"]
12-
extra_args: list[str]
12+
extra_args: list[Union[str, dict]]
1313

1414

1515
mod = Module()
@@ -30,7 +30,26 @@ def cursorless_wrapper(m) -> Wrapper:
3030
extra_args=[paired_delimiter_info.left, paired_delimiter_info.right],
3131
)
3232
except AttributeError:
33-
return Wrapper(type="snippet", extra_args=[m.cursorless_wrapper_snippet])
33+
snippet_name, variable_name = parse_snippet_location(
34+
m.cursorless_wrapper_snippet
35+
)
36+
return Wrapper(
37+
type="snippet",
38+
extra_args=[
39+
{
40+
"type": "named",
41+
"name": snippet_name,
42+
"variableName": variable_name,
43+
}
44+
],
45+
)
46+
47+
48+
def parse_snippet_location(snippet_location: str) -> tuple[str, str]:
49+
[snippet_name, variable_name] = snippet_location.split(".")
50+
if snippet_name is None or variable_name is None:
51+
raise Exception("Snippet location missing '.'")
52+
return (snippet_name, variable_name)
3453

3554

3655
# Maps from (action_type, wrapper_type) to action name

src/command.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
mod = Module()
1313

1414
CURSORLESS_COMMAND_ID = "cursorless.command"
15-
CURSORLESS_COMMAND_VERSION = 4
15+
CURSORLESS_COMMAND_VERSION = 5
1616
last_phrase = None
1717

1818

src/cursorless_snippets.talon

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,4 @@ tag: user.cursorless
88
user.cursorless_single_target_command(cursorless_insert_snippet_action, cursorless_positional_target, cursorless_insertion_snippet)
99

1010
{user.cursorless_insert_snippet_action} {user.cursorless_insertion_snippet_single_phrase} <user.text> [{user.cursorless_phrase_terminator}]:
11-
user.cursorless_insert_snippet_with_phrase(cursorless_insert_snippet_action, cursorless_insertion_snippet_single_phrase, text)
11+
user.private_cursorless_insert_snippet_with_phrase(cursorless_insert_snippet_action, cursorless_insertion_snippet_single_phrase, text)

src/snippets.py

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any, Optional
2+
13
from talon import Module, actions, app
24

35
from .csv_overrides import init_csv_and_watch_changes
@@ -27,13 +29,13 @@
2729
@mod.capture(
2830
rule="{user.cursorless_insertion_snippet_no_phrase} | {user.cursorless_insertion_snippet_single_phrase}"
2931
)
30-
def cursorless_insertion_snippet(m) -> str:
32+
def cursorless_insertion_snippet(m) -> dict:
3133
try:
32-
return m.cursorless_insertion_snippet_no_phrase
34+
name = m.cursorless_insertion_snippet_no_phrase
3335
except AttributeError:
34-
pass
36+
name = m.cursorless_insertion_snippet_single_phrase.split(".")[0]
3537

36-
return m.cursorless_insertion_snippet_single_phrase.split(".")[0]
38+
return {"type": "named", "name": name}
3739

3840

3941
# NOTE: Please do not change these dicts. Use the CSVs for customization.
@@ -65,13 +67,76 @@ def cursorless_insertion_snippet(m) -> str:
6567

6668
@mod.action_class
6769
class Actions:
68-
def cursorless_insert_snippet_with_phrase(
70+
def private_cursorless_insert_snippet_with_phrase(
6971
action: str, snippet_description: str, text: str
7072
):
7173
"""Perform cursorless wrap action"""
7274
snippet_name, snippet_variable = snippet_description.split(".")
7375
actions.user.cursorless_implicit_target_command(
74-
action, snippet_name, {snippet_variable: text}
76+
action,
77+
{
78+
"type": "named",
79+
"name": snippet_name,
80+
"substitutions": {snippet_variable: text},
81+
},
82+
)
83+
84+
def cursorless_insert_snippet_by_name(name: str):
85+
"""Inserts a named snippet"""
86+
actions.user.cursorless_implicit_target_command(
87+
"insertSnippet",
88+
{
89+
"type": "named",
90+
"name": name,
91+
},
92+
)
93+
94+
def cursorless_insert_snippet(body: str):
95+
"""Inserts a custom snippet"""
96+
actions.user.cursorless_implicit_target_command(
97+
"insertSnippet",
98+
{
99+
"type": "custom",
100+
"body": body,
101+
},
102+
)
103+
104+
def cursorless_wrap_with_snippet_by_name(
105+
name: str, variable_name: str, target: dict
106+
):
107+
"""Wrap target with a named snippet"""
108+
actions.user.cursorless_single_target_command_with_arg_list(
109+
"wrapWithSnippet",
110+
target,
111+
[
112+
{
113+
"type": "named",
114+
"name": name,
115+
"variableName": variable_name,
116+
}
117+
],
118+
)
119+
120+
def cursorless_wrap_with_snippet(
121+
body: str,
122+
target: dict,
123+
variable_name: Optional[str] = None,
124+
scope: Optional[str] = None,
125+
):
126+
"""Wrap target with a custom snippet"""
127+
snippet_arg: dict[str, Any] = {
128+
"type": "custom",
129+
"body": body,
130+
}
131+
if scope is not None:
132+
snippet_arg["scopeType"] = {"type": scope}
133+
if variable_name is not None:
134+
snippet_arg["variableName"] = variable_name
135+
136+
actions.user.cursorless_single_target_command_with_arg_list(
137+
"wrapWithSnippet",
138+
target,
139+
[snippet_arg],
75140
)
76141

77142

0 commit comments

Comments
 (0)