Skip to content

Commit

Permalink
Extending custom grammar (#2436)
Browse files Browse the repository at this point in the history
Extending custom grammar with actions

#492

## 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: Pokey Rule <[email protected]>
  • Loading branch information
3 people authored Jun 21, 2024
1 parent ca57264 commit 4eca035
Show file tree
Hide file tree
Showing 37 changed files with 1,124 additions and 80 deletions.
6 changes: 6 additions & 0 deletions cursorless-talon-dev/src/cursorless_test.talon
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,9 @@ test api extract decorated marks <user.cursorless_target>:
user.private_cursorless_test_extract_decorated_marks(cursorless_target)
test api alternate highlight nothing:
user.private_cursorless_test_alternate_highlight_nothing()

test api parsed: user.cursorless_custom_command("chuck block")
test api parsed <user.cursorless_target>:
user.cursorless_custom_command("chuck block <target>", cursorless_target)
test api parsed <user.cursorless_target> plus <user.cursorless_target>:
user.cursorless_custom_command("bring block <target1> after <target2>", cursorless_target_1, cursorless_target_2)
22 changes: 21 additions & 1 deletion cursorless-talon/src/public_api.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from talon import Module
from typing import Any, Optional

from talon import Module, actions

from .targets.target_types import (
CursorlessDestination,
Expand All @@ -20,3 +22,21 @@ def cursorless_create_destination(
) -> CursorlessDestination:
"""Cursorless: Create destination from target"""
return PrimitiveDestination(insertion_mode, target)


@mod.action_class
class CommandActions:
def cursorless_custom_command(
content: str, # pyright: ignore [reportGeneralTypeIssues]
arg1: Optional[Any] = None,
arg2: Optional[Any] = None,
arg3: Optional[Any] = None,
):
"""Cursorless: Run custom parsed command"""
actions.user.private_cursorless_command_and_wait(
{
"name": "parsed",
"content": content,
"arguments": [arg for arg in [arg1, arg2, arg3] if arg is not None],
}
)
64 changes: 64 additions & 0 deletions data/fixtures/recorded/actions/parsed/bigBringAirPlusCap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
languageId: plaintext
command:
version: 7
spokenForm: big bring air plus cap
action:
name: parsed
content: bring block <target1> after <target2>
arguments:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: c}
usePrePhraseSnapshot: true
spokenFormError: Action 'parsed'
initialState:
documentContents: |-
aaa
bbb
ccc
ddd
eee
fff
selections:
- anchor: {line: 7, character: 3}
active: {line: 7, character: 3}
marks:
default.a:
start: {line: 0, character: 0}
end: {line: 0, character: 3}
default.c:
start: {line: 3, character: 0}
end: {line: 3, character: 3}
finalState:
documentContents: |-
aaa
bbb
ccc
ddd
aaa
bbb
eee
fff
selections:
- anchor: {line: 10, character: 3}
active: {line: 10, character: 3}
thatMark:
- type: UntypedTarget
contentRange:
start: {line: 6, character: 0}
end: {line: 7, character: 3}
isReversed: false
hasExplicitRange: true
sourceMark:
- type: UntypedTarget
contentRange:
start: {line: 0, character: 0}
end: {line: 1, character: 3}
isReversed: false
hasExplicitRange: true
40 changes: 40 additions & 0 deletions data/fixtures/recorded/actions/parsed/destroyAir.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
languageId: plaintext
command:
version: 7
spokenForm: destroy air
action:
name: parsed
content: chuck block <target>
arguments:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
usePrePhraseSnapshot: true
spokenFormError: Action 'parsed'
initialState:
documentContents: |-
aaa
bbb
ccc
ddd
selections:
- anchor: {line: 4, character: 3}
active: {line: 4, character: 3}
marks:
default.a:
start: {line: 0, character: 0}
end: {line: 0, character: 3}
finalState:
documentContents: |-
ccc
ddd
selections:
- anchor: {line: 1, character: 3}
active: {line: 1, character: 3}
thatMark:
- type: RawSelectionTarget
contentRange:
start: {line: 0, character: 0}
end: {line: 0, character: 0}
isReversed: false
hasExplicitRange: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
languageId: plaintext
command:
version: 7
spokenForm: destroy air and each past gust
action:
name: parsed
content: chuck block <target>
arguments:
- type: list
elements:
- type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: a}
- type: range
anchor:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: e}
active:
type: primitive
mark: {type: decoratedSymbol, symbolColor: default, character: g}
excludeAnchor: false
excludeActive: false
usePrePhraseSnapshot: true
spokenFormError: Action 'parsed'
initialState:
documentContents: |-
aaa
bbb
ccc
ddd
eee
fff
ggg
hhh
selections:
- anchor: {line: 10, character: 3}
active: {line: 10, character: 3}
marks:
default.a:
start: {line: 0, character: 0}
end: {line: 0, character: 3}
default.e:
start: {line: 6, character: 0}
end: {line: 6, character: 3}
default.g:
start: {line: 9, character: 0}
end: {line: 9, character: 3}
finalState:
documentContents: |-
ccc
ddd
selections:
- anchor: {line: 1, character: 3}
active: {line: 1, character: 3}
thatMark:
- type: RawSelectionTarget
contentRange:
start: {line: 0, character: 0}
end: {line: 0, character: 0}
isReversed: false
hasExplicitRange: true
- type: RawSelectionTarget
contentRange:
start: {line: 1, character: 3}
end: {line: 1, character: 3}
isReversed: false
hasExplicitRange: true
35 changes: 35 additions & 0 deletions data/fixtures/recorded/actions/parsed/destruction.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
languageId: plaintext
command:
version: 7
spokenForm: destruction
action:
name: parsed
content: chuck block
arguments: []
usePrePhraseSnapshot: true
spokenFormError: Action 'parsed'
initialState:
documentContents: |-
aaa
bbb
ccc
ddd
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
marks: {}
finalState:
documentContents: |-
ccc
ddd
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
thatMark:
- type: RawSelectionTarget
contentRange:
start: {line: 0, character: 0}
end: {line: 0, character: 0}
isReversed: false
hasExplicitRange: true
12 changes: 10 additions & 2 deletions packages/common/src/types/command/ActionDescriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { DestinationDescriptor } from "./DestinationDescriptor.types";
/**
* A simple action takes only a single target and no other arguments.
*/
const simpleActionNames = [
export const simpleActionNames = [
"breakLine",
"clearAndSetSelection",
"copyToClipboard",
Expand Down Expand Up @@ -72,6 +72,7 @@ const complexActionNames = [
"swapTargets",
"wrapWithPairedDelimiter",
"wrapWithSnippet",
"parsed",
] as const;

export const actionNames = [
Expand Down Expand Up @@ -219,6 +220,12 @@ export interface GetTextActionDescriptor {
target: PartialTargetDescriptor;
}

interface ParsedActionDescriptor {
name: "parsed";
content: string;
arguments: unknown[];
}

export type ActionDescriptor =
| SimpleActionDescriptor
| BringMoveActionDescriptor
Expand All @@ -233,4 +240,5 @@ export type ActionDescriptor =
| WrapWithSnippetActionDescriptor
| WrapWithPairedDelimiterActionDescriptor
| EditNewActionDescriptor
| GetTextActionDescriptor;
| GetTextActionDescriptor
| ParsedActionDescriptor;
36 changes: 31 additions & 5 deletions packages/common/src/types/command/PartialTargetDescriptor.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ export interface LastCursorPositionMark {
type: "lastCursorPosition";
}

export type SimplePartialMark =
| ThatMark
| KeyboardMark
| SourceMark
| NothingMark
| LastCursorPositionMark;

export interface DecoratedSymbolMark {
type: "decoratedSymbol";
symbolColor: string;
Expand All @@ -39,14 +46,16 @@ export interface LineNumberMark {
/**
* Constructs a range between {@link anchor} and {@link active}
*/
export interface RangeMark {
export interface RangeMarkFor<T> {
type: "range";
anchor: PartialMark;
active: PartialMark;
anchor: T;
active: T;
excludeAnchor: boolean;
excludeActive: boolean;
}

export type PartialRangeMark = RangeMarkFor<PartialMark>;

interface SimplePosition {
readonly line: number;
readonly character: number;
Expand All @@ -69,6 +78,22 @@ export interface ExplicitMark {
range: SimpleRange;
}

/**
* Can be used when constructing a primitive target that applies modifiers to
* the output of some other complex target descriptor. For example, we use this
* to apply the hoisted modifiers to the output of a range target when we hoist
* the "every funk" modifier on a command like "take every funk air until bat".
*/
export interface PartialTargetMark {
type: "target";

/**
* The target descriptor that will be used to generate the targets output by
* this mark.
*/
target: PartialTargetDescriptor;
}

export type PartialMark =
| CursorMark
| ThatMark
Expand All @@ -77,8 +102,9 @@ export type PartialMark =
| DecoratedSymbolMark
| NothingMark
| LineNumberMark
| RangeMark
| ExplicitMark;
| PartialRangeMark
| ExplicitMark
| PartialTargetMark;

export const simpleSurroundingPairNames = [
"angleBrackets",
Expand Down
1 change: 1 addition & 0 deletions packages/cursorless-engine/src/CommandHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ function sanitizeActionInPlace(action: ActionDescriptor): void {
case "wrapWithPairedDelimiter":
case "findInDocument":
case "private.setKeyboardTarget":
case "parsed":
break;

default: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from "@cursorless/common";
import { CommandRunner } from "../../CommandRunner";
import { ActionRecord, ActionReturnValue } from "../../actions/actions.types";
import { parseAndFillOutAction } from "../../customCommandGrammar/parseAndFillOutAction";
import { StoredTargetMap } from "../../index";
import { TargetPipelineRunner } from "../../processTargets";
import { ModifierStage } from "../../processTargets/PipelineStages.types";
Expand Down Expand Up @@ -197,6 +198,14 @@ export class CommandRunnerImpl implements CommandRunner {
actionDescriptor.options,
);

case "parsed":
return this.runAction(
parseAndFillOutAction(
actionDescriptor.content,
actionDescriptor.arguments,
),
);

default: {
const action = this.actions[actionDescriptor.name];
this.finalStages = action.getFinalStages?.() ?? [];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,8 @@ function upgradeAction(
options: action.args?.[0] as GetTextActionOptions | undefined,
target: upgradeTarget(targets[0]),
};
case "parsed":
throw Error("Parsed action should not be present in V5");
default:
return {
name,
Expand Down
Loading

0 comments on commit 4eca035

Please sign in to comment.