Skip to content

Commit 43e9e21

Browse files
Add action to select new targets (#2707)
Adds a new selection while preserving existing ones `"append <target>"` replaces `"take this and <target>"` You can also use `append pre` and `append post` to add a cursor before or after the given target (rather than selecting that target), while keeping your current selections unchanged. (Previously you could do something like `pre this and <target>` but this would also reduce your current selection to a cursor before the current target.) ## 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 --------- Co-authored-by: Phil Cohen <[email protected]>
1 parent c69a57a commit 43e9e21

File tree

10 files changed

+184
-19
lines changed

10 files changed

+184
-19
lines changed

cursorless-talon/src/spoken_forms.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
"NOTE FOR USERS": "Please don't edit this json file; see https://www.cursorless.org/docs/user/customization",
33
"actions.csv": {
44
"simple_action": {
5+
"append post": "addSelectionAfter",
6+
"append pre": "addSelectionBefore",
7+
"append": "addSelection",
58
"bottom": "scrollToBottom",
6-
"break": "breakLine",
79
"break point": "toggleLineBreakpoint",
10+
"break": "breakLine",
811
"carve": "cutToClipboard",
912
"center": "scrollToCenter",
1013
"change": "clearAndSetSelection",
@@ -22,8 +25,8 @@
2225
"extract": "extractVariable",
2326
"float": "insertEmptyLineAfter",
2427
"fold": "foldRegion",
25-
"follow": "followLink",
2628
"follow split": "followLinkAside",
29+
"follow": "followLink",
2730
"give": "deselect",
2831
"highlight": "highlight",
2932
"hover": "showHover",
@@ -39,8 +42,8 @@
3942
"reference": "showReferences",
4043
"rename": "rename",
4144
"reverse": "reverseTargets",
42-
"scout": "findInDocument",
4345
"scout all": "findInWorkspace",
46+
"scout": "findInDocument",
4447
"shuffle": "randomizeTargets",
4548
"snippet make": "generateSnippet",
4649
"sort": "sortTargets",
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: append post whale
5+
action:
6+
name: addSelectionAfter
7+
target:
8+
type: primitive
9+
mark: {type: decoratedSymbol, symbolColor: default, character: w}
10+
usePrePhraseSnapshot: true
11+
initialState:
12+
documentContents: hello world
13+
selections:
14+
- anchor: {line: 0, character: 0}
15+
active: {line: 0, character: 0}
16+
marks:
17+
default.w:
18+
start: {line: 0, character: 6}
19+
end: {line: 0, character: 11}
20+
finalState:
21+
documentContents: hello world
22+
selections:
23+
- anchor: {line: 0, character: 0}
24+
active: {line: 0, character: 0}
25+
- anchor: {line: 0, character: 11}
26+
active: {line: 0, character: 11}
27+
thatMark:
28+
- type: UntypedTarget
29+
contentRange:
30+
start: {line: 0, character: 6}
31+
end: {line: 0, character: 11}
32+
isReversed: false
33+
hasExplicitRange: false
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: append pre whale
5+
action:
6+
name: addSelectionBefore
7+
target:
8+
type: primitive
9+
mark: {type: decoratedSymbol, symbolColor: default, character: w}
10+
usePrePhraseSnapshot: true
11+
initialState:
12+
documentContents: hello world
13+
selections:
14+
- anchor: {line: 0, character: 0}
15+
active: {line: 0, character: 0}
16+
marks:
17+
default.w:
18+
start: {line: 0, character: 6}
19+
end: {line: 0, character: 11}
20+
finalState:
21+
documentContents: hello world
22+
selections:
23+
- anchor: {line: 0, character: 0}
24+
active: {line: 0, character: 0}
25+
- anchor: {line: 0, character: 6}
26+
active: {line: 0, character: 6}
27+
thatMark:
28+
- type: UntypedTarget
29+
contentRange:
30+
start: {line: 0, character: 6}
31+
end: {line: 0, character: 11}
32+
isReversed: false
33+
hasExplicitRange: false
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
languageId: plaintext
2+
command:
3+
version: 7
4+
spokenForm: append whale
5+
action:
6+
name: addSelection
7+
target:
8+
type: primitive
9+
mark: {type: decoratedSymbol, symbolColor: default, character: w}
10+
usePrePhraseSnapshot: true
11+
initialState:
12+
documentContents: hello world
13+
selections:
14+
- anchor: {line: 0, character: 0}
15+
active: {line: 0, character: 0}
16+
marks:
17+
default.w:
18+
start: {line: 0, character: 6}
19+
end: {line: 0, character: 11}
20+
finalState:
21+
documentContents: hello world
22+
selections:
23+
- anchor: {line: 0, character: 0}
24+
active: {line: 0, character: 0}
25+
- anchor: {line: 0, character: 6}
26+
active: {line: 0, character: 11}
27+
thatMark:
28+
- type: UntypedTarget
29+
contentRange:
30+
start: {line: 0, character: 6}
31+
end: {line: 0, character: 11}
32+
isReversed: false
33+
hasExplicitRange: false

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import type { DestinationDescriptor } from "./DestinationDescriptor.types";
88
* A simple action takes only a single target and no other arguments.
99
*/
1010
export const simpleActionNames = [
11+
"addSelection",
12+
"addSelectionAfter",
13+
"addSelectionBefore",
1114
"breakLine",
1215
"clearAndSetSelection",
1316
"copyToClipboard",
@@ -52,9 +55,9 @@ export const simpleActionNames = [
5255
"toggleLineBreakpoint",
5356
"toggleLineComment",
5457
"unfoldRegion",
58+
"private.getTargets",
5559
"private.setKeyboardTarget",
5660
"private.showParseTree",
57-
"private.getTargets",
5861
] as const;
5962

6063
const complexActionNames = [

packages/cursorless-engine/src/CommandHistory.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,9 @@ function sanitizeActionInPlace(action: ActionDescriptor): void {
130130
delete action.options?.commandArgs;
131131
break;
132132

133+
case "addSelection":
134+
case "addSelectionAfter":
135+
case "addSelectionBefore":
133136
case "breakLine":
134137
case "clearAndSetSelection":
135138
case "copyToClipboard":

packages/cursorless-engine/src/actions/Actions.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import GenerateSnippet from "./GenerateSnippet";
1818
import GetTargets from "./GetTargets";
1919
import GetText from "./GetText";
2020
import Highlight from "./Highlight";
21+
import { IndentLine, OutdentLine } from "./IndentLine";
2122
import {
2223
CopyContentAfter as InsertCopyAfter,
2324
CopyContentBefore as InsertCopyBefore,
@@ -35,13 +36,15 @@ import Replace from "./Replace";
3536
import Rewrap from "./Rewrap";
3637
import { ScrollToBottom, ScrollToCenter, ScrollToTop } from "./Scroll";
3738
import {
39+
AddSelection,
40+
AddSelectionAfter,
41+
AddSelectionBefore,
3842
SetSelection,
3943
SetSelectionAfter,
4044
SetSelectionBefore,
4145
} from "./SetSelection";
4246
import { SetSpecialTarget } from "./SetSpecialTarget";
4347
import ShowParseTree from "./ShowParseTree";
44-
import { IndentLine, OutdentLine } from "./IndentLine";
4548
import {
4649
ExtractVariable,
4750
Fold,
@@ -73,6 +76,9 @@ export class Actions implements ActionRecord {
7376
private modifierStageFactory: ModifierStageFactory,
7477
) {}
7578

79+
addSelection = new AddSelection();
80+
addSelectionBefore = new AddSelectionBefore();
81+
addSelectionAfter = new AddSelectionAfter();
7682
callAsFunction = new Call(this);
7783
clearAndSetSelection = new Clear(this);
7884
copyToClipboard = new CopyToClipboard(this, this.rangeUpdater);

packages/cursorless-engine/src/actions/SetSelection.ts

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,23 @@ import type { Target } from "../typings/target.types";
44
import { ensureSingleEditor } from "../util/targetUtils";
55
import type { SimpleAction, ActionReturnValue } from "./actions.types";
66

7-
export class SetSelection implements SimpleAction {
8-
constructor() {
7+
abstract class SetSelectionBase implements SimpleAction {
8+
constructor(
9+
private selectionMode: "set" | "add",
10+
private rangeMode: "content" | "before" | "after",
11+
) {
912
this.run = this.run.bind(this);
1013
}
1114

12-
protected getSelection(target: Target) {
13-
return target.contentSelection;
14-
}
15-
1615
async run(targets: Target[]): Promise<ActionReturnValue> {
1716
const editor = ensureSingleEditor(targets);
17+
const targetSelections = this.getSelections(targets);
18+
19+
const selections =
20+
this.selectionMode === "add"
21+
? editor.selections.concat(targetSelections)
22+
: targetSelections;
1823

19-
const selections = targets.map(this.getSelection);
2024
await ide()
2125
.getEditableTextEditor(editor)
2226
.setSelections(selections, { focusEditor: true });
@@ -25,16 +29,57 @@ export class SetSelection implements SimpleAction {
2529
thatTargets: targets,
2630
};
2731
}
32+
33+
private getSelections(targets: Target[]): Selection[] {
34+
switch (this.rangeMode) {
35+
case "content":
36+
return targets.map((target) => target.contentSelection);
37+
case "before":
38+
return targets.map(
39+
(target) =>
40+
new Selection(target.contentRange.start, target.contentRange.start),
41+
);
42+
case "after":
43+
return targets.map(
44+
(target) =>
45+
new Selection(target.contentRange.end, target.contentRange.end),
46+
);
47+
}
48+
}
49+
}
50+
51+
export class SetSelection extends SetSelectionBase {
52+
constructor() {
53+
super("set", "content");
54+
}
55+
}
56+
57+
export class SetSelectionBefore extends SetSelectionBase {
58+
constructor() {
59+
super("set", "before");
60+
}
61+
}
62+
63+
export class SetSelectionAfter extends SetSelectionBase {
64+
constructor() {
65+
super("set", "after");
66+
}
2867
}
2968

30-
export class SetSelectionBefore extends SetSelection {
31-
protected getSelection(target: Target) {
32-
return new Selection(target.contentRange.start, target.contentRange.start);
69+
export class AddSelection extends SetSelectionBase {
70+
constructor() {
71+
super("add", "content");
72+
}
73+
}
74+
75+
export class AddSelectionBefore extends SetSelectionBase {
76+
constructor() {
77+
super("add", "before");
3378
}
3479
}
3580

36-
export class SetSelectionAfter extends SetSelection {
37-
protected getSelection(target: Target) {
38-
return new Selection(target.contentRange.end, target.contentRange.end);
81+
export class AddSelectionAfter extends SetSelectionBase {
82+
constructor() {
83+
super("add", "after");
3984
}
4085
}

packages/cursorless-engine/src/spokenForms/defaultSpokenFormMapCore.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ export const defaultSpokenFormMapCore: DefaultSpokenFormMapDefinition = {
144144

145145
customRegex: {},
146146
action: {
147+
addSelection: "append",
148+
addSelectionAfter: "append post",
149+
addSelectionBefore: "append pre",
147150
breakLine: "break",
148151
scrollToBottom: "bottom",
149152
toggleLineBreakpoint: "break point",

packages/cursorless-org-docs/src/docs/user/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,9 +531,12 @@ Despite the name cursorless, some of the most basic commands in cursorless are f
531531
532532
Note that when combined with list targets, `take`/`pre`/`post` commands will result in multiple cursors.
533533
534+
- `"take <TARGET>"`: Selects the given target.
534535
- `"pre <TARGET>"`: Places the cursor before the given target.
535536
- `"post <TARGET>"`: Places the cursor after the given target.
536-
- `"take <TARGET>"`: Selects the given target.
537+
- `"append <TARGET>"`: Selects the given target, while preserving your existing selections.
538+
- `"append pre <TARGET>"`: Adds a new cursor before the given target, while preserving your existing selections.
539+
- `"append post <TARGET>"`: Adds a new cursor after the given target, while preserving your existing selections.
537540
- `"give <TARGET>"`: Deselects the given target.
538541
539542
eg:

0 commit comments

Comments
 (0)