Skip to content

Commit 9611d3d

Browse files
AndreasArvidssonpre-commit-ci-lite[bot]phillco
authored
Generate snippets for community format (#2744)
If the user has set the `user.cursorless_use_community_snippets` tag we create a snippet in the community snippet format instead of the Cursorless one. The legacy Cursorless snippets will be removed in a final pull request. ## Checklist - [x] I have added [tests](https://www.cursorless.org/docs/contributing/test-case-recorder/) - [/] 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) - [/] I have not broken the cheatsheet --------- Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Co-authored-by: Phil Cohen <phillip@phillip.io>
1 parent 3d1447c commit 9611d3d

File tree

17 files changed

+778
-263
lines changed

17 files changed

+778
-263
lines changed

cursorless-talon/src/actions/actions.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@
5555

5656
# Don't wait for these actions to finish, usually because they hang on some kind of user interaction
5757
no_wait_actions = [
58-
"generateSnippet",
5958
"rename",
6059
]
6160

@@ -99,6 +98,8 @@ def cursorless_command(action_name: str, target: CursorlessExplicitTarget): # p
9998
)
10099
elif action_name == "callAsFunction":
101100
actions.user.private_cursorless_call(target)
101+
elif action_name == "generateSnippet":
102+
actions.user.private_cursorless_generate_snippet_action(target)
102103
elif action_name in no_wait_actions:
103104
action = {"name": action_name, "target": target}
104105
actions.user.private_cursorless_command_no_wait(action)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import glob
2+
from pathlib import Path
3+
4+
from talon import Context, Module, actions, settings
5+
6+
from ..targets.target_types import CursorlessExplicitTarget
7+
8+
mod = Module()
9+
10+
ctx = Context()
11+
ctx.matches = r"""
12+
tag: user.cursorless_use_community_snippets
13+
"""
14+
15+
16+
@mod.action_class
17+
class Actions:
18+
def private_cursorless_generate_snippet_action(target: CursorlessExplicitTarget): # pyright: ignore [reportGeneralTypeIssues]
19+
"""Generate a snippet from the given target"""
20+
actions.user.private_cursorless_command_no_wait(
21+
{
22+
"name": "generateSnippet",
23+
"target": target,
24+
}
25+
)
26+
27+
28+
@ctx.action_class("user")
29+
class UserActions:
30+
def private_cursorless_generate_snippet_action(target: CursorlessExplicitTarget): # pyright: ignore [reportGeneralTypeIssues]
31+
actions.user.private_cursorless_command_no_wait(
32+
{
33+
"name": "generateSnippet",
34+
"target": target,
35+
"directory": str(get_directory_path()),
36+
}
37+
)
38+
39+
40+
def get_directory_path() -> Path:
41+
settings_dir = get_setting_dir()
42+
if settings_dir is not None:
43+
return settings_dir
44+
return get_community_snippets_dir()
45+
46+
47+
def get_setting_dir() -> Path | None:
48+
try:
49+
setting_dir = settings.get("user.snippets_dir")
50+
if not setting_dir:
51+
return None
52+
53+
dir = Path(str(setting_dir))
54+
55+
if not dir.is_absolute():
56+
user_dir = Path(actions.path.talon_user())
57+
dir = user_dir / dir
58+
59+
return dir.resolve()
60+
except Exception:
61+
return None
62+
63+
64+
def get_community_snippets_dir() -> Path:
65+
files = glob.iglob(
66+
f"{actions.path.talon_user()}/**/snippets/snippets/*.snippet",
67+
recursive=True,
68+
)
69+
for file in files:
70+
return Path(file).parent
71+
raise ValueError("Could not find community snippets directory")
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
languageId: typescript
2+
command:
3+
version: 7
4+
spokenForm: snippet make funk
5+
action:
6+
name: generateSnippet
7+
directory: ""
8+
snippetName: snippetTest1
9+
target:
10+
type: primitive
11+
modifiers:
12+
- type: containingScope
13+
scopeType: {type: namedFunction}
14+
usePrePhraseSnapshot: true
15+
spokenFormError: generateSnippet.snippetName
16+
initialState:
17+
documentContents: |2-
18+
function helloWorld() {
19+
const whatever = "hello";
20+
21+
if (whatever == null) {
22+
console.log("hello")
23+
}
24+
}
25+
selections:
26+
- anchor: {line: 0, character: 13}
27+
active: {line: 0, character: 23}
28+
- anchor: {line: 3, character: 8}
29+
active: {line: 5, character: 9}
30+
marks: {}
31+
finalState:
32+
documentContents: |
33+
name: snippetTest1
34+
language: typescript
35+
phrase:
36+
37+
$1.wrapperPhrase:
38+
$0.wrapperPhrase:
39+
-
40+
function $1() {
41+
const whatever = "hello";
42+
43+
$0
44+
}
45+
---
46+
selections:
47+
- anchor: {line: 2, character: 8}
48+
active: {line: 2, character: 8}
49+
thatMark:
50+
- type: UntypedTarget
51+
contentRange:
52+
start: {line: 0, character: 4}
53+
end: {line: 6, character: 5}
54+
isReversed: false
55+
hasExplicitRange: true
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
languageId: typescript
2+
command:
3+
version: 7
4+
spokenForm: snippet make state
5+
action:
6+
name: generateSnippet
7+
directory: ""
8+
snippetName: snippetTest1
9+
target:
10+
type: primitive
11+
modifiers:
12+
- type: containingScope
13+
scopeType: {type: statement}
14+
usePrePhraseSnapshot: true
15+
spokenFormError: generateSnippet.snippetName
16+
initialState:
17+
documentContents: |-
18+
if () {
19+
console.log("hello")
20+
}
21+
selections:
22+
- anchor: {line: 0, character: 4}
23+
active: {line: 0, character: 4}
24+
marks: {}
25+
finalState:
26+
documentContents: |
27+
name: snippetTest1
28+
language: typescript
29+
phrase:
30+
31+
$0.wrapperPhrase:
32+
-
33+
if ($0) {
34+
console.log("hello")
35+
}
36+
---
37+
selections:
38+
- anchor: {line: 2, character: 8}
39+
active: {line: 2, character: 8}
40+
thatMark:
41+
- type: UntypedTarget
42+
contentRange:
43+
start: {line: 0, character: 0}
44+
end: {line: 2, character: 1}
45+
isReversed: false
46+
hasExplicitRange: true
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: test snippet make line
5+
action:
6+
name: generateSnippet
7+
directory: ""
8+
snippetName: testSnippet
9+
target:
10+
type: primitive
11+
modifiers:
12+
- type: containingScope
13+
scopeType: {type: line}
14+
usePrePhraseSnapshot: true
15+
spokenFormError: generateSnippet.snippetName
16+
initialState:
17+
documentContents: \textbf{$foo}
18+
selections:
19+
- anchor: {line: 0, character: 9}
20+
active: {line: 0, character: 12}
21+
marks: {}
22+
finalState:
23+
documentContents: |
24+
name: testSnippet
25+
language: plaintext
26+
phrase:
27+
28+
$0.wrapperPhrase:
29+
-
30+
\textbf{\$$0}
31+
---
32+
selections:
33+
- anchor: {line: 2, character: 8}
34+
active: {line: 2, character: 8}
35+
thatMark:
36+
- type: UntypedTarget
37+
contentRange:
38+
start: {line: 0, character: 0}
39+
end: {line: 0, character: 13}
40+
isReversed: false
41+
hasExplicitRange: true

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ export interface PasteActionDescriptor {
135135

136136
export interface GenerateSnippetActionDescriptor {
137137
name: "generateSnippet";
138+
directory?: string;
138139
snippetName?: string;
139140
target: PartialTargetDescriptor;
140141
}

packages/cursorless-engine/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
"lodash-es": "^4.17.21",
3131
"moo": "0.5.2",
3232
"nearley": "2.20.1",
33+
"talon-snippets": "1.1.0",
3334
"uuid": "^10.0.0",
3435
"zod": "3.23.8"
3536
},

0 commit comments

Comments
 (0)