Skip to content

Generate snippets for community format #2744

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 12 commits into from
Jan 23, 2025
Merged
3 changes: 2 additions & 1 deletion cursorless-talon/src/actions/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@

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

Expand Down Expand Up @@ -99,6 +98,8 @@ def cursorless_command(action_name: str, target: CursorlessExplicitTarget): # p
)
elif action_name == "callAsFunction":
actions.user.private_cursorless_call(target)
elif action_name == "generateSnippet":
actions.user.private_cursorless_generate_snippet_action(target)
elif action_name in no_wait_actions:
action = {"name": action_name, "target": target}
actions.user.private_cursorless_command_no_wait(action)
Expand Down
71 changes: 71 additions & 0 deletions cursorless-talon/src/actions/generate_snippet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import glob
from pathlib import Path

from talon import Context, Module, actions, settings

from ..targets.target_types import CursorlessExplicitTarget

mod = Module()

ctx = Context()
ctx.matches = r"""
tag: user.cursorless_use_community_snippets
"""


@mod.action_class
class Actions:
def private_cursorless_generate_snippet_action(target: CursorlessExplicitTarget): # pyright: ignore [reportGeneralTypeIssues]
"""Generate a snippet from the given target"""
actions.user.private_cursorless_command_no_wait(
{
"name": "generateSnippet",
"target": target,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

By default don't send dirPath argument. This is for the legacy Cursorless snippet format. The vscode setting for snippet directory will be used.

}
)


@ctx.action_class("user")
class UserActions:
def private_cursorless_generate_snippet_action(target: CursorlessExplicitTarget): # pyright: ignore [reportGeneralTypeIssues]
actions.user.private_cursorless_command_no_wait(
{
"name": "generateSnippet",
"target": target,
"directory": str(get_directory_path()),
}
)


def get_directory_path() -> Path:
settings_dir = get_setting_dir()
if settings_dir is not None:
return settings_dir
return get_community_snippets_dir()


def get_setting_dir() -> Path | None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TIL:

We can't currently use it in action definitions until v0.5 releases but we can use it in code like this

try:
setting_dir = settings.get("user.snippets_dir")
if not setting_dir:
return None

dir = Path(str(setting_dir))

if not dir.is_absolute():
user_dir = Path(actions.path.talon_user())
dir = user_dir / dir

return dir.resolve()
except Exception:
return None


def get_community_snippets_dir() -> Path:
files = glob.iglob(
f"{actions.path.talon_user()}/**/snippets/snippets/*.snippet",
recursive=True,
)
for file in files:
return Path(file).parent
raise ValueError("Could not find community snippets directory")
55 changes: 55 additions & 0 deletions data/fixtures/recorded/actions/snippets/snipMakeFunk2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
languageId: typescript
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Double up on existing snippet make tests in the community format

command:
version: 7
spokenForm: snippet make funk
action:
name: generateSnippet
directory: ""
snippetName: snippetTest1
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: namedFunction}
usePrePhraseSnapshot: true
spokenFormError: generateSnippet.snippetName
initialState:
documentContents: |2-
function helloWorld() {
const whatever = "hello";

if (whatever == null) {
console.log("hello")
}
}
selections:
- anchor: {line: 0, character: 13}
active: {line: 0, character: 23}
- anchor: {line: 3, character: 8}
active: {line: 5, character: 9}
marks: {}
finalState:
documentContents: |
name: snippetTest1
language: typescript
phrase:

$1.wrapperPhrase:
$0.wrapperPhrase:
-
function $1() {
const whatever = "hello";

$0
}
---
selections:
- anchor: {line: 2, character: 8}
active: {line: 2, character: 8}
thatMark:
- type: UntypedTarget
contentRange:
start: {line: 0, character: 4}
end: {line: 6, character: 5}
isReversed: false
hasExplicitRange: true
46 changes: 46 additions & 0 deletions data/fixtures/recorded/actions/snippets/snipMakeState2.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
languageId: typescript
command:
version: 7
spokenForm: snippet make state
action:
name: generateSnippet
directory: ""
snippetName: snippetTest1
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: statement}
usePrePhraseSnapshot: true
spokenFormError: generateSnippet.snippetName
initialState:
documentContents: |-
if () {
console.log("hello")
}
selections:
- anchor: {line: 0, character: 4}
active: {line: 0, character: 4}
marks: {}
finalState:
documentContents: |
name: snippetTest1
language: typescript
phrase:

$0.wrapperPhrase:
-
if ($0) {
console.log("hello")
}
---
selections:
- anchor: {line: 2, character: 8}
active: {line: 2, character: 8}
thatMark:
- type: UntypedTarget
contentRange:
start: {line: 0, character: 0}
end: {line: 2, character: 1}
isReversed: false
hasExplicitRange: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
languageId: plaintext
command:
version: 7
spokenForm: test snippet make line
action:
name: generateSnippet
directory: ""
snippetName: testSnippet
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: line}
usePrePhraseSnapshot: true
spokenFormError: generateSnippet.snippetName
initialState:
documentContents: \textbf{$foo}
selections:
- anchor: {line: 0, character: 9}
active: {line: 0, character: 12}
marks: {}
finalState:
documentContents: |
name: testSnippet
language: plaintext
phrase:

$0.wrapperPhrase:
-
\textbf{\$$0}
---
selections:
- anchor: {line: 2, character: 8}
active: {line: 2, character: 8}
thatMark:
- type: UntypedTarget
contentRange:
start: {line: 0, character: 0}
end: {line: 0, character: 13}
isReversed: false
hasExplicitRange: true
1 change: 1 addition & 0 deletions packages/common/src/types/command/ActionDescriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ export interface PasteActionDescriptor {

export interface GenerateSnippetActionDescriptor {
name: "generateSnippet";
directory?: string;
snippetName?: string;
target: PartialTargetDescriptor;
}
Expand Down
1 change: 1 addition & 0 deletions packages/cursorless-engine/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"lodash-es": "^4.17.21",
"moo": "0.5.2",
"nearley": "2.20.1",
"talon-snippets": "1.1.0",
"uuid": "^10.0.0",
"zod": "3.23.8"
},
Expand Down
Loading
Loading