mirror of
https://github.com/cursorless-dev/cursorless.git
synced 2024-10-04 21:07:21 +03:00
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 <755842+pokey@users.noreply.github.com> 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 a9f100f58..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
This commit is contained in:
parent
cff8b4d59c
commit
94bab18f73
@ -1,5 +1,5 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal
|
||||
from typing import Literal, Union
|
||||
|
||||
from talon import Module, actions
|
||||
|
||||
@ -9,7 +9,7 @@ from ..paired_delimiter import paired_delimiters_map
|
||||
@dataclass
|
||||
class Wrapper:
|
||||
type: Literal["pairedDelimiter", "snippet"]
|
||||
extra_args: list[str]
|
||||
extra_args: list[Union[str, dict]]
|
||||
|
||||
|
||||
mod = Module()
|
||||
@ -30,7 +30,26 @@ def cursorless_wrapper(m) -> Wrapper:
|
||||
extra_args=[paired_delimiter_info.left, paired_delimiter_info.right],
|
||||
)
|
||||
except AttributeError:
|
||||
return Wrapper(type="snippet", extra_args=[m.cursorless_wrapper_snippet])
|
||||
snippet_name, variable_name = parse_snippet_location(
|
||||
m.cursorless_wrapper_snippet
|
||||
)
|
||||
return Wrapper(
|
||||
type="snippet",
|
||||
extra_args=[
|
||||
{
|
||||
"type": "named",
|
||||
"name": snippet_name,
|
||||
"variableName": variable_name,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
def parse_snippet_location(snippet_location: str) -> tuple[str, str]:
|
||||
[snippet_name, variable_name] = snippet_location.split(".")
|
||||
if snippet_name is None or variable_name is None:
|
||||
raise Exception("Snippet location missing '.'")
|
||||
return (snippet_name, variable_name)
|
||||
|
||||
|
||||
# Maps from (action_type, wrapper_type) to action name
|
||||
|
@ -12,7 +12,7 @@ from .primitive_target import IMPLICIT_TARGET
|
||||
mod = Module()
|
||||
|
||||
CURSORLESS_COMMAND_ID = "cursorless.command"
|
||||
CURSORLESS_COMMAND_VERSION = 4
|
||||
CURSORLESS_COMMAND_VERSION = 5
|
||||
last_phrase = None
|
||||
|
||||
|
||||
|
@ -8,4 +8,4 @@ tag: user.cursorless
|
||||
user.cursorless_single_target_command(cursorless_insert_snippet_action, cursorless_positional_target, cursorless_insertion_snippet)
|
||||
|
||||
{user.cursorless_insert_snippet_action} {user.cursorless_insertion_snippet_single_phrase} <user.text> [{user.cursorless_phrase_terminator}]:
|
||||
user.cursorless_insert_snippet_with_phrase(cursorless_insert_snippet_action, cursorless_insertion_snippet_single_phrase, text)
|
||||
user.private_cursorless_insert_snippet_with_phrase(cursorless_insert_snippet_action, cursorless_insertion_snippet_single_phrase, text)
|
||||
|
@ -1,3 +1,5 @@
|
||||
from typing import Any, Optional
|
||||
|
||||
from talon import Module, actions, app
|
||||
|
||||
from .csv_overrides import init_csv_and_watch_changes
|
||||
@ -27,13 +29,13 @@ mod.list("cursorless_phrase_terminator", "Contains term used to terminate a phra
|
||||
@mod.capture(
|
||||
rule="{user.cursorless_insertion_snippet_no_phrase} | {user.cursorless_insertion_snippet_single_phrase}"
|
||||
)
|
||||
def cursorless_insertion_snippet(m) -> str:
|
||||
def cursorless_insertion_snippet(m) -> dict:
|
||||
try:
|
||||
return m.cursorless_insertion_snippet_no_phrase
|
||||
name = m.cursorless_insertion_snippet_no_phrase
|
||||
except AttributeError:
|
||||
pass
|
||||
name = m.cursorless_insertion_snippet_single_phrase.split(".")[0]
|
||||
|
||||
return m.cursorless_insertion_snippet_single_phrase.split(".")[0]
|
||||
return {"type": "named", "name": name}
|
||||
|
||||
|
||||
# NOTE: Please do not change these dicts. Use the CSVs for customization.
|
||||
@ -65,13 +67,76 @@ insertion_snippets_single_phrase = {
|
||||
|
||||
@mod.action_class
|
||||
class Actions:
|
||||
def cursorless_insert_snippet_with_phrase(
|
||||
def private_cursorless_insert_snippet_with_phrase(
|
||||
action: str, snippet_description: str, text: str
|
||||
):
|
||||
"""Perform cursorless wrap action"""
|
||||
snippet_name, snippet_variable = snippet_description.split(".")
|
||||
actions.user.cursorless_implicit_target_command(
|
||||
action, snippet_name, {snippet_variable: text}
|
||||
action,
|
||||
{
|
||||
"type": "named",
|
||||
"name": snippet_name,
|
||||
"substitutions": {snippet_variable: text},
|
||||
},
|
||||
)
|
||||
|
||||
def cursorless_insert_snippet_by_name(name: str):
|
||||
"""Inserts a named snippet"""
|
||||
actions.user.cursorless_implicit_target_command(
|
||||
"insertSnippet",
|
||||
{
|
||||
"type": "named",
|
||||
"name": name,
|
||||
},
|
||||
)
|
||||
|
||||
def cursorless_insert_snippet(body: str):
|
||||
"""Inserts a custom snippet"""
|
||||
actions.user.cursorless_implicit_target_command(
|
||||
"insertSnippet",
|
||||
{
|
||||
"type": "custom",
|
||||
"body": body,
|
||||
},
|
||||
)
|
||||
|
||||
def cursorless_wrap_with_snippet_by_name(
|
||||
name: str, variable_name: str, target: dict
|
||||
):
|
||||
"""Wrap target with a named snippet"""
|
||||
actions.user.cursorless_single_target_command_with_arg_list(
|
||||
"wrapWithSnippet",
|
||||
target,
|
||||
[
|
||||
{
|
||||
"type": "named",
|
||||
"name": name,
|
||||
"variableName": variable_name,
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
def cursorless_wrap_with_snippet(
|
||||
body: str,
|
||||
target: dict,
|
||||
variable_name: Optional[str] = None,
|
||||
scope: Optional[str] = None,
|
||||
):
|
||||
"""Wrap target with a custom snippet"""
|
||||
snippet_arg: dict[str, Any] = {
|
||||
"type": "custom",
|
||||
"body": body,
|
||||
}
|
||||
if scope is not None:
|
||||
snippet_arg["scopeType"] = {"type": scope}
|
||||
if variable_name is not None:
|
||||
snippet_arg["variableName"] = variable_name
|
||||
|
||||
actions.user.cursorless_single_target_command_with_arg_list(
|
||||
"wrapWithSnippet",
|
||||
target,
|
||||
[snippet_arg],
|
||||
)
|
||||
|
||||
|
||||
|
@ -119,6 +119,15 @@ Cursorless exposes a couple talon actions and captures that you can use to defin
|
||||
Performs a built-in IDE command on the given target
|
||||
eg: `user.cursorless_ide_command("editor.action.addCommentLine", cursorless_target)`
|
||||
|
||||
#### Snippet actions
|
||||
|
||||
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_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
|
||||
|
||||
```talon
|
||||
|
@ -3,3 +3,5 @@
|
||||
Cursorless has experimental support for snippets. Currently these snippets are just used for wrapping targets.
|
||||
|
||||
The best place to start is to look at the [core cursorless snippets](../../../cursorless-snippets). Additionally, there is autocomplete with documentation as you're writing a snippet.
|
||||
|
||||
Note that for `body`, we support [the full textmate syntax supported by VSCode](https://code.visualstudio.com/docs/editor/userdefinedsnippets#_snippet-syntax), but we prefer to use variable names (eg `$foo`) instead of placeholders (eg `$1`) so that it is easy to use snippets for wrapping.
|
||||
|
@ -69,8 +69,9 @@ export * from "./types/command/ActionCommand";
|
||||
export * from "./types/command/legacy/CommandV0V1.types";
|
||||
export * from "./types/command/legacy/CommandV2.types";
|
||||
export * from "./types/command/legacy/CommandV3.types";
|
||||
export * from "./types/command/legacy/CommandV4.types";
|
||||
export * from "./types/command/legacy/targetDescriptorV2.types";
|
||||
export * from "./types/command/CommandV4.types";
|
||||
export * from "./types/command/CommandV5.types";
|
||||
export * from "./types/command/legacy/PartialTargetDescriptorV3.types";
|
||||
export * from "./types/CommandServerApi";
|
||||
export * from "./util/itertools";
|
||||
|
@ -1,11 +1,11 @@
|
||||
import type { PartialTargetDescriptor } from "./PartialTargetDescriptor.types";
|
||||
import type { ActionCommand } from "./ActionCommand";
|
||||
|
||||
export interface CommandV4 {
|
||||
export interface CommandV5 {
|
||||
/**
|
||||
* The version number of the command API
|
||||
*/
|
||||
version: 4;
|
||||
version: 5;
|
||||
|
||||
/**
|
||||
* The spoken form of the command if issued from a voice command system
|
@ -1,16 +1,23 @@
|
||||
import type { ActionCommand } from "./ActionCommand";
|
||||
import type { CommandV5 } from "./CommandV5.types";
|
||||
import type { CommandV0, CommandV1 } from "./legacy/CommandV0V1.types";
|
||||
import type { CommandV2 } from "./legacy/CommandV2.types";
|
||||
import type { CommandV3 } from "./legacy/CommandV3.types";
|
||||
import type { CommandV4 } from "./CommandV4.types";
|
||||
import type { CommandV4 } from "./legacy/CommandV4.types";
|
||||
|
||||
export type CommandComplete = Required<Omit<CommandLatest, "spokenForm">> &
|
||||
Pick<CommandLatest, "spokenForm"> & { action: Required<ActionCommand> };
|
||||
|
||||
export const LATEST_VERSION = 4 as const;
|
||||
export const LATEST_VERSION = 5 as const;
|
||||
|
||||
export type CommandLatest = Command & {
|
||||
version: typeof LATEST_VERSION;
|
||||
};
|
||||
|
||||
export type Command = CommandV0 | CommandV1 | CommandV2 | CommandV3 | CommandV4;
|
||||
export type Command =
|
||||
| CommandV0
|
||||
| CommandV1
|
||||
| CommandV2
|
||||
| CommandV3
|
||||
| CommandV4
|
||||
| CommandV5;
|
||||
|
95
packages/common/src/types/command/legacy/CommandV4.types.ts
Normal file
95
packages/common/src/types/command/legacy/CommandV4.types.ts
Normal file
@ -0,0 +1,95 @@
|
||||
import { PartialTargetDescriptorV4 } from "./PartialTargetDescriptorV4.types";
|
||||
|
||||
type ActionType =
|
||||
| "callAsFunction"
|
||||
| "clearAndSetSelection"
|
||||
| "copyToClipboard"
|
||||
| "cutToClipboard"
|
||||
| "deselect"
|
||||
| "editNew"
|
||||
| "editNewLineAfter"
|
||||
| "editNewLineBefore"
|
||||
| "executeCommand"
|
||||
| "extractVariable"
|
||||
| "findInWorkspace"
|
||||
| "foldRegion"
|
||||
| "followLink"
|
||||
| "generateSnippet"
|
||||
| "getText"
|
||||
| "highlight"
|
||||
| "indentLine"
|
||||
| "insertCopyAfter"
|
||||
| "insertCopyBefore"
|
||||
| "insertEmptyLineAfter"
|
||||
| "insertEmptyLineBefore"
|
||||
| "insertEmptyLinesAround"
|
||||
| "insertSnippet"
|
||||
| "moveToTarget"
|
||||
| "outdentLine"
|
||||
| "pasteFromClipboard"
|
||||
| "randomizeTargets"
|
||||
| "remove"
|
||||
| "rename"
|
||||
| "replace"
|
||||
| "replaceWithTarget"
|
||||
| "revealDefinition"
|
||||
| "revealTypeDefinition"
|
||||
| "reverseTargets"
|
||||
| "rewrapWithPairedDelimiter"
|
||||
| "scrollToBottom"
|
||||
| "scrollToCenter"
|
||||
| "scrollToTop"
|
||||
| "setSelection"
|
||||
| "setSelectionAfter"
|
||||
| "setSelectionBefore"
|
||||
| "showDebugHover"
|
||||
| "showHover"
|
||||
| "showQuickFix"
|
||||
| "showReferences"
|
||||
| "sortTargets"
|
||||
| "swapTargets"
|
||||
| "toggleLineBreakpoint"
|
||||
| "toggleLineComment"
|
||||
| "unfoldRegion"
|
||||
| "wrapWithPairedDelimiter"
|
||||
| "wrapWithSnippet";
|
||||
|
||||
export interface ActionCommandV4 {
|
||||
/**
|
||||
* The action to run
|
||||
*/
|
||||
name: ActionType;
|
||||
|
||||
/**
|
||||
* A list of arguments expected by the given action.
|
||||
*/
|
||||
args?: unknown[];
|
||||
}
|
||||
|
||||
export interface CommandV4 {
|
||||
/**
|
||||
* The version number of the command API
|
||||
*/
|
||||
version: 4;
|
||||
|
||||
/**
|
||||
* The spoken form of the command if issued from a voice command system
|
||||
*/
|
||||
spokenForm?: string;
|
||||
|
||||
/**
|
||||
* If the command is issued from a voice command system, this boolean indicates
|
||||
* whether we should use the pre phrase snapshot. Only set this to true if the
|
||||
* voice command system issues a pre phrase signal at the start of every
|
||||
* phrase.
|
||||
*/
|
||||
usePrePhraseSnapshot: boolean;
|
||||
|
||||
action: ActionCommandV4;
|
||||
|
||||
/**
|
||||
* A list of targets expected by the action. Inference will be run on the
|
||||
* targets
|
||||
*/
|
||||
targets: PartialTargetDescriptorV4[];
|
||||
}
|
@ -0,0 +1,351 @@
|
||||
interface CursorMark {
|
||||
type: "cursor";
|
||||
}
|
||||
|
||||
interface ThatMark {
|
||||
type: "that";
|
||||
}
|
||||
|
||||
interface SourceMark {
|
||||
type: "source";
|
||||
}
|
||||
|
||||
interface NothingMark {
|
||||
type: "nothing";
|
||||
}
|
||||
|
||||
interface DecoratedSymbolMark {
|
||||
type: "decoratedSymbol";
|
||||
symbolColor: string;
|
||||
character: string;
|
||||
}
|
||||
|
||||
type LineNumberType = "absolute" | "relative" | "modulo100";
|
||||
|
||||
interface LineNumberMark {
|
||||
type: "lineNumber";
|
||||
lineNumberType: LineNumberType;
|
||||
lineNumber: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a range between {@link anchor} and {@link active}
|
||||
*/
|
||||
interface RangeMark {
|
||||
type: "range";
|
||||
anchor: Mark;
|
||||
active: Mark;
|
||||
excludeAnchor?: boolean;
|
||||
excludeActive?: boolean;
|
||||
}
|
||||
|
||||
type Mark =
|
||||
| CursorMark
|
||||
| ThatMark
|
||||
| SourceMark
|
||||
| DecoratedSymbolMark
|
||||
| NothingMark
|
||||
| LineNumberMark
|
||||
| RangeMark;
|
||||
|
||||
type SimpleSurroundingPairName =
|
||||
| "angleBrackets"
|
||||
| "backtickQuotes"
|
||||
| "curlyBrackets"
|
||||
| "doubleQuotes"
|
||||
| "escapedDoubleQuotes"
|
||||
| "escapedParentheses"
|
||||
| "escapedSquareBrackets"
|
||||
| "escapedSingleQuotes"
|
||||
| "parentheses"
|
||||
| "singleQuotes"
|
||||
| "squareBrackets";
|
||||
type ComplexSurroundingPairName = "string" | "any" | "collectionBoundary";
|
||||
type SurroundingPairName =
|
||||
| SimpleSurroundingPairName
|
||||
| ComplexSurroundingPairName;
|
||||
|
||||
type SimpleScopeTypeType =
|
||||
| "argumentOrParameter"
|
||||
| "anonymousFunction"
|
||||
| "attribute"
|
||||
| "branch"
|
||||
| "class"
|
||||
| "className"
|
||||
| "collectionItem"
|
||||
| "collectionKey"
|
||||
| "comment"
|
||||
| "functionCall"
|
||||
| "functionCallee"
|
||||
| "functionName"
|
||||
| "ifStatement"
|
||||
| "list"
|
||||
| "map"
|
||||
| "name"
|
||||
| "namedFunction"
|
||||
| "regularExpression"
|
||||
| "statement"
|
||||
| "string"
|
||||
| "type"
|
||||
| "value"
|
||||
| "condition"
|
||||
| "section"
|
||||
| "sectionLevelOne"
|
||||
| "sectionLevelTwo"
|
||||
| "sectionLevelThree"
|
||||
| "sectionLevelFour"
|
||||
| "sectionLevelFive"
|
||||
| "sectionLevelSix"
|
||||
| "selector"
|
||||
| "switchStatementSubject"
|
||||
| "unit"
|
||||
| "xmlBothTags"
|
||||
| "xmlElement"
|
||||
| "xmlEndTag"
|
||||
| "xmlStartTag"
|
||||
// Latex scope types
|
||||
| "part"
|
||||
| "chapter"
|
||||
| "subSection"
|
||||
| "subSubSection"
|
||||
| "namedParagraph"
|
||||
| "subParagraph"
|
||||
| "environment"
|
||||
// Text based scopes
|
||||
| "token"
|
||||
| "line"
|
||||
| "notebookCell"
|
||||
| "paragraph"
|
||||
| "document"
|
||||
| "character"
|
||||
| "word"
|
||||
| "identifier"
|
||||
| "nonWhitespaceSequence"
|
||||
| "boundedNonWhitespaceSequence"
|
||||
| "url";
|
||||
|
||||
interface SimpleScopeType {
|
||||
type: SimpleScopeTypeType;
|
||||
}
|
||||
|
||||
interface CustomRegexScopeType {
|
||||
type: "customRegex";
|
||||
regex: string;
|
||||
}
|
||||
|
||||
type SurroundingPairDirection = "left" | "right";
|
||||
interface SurroundingPairScopeType {
|
||||
type: "surroundingPair";
|
||||
delimiter: SurroundingPairName;
|
||||
forceDirection?: SurroundingPairDirection;
|
||||
|
||||
/**
|
||||
* If `true`, then only accept pairs where the pair completely contains the
|
||||
* selection, ie without the edges touching.
|
||||
*/
|
||||
requireStrongContainment?: boolean;
|
||||
}
|
||||
|
||||
interface OneOfScopeType {
|
||||
type: "oneOf";
|
||||
scopeTypes: ScopeType[];
|
||||
}
|
||||
|
||||
type ScopeType =
|
||||
| SimpleScopeType
|
||||
| SurroundingPairScopeType
|
||||
| CustomRegexScopeType
|
||||
| OneOfScopeType;
|
||||
|
||||
interface InteriorOnlyModifier {
|
||||
type: "interiorOnly";
|
||||
}
|
||||
|
||||
interface ExcludeInteriorModifier {
|
||||
type: "excludeInterior";
|
||||
}
|
||||
|
||||
interface ContainingScopeModifier {
|
||||
type: "containingScope";
|
||||
scopeType: ScopeType;
|
||||
ancestorIndex?: number;
|
||||
}
|
||||
|
||||
interface EveryScopeModifier {
|
||||
type: "everyScope";
|
||||
scopeType: ScopeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refer to scopes by absolute index relative to iteration scope, eg "first
|
||||
* funk" to refer to the first function in a class.
|
||||
*/
|
||||
interface OrdinalScopeModifier {
|
||||
type: "ordinalScope";
|
||||
|
||||
scopeType: ScopeType;
|
||||
|
||||
/** The start of the range. Start from end of iteration scope if `start` is negative */
|
||||
start: number;
|
||||
|
||||
/** The number of scopes to include. Will always be positive. If greater than 1, will include scopes after {@link start} */
|
||||
length: number;
|
||||
}
|
||||
|
||||
type Direction = "forward" | "backward";
|
||||
|
||||
/**
|
||||
* Refer to scopes by offset relative to input target, eg "next
|
||||
* funk" to refer to the first function after the function containing the target input.
|
||||
*/
|
||||
interface RelativeScopeModifier {
|
||||
type: "relativeScope";
|
||||
|
||||
scopeType: ScopeType;
|
||||
|
||||
/** Indicates how many scopes away to start relative to the input target.
|
||||
* Note that if {@link direction} is `"backward"`, then this scope will be the
|
||||
* end of the output range. */
|
||||
offset: number;
|
||||
|
||||
/** The number of scopes to include. Will always be positive. If greater
|
||||
* than 1, will include scopes in the direction of {@link direction} */
|
||||
length: number;
|
||||
|
||||
/** Indicates which direction both {@link offset} and {@link length} go
|
||||
* relative to input target */
|
||||
direction: Direction;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts its input to a raw selection with no type information so for
|
||||
* example if it is the destination of a bring or move it should inherit the
|
||||
* type information such as delimiters from its source.
|
||||
*/
|
||||
interface RawSelectionModifier {
|
||||
type: "toRawSelection";
|
||||
}
|
||||
|
||||
interface LeadingModifier {
|
||||
type: "leading";
|
||||
}
|
||||
|
||||
interface TrailingModifier {
|
||||
type: "trailing";
|
||||
}
|
||||
|
||||
interface KeepContentFilterModifier {
|
||||
type: "keepContentFilter";
|
||||
}
|
||||
|
||||
interface KeepEmptyFilterModifier {
|
||||
type: "keepEmptyFilter";
|
||||
}
|
||||
|
||||
interface InferPreviousMarkModifier {
|
||||
type: "inferPreviousMark";
|
||||
}
|
||||
|
||||
type TargetPosition = "before" | "after" | "start" | "end";
|
||||
|
||||
interface PositionModifier {
|
||||
type: "position";
|
||||
position: TargetPosition;
|
||||
}
|
||||
|
||||
interface PartialPrimitiveTargetDescriptor {
|
||||
type: "primitive";
|
||||
mark?: Mark;
|
||||
modifiers?: Modifier[];
|
||||
}
|
||||
|
||||
interface HeadTailModifier {
|
||||
type: "extendThroughStartOf" | "extendThroughEndOf";
|
||||
modifiers?: Modifier[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs {@link modifier} if the target has no explicit scope type, ie if
|
||||
* {@link Target.hasExplicitScopeType} is `false`.
|
||||
*/
|
||||
interface ModifyIfUntypedModifier {
|
||||
type: "modifyIfUntyped";
|
||||
|
||||
/**
|
||||
* The modifier to apply if the target is untyped
|
||||
*/
|
||||
modifier: Modifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries each of the modifiers in {@link modifiers} in turn until one of them
|
||||
* doesn't throw an error, returning the output from the first modifier not
|
||||
* throwing an error.
|
||||
*/
|
||||
interface CascadingModifier {
|
||||
type: "cascading";
|
||||
|
||||
/**
|
||||
* The modifiers to try in turn
|
||||
*/
|
||||
modifiers: Modifier[];
|
||||
}
|
||||
|
||||
/**
|
||||
* First applies {@link anchor} to input, then independently applies
|
||||
* {@link active}, and forms a range between the two resulting targets
|
||||
*/
|
||||
interface RangeModifier {
|
||||
type: "range";
|
||||
anchor: Modifier;
|
||||
active: Modifier;
|
||||
excludeAnchor?: boolean;
|
||||
excludeActive?: boolean;
|
||||
}
|
||||
|
||||
type Modifier =
|
||||
| PositionModifier
|
||||
| InteriorOnlyModifier
|
||||
| ExcludeInteriorModifier
|
||||
| ContainingScopeModifier
|
||||
| EveryScopeModifier
|
||||
| OrdinalScopeModifier
|
||||
| RelativeScopeModifier
|
||||
| HeadTailModifier
|
||||
| LeadingModifier
|
||||
| TrailingModifier
|
||||
| RawSelectionModifier
|
||||
| ModifyIfUntypedModifier
|
||||
| CascadingModifier
|
||||
| RangeModifier
|
||||
| KeepContentFilterModifier
|
||||
| KeepEmptyFilterModifier
|
||||
| InferPreviousMarkModifier;
|
||||
|
||||
// continuous is one single continuous selection between the two targets
|
||||
// vertical puts a selection on each line vertically between the two targets
|
||||
type RangeType = "continuous" | "vertical";
|
||||
|
||||
interface PartialRangeTargetDescriptor {
|
||||
type: "range";
|
||||
anchor: PartialPrimitiveTargetDescriptor | ImplicitTargetDescriptor;
|
||||
active: PartialPrimitiveTargetDescriptor;
|
||||
excludeAnchor: boolean;
|
||||
excludeActive: boolean;
|
||||
rangeType?: RangeType;
|
||||
}
|
||||
|
||||
interface PartialListTargetDescriptor {
|
||||
type: "list";
|
||||
elements: (PartialPrimitiveTargetDescriptor | PartialRangeTargetDescriptor)[];
|
||||
}
|
||||
|
||||
interface ImplicitTargetDescriptor {
|
||||
type: "implicit";
|
||||
}
|
||||
|
||||
export type PartialTargetDescriptorV4 =
|
||||
| PartialPrimitiveTargetDescriptor
|
||||
| PartialRangeTargetDescriptor
|
||||
| PartialListTargetDescriptor
|
||||
| ImplicitTargetDescriptor;
|
@ -1,5 +1,6 @@
|
||||
import {
|
||||
RangeExpansionBehavior,
|
||||
ScopeType,
|
||||
Snippet,
|
||||
SnippetDefinition,
|
||||
textFormatters,
|
||||
@ -20,57 +21,113 @@ import { Target } from "../typings/target.types";
|
||||
import { ensureSingleEditor } from "../util/targetUtils";
|
||||
import { Action, ActionReturnValue } from "./actions.types";
|
||||
|
||||
interface NamedSnippetArg {
|
||||
type: "named";
|
||||
name: string;
|
||||
substitutions?: Record<string, string>;
|
||||
}
|
||||
interface CustomSnippetArg {
|
||||
type: "custom";
|
||||
body: string;
|
||||
scopeType?: ScopeType;
|
||||
substitutions?: Record<string, string>;
|
||||
}
|
||||
type InsertSnippetArg = NamedSnippetArg | CustomSnippetArg;
|
||||
|
||||
export default class InsertSnippet implements Action {
|
||||
private snippetParser = new SnippetParser();
|
||||
|
||||
getPrePositionStages(snippetName: string) {
|
||||
const snippet = this.graph.snippets.getSnippetStrict(snippetName);
|
||||
|
||||
const defaultScopeTypes = snippet.insertionScopeTypes;
|
||||
|
||||
if (defaultScopeTypes == null) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
new ModifyIfUntypedExplicitStage({
|
||||
type: "cascading",
|
||||
modifiers: defaultScopeTypes.map((scopeType) => ({
|
||||
type: "containingScope",
|
||||
scopeType: {
|
||||
type: scopeType,
|
||||
},
|
||||
})),
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
constructor(private graph: Graph) {
|
||||
this.run = this.run.bind(this);
|
||||
}
|
||||
|
||||
getPrePositionStages(snippetDescription: InsertSnippetArg) {
|
||||
const defaultScopeTypes = this.getScopeTypes(snippetDescription);
|
||||
|
||||
return defaultScopeTypes.length === 0
|
||||
? []
|
||||
: [
|
||||
new ModifyIfUntypedExplicitStage({
|
||||
type: "cascading",
|
||||
modifiers: defaultScopeTypes.map((scopeType) => ({
|
||||
type: "containingScope",
|
||||
scopeType,
|
||||
})),
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
private getScopeTypes(snippetDescription: InsertSnippetArg): ScopeType[] {
|
||||
if (snippetDescription.type === "named") {
|
||||
const { name } = snippetDescription;
|
||||
|
||||
const snippet = this.graph.snippets.getSnippetStrict(name);
|
||||
|
||||
const scopeTypeTypes = snippet.insertionScopeTypes;
|
||||
return scopeTypeTypes == null
|
||||
? []
|
||||
: scopeTypeTypes.map((scopeTypeType) => ({
|
||||
type: scopeTypeType,
|
||||
}));
|
||||
} else {
|
||||
return snippetDescription.scopeType == null
|
||||
? []
|
||||
: [snippetDescription.scopeType];
|
||||
}
|
||||
}
|
||||
|
||||
private getSnippetInfo(
|
||||
snippetDescription: InsertSnippetArg,
|
||||
targets: Target[],
|
||||
) {
|
||||
if (snippetDescription.type === "named") {
|
||||
const { name } = snippetDescription;
|
||||
|
||||
const snippet = this.graph.snippets.getSnippetStrict(name);
|
||||
|
||||
const definition = findMatchingSnippetDefinitionStrict(
|
||||
targets,
|
||||
snippet.definitions,
|
||||
);
|
||||
|
||||
return {
|
||||
body: definition.body.join("\n"),
|
||||
|
||||
formatSubstitutions(substitutions: Record<string, string> | undefined) {
|
||||
return substitutions == null
|
||||
? undefined
|
||||
: formatSubstitutions(snippet, definition, substitutions);
|
||||
},
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
body: snippetDescription.body,
|
||||
|
||||
formatSubstitutions(substitutions: Record<string, string> | undefined) {
|
||||
return substitutions;
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async run(
|
||||
[targets]: [Target[]],
|
||||
snippetName: string,
|
||||
substitutions: Record<string, string>,
|
||||
snippetDescription: InsertSnippetArg,
|
||||
): Promise<ActionReturnValue> {
|
||||
const snippet = this.graph.snippets.getSnippetStrict(snippetName);
|
||||
|
||||
const editor = ide().getEditableTextEditor(ensureSingleEditor(targets));
|
||||
|
||||
const definition = findMatchingSnippetDefinitionStrict(
|
||||
const { body, formatSubstitutions } = this.getSnippetInfo(
|
||||
snippetDescription,
|
||||
targets,
|
||||
snippet.definitions,
|
||||
);
|
||||
|
||||
const parsedSnippet = this.snippetParser.parse(definition.body.join("\n"));
|
||||
const parsedSnippet = this.snippetParser.parse(body);
|
||||
|
||||
const formattedSubstitutions =
|
||||
substitutions == null
|
||||
? undefined
|
||||
: formatSubstitutions(snippet, definition, substitutions);
|
||||
|
||||
transformSnippetVariables(parsedSnippet, null, formattedSubstitutions);
|
||||
transformSnippetVariables(
|
||||
parsedSnippet,
|
||||
null,
|
||||
formatSubstitutions(snippetDescription.substitutions),
|
||||
);
|
||||
|
||||
const snippetString = parsedSnippet.toTextmateString();
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { FlashStyle } from "@cursorless/common";
|
||||
import { FlashStyle, ScopeType } from "@cursorless/common";
|
||||
import { callFunctionAndUpdateSelections } from "../core/updateSelections/updateSelections";
|
||||
import { Graph } from "../index";
|
||||
import { ModifyIfUntypedStage } from "../processTargets/modifiers/ConditionalModifierStages";
|
||||
import { ide } from "../singletons/ide.singleton";
|
||||
import {
|
||||
@ -8,21 +9,31 @@ import {
|
||||
} from "../snippets/snippet";
|
||||
import { SnippetParser } from "../snippets/vendor/vscodeSnippet/snippetParser";
|
||||
import { Target } from "../typings/target.types";
|
||||
import { Graph } from "../typings/Graph";
|
||||
import { ensureSingleEditor, flashTargets } from "../util/targetUtils";
|
||||
import { Action, ActionReturnValue } from "./actions.types";
|
||||
|
||||
interface NamedSnippetArg {
|
||||
type: "named";
|
||||
name: string;
|
||||
variableName: string;
|
||||
}
|
||||
interface CustomSnippetArg {
|
||||
type: "custom";
|
||||
body: string;
|
||||
variableName?: string;
|
||||
scopeType?: ScopeType;
|
||||
}
|
||||
type WrapWithSnippetArg = NamedSnippetArg | CustomSnippetArg;
|
||||
|
||||
export default class WrapWithSnippet implements Action {
|
||||
private snippetParser = new SnippetParser();
|
||||
|
||||
getFinalStages(snippetLocation: string) {
|
||||
const [snippetName, placeholderName] =
|
||||
parseSnippetLocation(snippetLocation);
|
||||
constructor(private graph: Graph) {
|
||||
this.run = this.run.bind(this);
|
||||
}
|
||||
|
||||
const snippet = this.graph.snippets.getSnippetStrict(snippetName);
|
||||
|
||||
const variables = snippet.variables ?? {};
|
||||
const defaultScopeType = variables[placeholderName]?.wrapperScopeType;
|
||||
getFinalStages(snippet: WrapWithSnippetArg) {
|
||||
const defaultScopeType = this.getScopeType(snippet);
|
||||
|
||||
if (defaultScopeType == null) {
|
||||
return [];
|
||||
@ -33,37 +44,63 @@ export default class WrapWithSnippet implements Action {
|
||||
type: "modifyIfUntyped",
|
||||
modifier: {
|
||||
type: "containingScope",
|
||||
scopeType: {
|
||||
type: defaultScopeType,
|
||||
},
|
||||
scopeType: defaultScopeType,
|
||||
},
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
||||
constructor(private graph: Graph) {
|
||||
this.run = this.run.bind(this);
|
||||
private getScopeType(
|
||||
snippetDescription: WrapWithSnippetArg,
|
||||
): ScopeType | undefined {
|
||||
if (snippetDescription.type === "named") {
|
||||
const { name, variableName } = snippetDescription;
|
||||
|
||||
const snippet = this.graph.snippets.getSnippetStrict(name);
|
||||
|
||||
const variables = snippet.variables ?? {};
|
||||
const scopeTypeType = variables[variableName]?.wrapperScopeType;
|
||||
return scopeTypeType == null
|
||||
? undefined
|
||||
: {
|
||||
type: scopeTypeType,
|
||||
};
|
||||
} else {
|
||||
return snippetDescription.scopeType;
|
||||
}
|
||||
}
|
||||
|
||||
private getBody(
|
||||
snippetDescription: WrapWithSnippetArg,
|
||||
targets: Target[],
|
||||
): string {
|
||||
if (snippetDescription.type === "named") {
|
||||
const { name } = snippetDescription;
|
||||
|
||||
const snippet = this.graph.snippets.getSnippetStrict(name);
|
||||
|
||||
const definition = findMatchingSnippetDefinitionStrict(
|
||||
targets,
|
||||
snippet.definitions,
|
||||
);
|
||||
|
||||
return definition.body.join("\n");
|
||||
} else {
|
||||
return snippetDescription.body;
|
||||
}
|
||||
}
|
||||
|
||||
async run(
|
||||
[targets]: [Target[]],
|
||||
snippetLocation: string,
|
||||
snippetDescription: WrapWithSnippetArg,
|
||||
): Promise<ActionReturnValue> {
|
||||
const [snippetName, placeholderName] =
|
||||
parseSnippetLocation(snippetLocation);
|
||||
|
||||
const snippet = this.graph.snippets.getSnippetStrict(snippetName);
|
||||
|
||||
const editor = ide().getEditableTextEditor(ensureSingleEditor(targets));
|
||||
|
||||
const definition = findMatchingSnippetDefinitionStrict(
|
||||
targets,
|
||||
snippet.definitions,
|
||||
);
|
||||
const body = this.getBody(snippetDescription, targets);
|
||||
|
||||
const parsedSnippet = this.snippetParser.parse(definition.body.join("\n"));
|
||||
const parsedSnippet = this.snippetParser.parse(body);
|
||||
|
||||
transformSnippetVariables(parsedSnippet, placeholderName);
|
||||
transformSnippetVariables(parsedSnippet, snippetDescription.variableName);
|
||||
|
||||
const snippetString = parsedSnippet.toTextmateString();
|
||||
|
||||
@ -88,11 +125,3 @@ export default class WrapWithSnippet implements Action {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function parseSnippetLocation(snippetLocation: string): [string, string] {
|
||||
const [snippetName, placeholderName] = snippetLocation.split(".");
|
||||
if (snippetName == null || placeholderName == null) {
|
||||
throw new Error("Snippet location missing '.'");
|
||||
}
|
||||
return [snippetName, placeholderName];
|
||||
}
|
||||
|
@ -1,29 +1,26 @@
|
||||
import {
|
||||
ActionType,
|
||||
Command,
|
||||
CommandComplete,
|
||||
CommandLatest,
|
||||
EnforceUndefined,
|
||||
LATEST_VERSION,
|
||||
Modifier,
|
||||
OutdatedExtensionError,
|
||||
PartialTargetDescriptor,
|
||||
showWarning,
|
||||
SimpleScopeTypeType,
|
||||
} from "@cursorless/common";
|
||||
import { ide } from "../../singletons/ide.singleton";
|
||||
import { Graph } from "../../typings/Graph";
|
||||
import { getPartialPrimitiveTargets } from "../../util/getPrimitiveTargets";
|
||||
import { ActionType } from "@cursorless/common";
|
||||
import {
|
||||
Command,
|
||||
CommandComplete,
|
||||
CommandLatest,
|
||||
LATEST_VERSION,
|
||||
} from "@cursorless/common";
|
||||
import {
|
||||
Modifier,
|
||||
PartialTargetDescriptor,
|
||||
SimpleScopeTypeType,
|
||||
} from "@cursorless/common";
|
||||
import canonicalizeActionName from "./canonicalizeActionName";
|
||||
import canonicalizeTargets from "./canonicalizeTargets";
|
||||
import { upgradeV0ToV1 } from "./upgradeV0ToV1";
|
||||
import { upgradeV1ToV2 } from "./upgradeV1ToV2";
|
||||
import { upgradeV2ToV3 } from "./upgradeV2ToV3";
|
||||
import { upgradeV3ToV4 } from "./upgradeV3ToV4";
|
||||
import { upgradeV4ToV5 } from "./upgradeV4ToV5/upgradeV4ToV5";
|
||||
|
||||
/**
|
||||
* Given a command argument which comes from the client, normalize it so that it
|
||||
@ -79,6 +76,9 @@ function upgradeCommand(command: Command): CommandLatest {
|
||||
case 3:
|
||||
command = upgradeV3ToV4(command);
|
||||
break;
|
||||
case 4:
|
||||
command = upgradeV4ToV5(command);
|
||||
break;
|
||||
default:
|
||||
throw new Error(
|
||||
`Can't upgrade from unknown version ${command.version}`,
|
||||
|
@ -1,14 +1,12 @@
|
||||
import { CommandV4 } from "@cursorless/common";
|
||||
import { CommandV3 } from "@cursorless/common";
|
||||
import {
|
||||
PartialPrimitiveTargetDescriptorV3,
|
||||
PartialTargetDescriptorV3,
|
||||
} from "@cursorless/common";
|
||||
import {
|
||||
CommandV3,
|
||||
CommandV4,
|
||||
ImplicitTargetDescriptor,
|
||||
PartialPrimitiveTargetDescriptor,
|
||||
PartialPrimitiveTargetDescriptorV3,
|
||||
PartialRangeTargetDescriptor,
|
||||
PartialTargetDescriptor,
|
||||
PartialTargetDescriptorV3,
|
||||
} from "@cursorless/common";
|
||||
|
||||
export function upgradeV3ToV4(command: CommandV3): CommandV4 {
|
||||
|
@ -0,0 +1 @@
|
||||
export * from "./upgradeV4ToV5";
|
@ -0,0 +1,57 @@
|
||||
import {
|
||||
ActionCommand,
|
||||
ActionCommandV4,
|
||||
CommandV4,
|
||||
CommandV5,
|
||||
} from "@cursorless/common";
|
||||
|
||||
export function upgradeV4ToV5(command: CommandV4): CommandV5 {
|
||||
return {
|
||||
...command,
|
||||
version: 5,
|
||||
action: upgradeAction(command.action),
|
||||
};
|
||||
}
|
||||
|
||||
function upgradeAction(action: ActionCommandV4): ActionCommand {
|
||||
switch (action.name) {
|
||||
case "wrapWithSnippet": {
|
||||
const [name, variableName] = parseSnippetLocation(
|
||||
action.args![0] as string,
|
||||
);
|
||||
return {
|
||||
name: "wrapWithSnippet",
|
||||
args: [
|
||||
{
|
||||
type: "named",
|
||||
name,
|
||||
variableName,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
case "insertSnippet": {
|
||||
const [name, substitutions] = action.args!;
|
||||
return {
|
||||
name: "insertSnippet",
|
||||
args: [
|
||||
{
|
||||
type: "named",
|
||||
name,
|
||||
substitutions,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
default:
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
function parseSnippetLocation(snippetLocation: string): [string, string] {
|
||||
const [snippetName, placeholderName] = snippetLocation.split(".");
|
||||
if (snippetName == null || placeholderName == null) {
|
||||
throw new Error("Snippet location missing '.'");
|
||||
}
|
||||
return [snippetName, placeholderName];
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
languageId: plaintext
|
||||
command:
|
||||
version: 5
|
||||
spokenForm: custom insert
|
||||
action:
|
||||
name: insertSnippet
|
||||
args:
|
||||
- {type: custom, body: 'dummy snippet hole1: ($hole1), hole2: ($hole2)'}
|
||||
targets:
|
||||
- {type: implicit}
|
||||
usePrePhraseSnapshot: true
|
||||
initialState:
|
||||
documentContents: ""
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 0, character: 0}
|
||||
marks: {}
|
||||
finalState:
|
||||
documentContents: "dummy snippet hole1: (), hole2: ()"
|
||||
selections:
|
||||
- anchor: {line: 0, character: 22}
|
||||
active: {line: 0, character: 22}
|
||||
thatMark:
|
||||
- type: UntypedTarget
|
||||
contentRange:
|
||||
start: {line: 0, character: 0}
|
||||
end: {line: 0, character: 34}
|
||||
isReversed: false
|
||||
hasExplicitRange: true
|
@ -0,0 +1,35 @@
|
||||
languageId: plaintext
|
||||
command:
|
||||
version: 5
|
||||
spokenForm: custom insert after whale
|
||||
action:
|
||||
name: insertSnippet
|
||||
args:
|
||||
- {type: custom, body: 'dummy snippet hole1: ($hole1), hole2: ($hole2)'}
|
||||
targets:
|
||||
- type: primitive
|
||||
mark: {type: decoratedSymbol, symbolColor: default, character: w}
|
||||
modifiers:
|
||||
- {type: position, position: after}
|
||||
usePrePhraseSnapshot: true
|
||||
initialState:
|
||||
documentContents: hello world
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 0, character: 0}
|
||||
marks:
|
||||
default.w:
|
||||
start: {line: 0, character: 6}
|
||||
end: {line: 0, character: 11}
|
||||
finalState:
|
||||
documentContents: "hello world dummy snippet hole1: (), hole2: ()"
|
||||
selections:
|
||||
- anchor: {line: 0, character: 34}
|
||||
active: {line: 0, character: 34}
|
||||
thatMark:
|
||||
- type: UntypedTarget
|
||||
contentRange:
|
||||
start: {line: 0, character: 12}
|
||||
end: {line: 0, character: 46}
|
||||
isReversed: false
|
||||
hasExplicitRange: true
|
@ -0,0 +1,39 @@
|
||||
languageId: plaintext
|
||||
command:
|
||||
version: 5
|
||||
spokenForm: custom insert after whale
|
||||
action:
|
||||
name: insertSnippet
|
||||
args:
|
||||
- type: custom
|
||||
body: "dummy snippet hole1: ($hole1), hole2: ($hole2)"
|
||||
scopeType: {type: line}
|
||||
targets:
|
||||
- type: primitive
|
||||
mark: {type: decoratedSymbol, symbolColor: default, character: w}
|
||||
modifiers:
|
||||
- {type: position, position: after}
|
||||
usePrePhraseSnapshot: true
|
||||
initialState:
|
||||
documentContents: hello world
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 0, character: 0}
|
||||
marks:
|
||||
default.w:
|
||||
start: {line: 0, character: 6}
|
||||
end: {line: 0, character: 11}
|
||||
finalState:
|
||||
documentContents: |-
|
||||
hello world
|
||||
dummy snippet hole1: (), hole2: ()
|
||||
selections:
|
||||
- anchor: {line: 1, character: 22}
|
||||
active: {line: 1, character: 22}
|
||||
thatMark:
|
||||
- type: UntypedTarget
|
||||
contentRange:
|
||||
start: {line: 1, character: 0}
|
||||
end: {line: 1, character: 34}
|
||||
isReversed: false
|
||||
hasExplicitRange: true
|
@ -0,0 +1,31 @@
|
||||
languageId: plaintext
|
||||
command:
|
||||
version: 5
|
||||
spokenForm: custom insert hello world
|
||||
action:
|
||||
name: insertSnippet
|
||||
args:
|
||||
- type: custom
|
||||
body: "dummy snippet hole1: ($hole1), hole2: ($hole2)"
|
||||
substitutions: {hole2: hello world}
|
||||
targets:
|
||||
- {type: implicit}
|
||||
usePrePhraseSnapshot: true
|
||||
initialState:
|
||||
documentContents: ""
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 0, character: 0}
|
||||
marks: {}
|
||||
finalState:
|
||||
documentContents: "dummy snippet hole1: (), hole2: (hello world)"
|
||||
selections:
|
||||
- anchor: {line: 0, character: 22}
|
||||
active: {line: 0, character: 22}
|
||||
thatMark:
|
||||
- type: UntypedTarget
|
||||
contentRange:
|
||||
start: {line: 0, character: 0}
|
||||
end: {line: 0, character: 45}
|
||||
isReversed: false
|
||||
hasExplicitRange: true
|
@ -0,0 +1,38 @@
|
||||
languageId: plaintext
|
||||
command:
|
||||
version: 5
|
||||
spokenForm: custom wrap harp
|
||||
action:
|
||||
name: wrapWithSnippet
|
||||
args:
|
||||
- type: custom
|
||||
body: "dummy snippet hole1: ($hole1), hole2: ($hole2)"
|
||||
scopeType: {type: line}
|
||||
variableName: hole1
|
||||
targets:
|
||||
- type: primitive
|
||||
mark: {type: decoratedSymbol, symbolColor: default, character: h}
|
||||
usePrePhraseSnapshot: true
|
||||
initialState:
|
||||
documentContents: |
|
||||
hello world
|
||||
selections:
|
||||
- anchor: {line: 1, character: 0}
|
||||
active: {line: 1, character: 0}
|
||||
marks:
|
||||
default.h:
|
||||
start: {line: 0, character: 0}
|
||||
end: {line: 0, character: 5}
|
||||
finalState:
|
||||
documentContents: |
|
||||
dummy snippet hole1: (hello world), hole2: ()
|
||||
selections:
|
||||
- anchor: {line: 0, character: 44}
|
||||
active: {line: 0, character: 44}
|
||||
thatMark:
|
||||
- type: UntypedTarget
|
||||
contentRange:
|
||||
start: {line: 0, character: 0}
|
||||
end: {line: 0, character: 45}
|
||||
isReversed: false
|
||||
hasExplicitRange: true
|
@ -0,0 +1,32 @@
|
||||
languageId: plaintext
|
||||
command:
|
||||
version: 5
|
||||
spokenForm: custom wrap line
|
||||
action:
|
||||
name: wrapWithSnippet
|
||||
args:
|
||||
- {type: custom, body: 'dummy snippet hole1: ($hole1), hole2: ($hole2)', variableName: hole1}
|
||||
targets:
|
||||
- type: primitive
|
||||
modifiers:
|
||||
- type: containingScope
|
||||
scopeType: {type: line}
|
||||
usePrePhraseSnapshot: true
|
||||
initialState:
|
||||
documentContents: hello world
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 0, character: 0}
|
||||
marks: {}
|
||||
finalState:
|
||||
documentContents: "dummy snippet hole1: (hello world), hole2: ()"
|
||||
selections:
|
||||
- anchor: {line: 0, character: 44}
|
||||
active: {line: 0, character: 44}
|
||||
thatMark:
|
||||
- type: UntypedTarget
|
||||
contentRange:
|
||||
start: {line: 0, character: 0}
|
||||
end: {line: 0, character: 45}
|
||||
isReversed: false
|
||||
hasExplicitRange: true
|
@ -0,0 +1,32 @@
|
||||
languageId: plaintext
|
||||
command:
|
||||
version: 5
|
||||
spokenForm: custom wrap line
|
||||
action:
|
||||
name: wrapWithSnippet
|
||||
args:
|
||||
- {type: custom, body: 'dummy snippet hole1: ($TM_SELECTED_TEXT), hole2: ($hole2)'}
|
||||
targets:
|
||||
- type: primitive
|
||||
modifiers:
|
||||
- type: containingScope
|
||||
scopeType: {type: line}
|
||||
usePrePhraseSnapshot: true
|
||||
initialState:
|
||||
documentContents: hello world
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 0, character: 0}
|
||||
marks: {}
|
||||
finalState:
|
||||
documentContents: "dummy snippet hole1: (hello world), hole2: ()"
|
||||
selections:
|
||||
- anchor: {line: 0, character: 44}
|
||||
active: {line: 0, character: 44}
|
||||
thatMark:
|
||||
- type: UntypedTarget
|
||||
contentRange:
|
||||
start: {line: 0, character: 0}
|
||||
end: {line: 0, character: 45}
|
||||
isReversed: false
|
||||
hasExplicitRange: true
|
@ -0,0 +1,32 @@
|
||||
languageId: typescript
|
||||
command:
|
||||
version: 5
|
||||
spokenForm: snip funk
|
||||
action:
|
||||
name: insertSnippet
|
||||
args:
|
||||
- {type: named, name: functionDeclaration}
|
||||
targets:
|
||||
- {type: implicit}
|
||||
usePrePhraseSnapshot: true
|
||||
initialState:
|
||||
documentContents: ""
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 0, character: 0}
|
||||
marks: {}
|
||||
finalState:
|
||||
documentContents: |-
|
||||
function () {
|
||||
|
||||
}
|
||||
selections:
|
||||
- anchor: {line: 0, character: 9}
|
||||
active: {line: 0, character: 9}
|
||||
thatMark:
|
||||
- type: UntypedTarget
|
||||
contentRange:
|
||||
start: {line: 0, character: 0}
|
||||
end: {line: 2, character: 1}
|
||||
isReversed: false
|
||||
hasExplicitRange: true
|
@ -0,0 +1,34 @@
|
||||
languageId: typescript
|
||||
command:
|
||||
version: 5
|
||||
spokenForm: snip funk hello world
|
||||
action:
|
||||
name: insertSnippet
|
||||
args:
|
||||
- type: named
|
||||
name: functionDeclaration
|
||||
substitutions: {name: hello world}
|
||||
targets:
|
||||
- {type: implicit}
|
||||
usePrePhraseSnapshot: true
|
||||
initialState:
|
||||
documentContents: ""
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 0, character: 0}
|
||||
marks: {}
|
||||
finalState:
|
||||
documentContents: |-
|
||||
function helloWorld() {
|
||||
|
||||
}
|
||||
selections:
|
||||
- anchor: {line: 0, character: 20}
|
||||
active: {line: 0, character: 20}
|
||||
thatMark:
|
||||
- type: UntypedTarget
|
||||
contentRange:
|
||||
start: {line: 0, character: 0}
|
||||
end: {line: 2, character: 1}
|
||||
isReversed: false
|
||||
hasExplicitRange: true
|
@ -0,0 +1,35 @@
|
||||
languageId: typescript
|
||||
command:
|
||||
version: 5
|
||||
spokenForm: try wrap this
|
||||
action:
|
||||
name: wrapWithSnippet
|
||||
args:
|
||||
- {type: named, name: tryCatchStatement, variableName: body}
|
||||
targets:
|
||||
- type: primitive
|
||||
mark: {type: cursor}
|
||||
usePrePhraseSnapshot: true
|
||||
initialState:
|
||||
documentContents: const foo = "bar";
|
||||
selections:
|
||||
- anchor: {line: 0, character: 0}
|
||||
active: {line: 0, character: 0}
|
||||
marks: {}
|
||||
finalState:
|
||||
documentContents: |-
|
||||
try {
|
||||
const foo = "bar";
|
||||
} catch (err) {
|
||||
|
||||
}
|
||||
selections:
|
||||
- anchor: {line: 3, character: 4}
|
||||
active: {line: 3, character: 4}
|
||||
thatMark:
|
||||
- type: UntypedTarget
|
||||
contentRange:
|
||||
start: {line: 0, character: 0}
|
||||
end: {line: 4, character: 1}
|
||||
isReversed: false
|
||||
hasExplicitRange: true
|
@ -1,6 +1,6 @@
|
||||
languageId: plaintext
|
||||
command:
|
||||
version: 4
|
||||
version: 5
|
||||
spokenForm: take harp
|
||||
action: {name: setSelection}
|
||||
targets:
|
||||
|
Loading…
Reference in New Issue
Block a user