Skip to content

Commit

Permalink
Added increment and decrement actions (#2236)
Browse files Browse the repository at this point in the history
Fixes #2192

## 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: Pokey Rule <755842+pokey@users.noreply.github.com>
  • Loading branch information
AndreasArvidsson and pokey authored Feb 21, 2024
1 parent 585c852 commit aaf645a
Show file tree
Hide file tree
Showing 12 changed files with 564 additions and 1 deletion.
8 changes: 8 additions & 0 deletions changelog/2024-02-addedIncrementAndDecrementActions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
tags: [enhancement]
pullRequest: 2236
---

- Added increment action. Will increment a number. eg `"increment this"` to change `1` to `2`.

- Added decrement action. Will decrement a number. eg `"decrement this"` to change `2` to `1`.
2 changes: 2 additions & 0 deletions cursorless-talon/src/spoken_forms.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"comment": "toggleLineComment",
"copy": "copyToClipboard",
"crown": "scrollToTop",
"decrement": "decrement",
"dedent": "outdentLine",
"define": "revealDefinition",
"drink": "editNewLineBefore",
Expand All @@ -25,6 +26,7 @@
"give": "deselect",
"highlight": "highlight",
"hover": "showHover",
"increment": "increment",
"indent": "indentLine",
"inspect": "showDebugHover",
"join": "joinLines",
Expand Down
5 changes: 5 additions & 0 deletions docs/user/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,11 @@ For example:
- `"indent air"`
- `"dedent funk bat"`
### Increment / decrement
- `"increment <TARGET>"`: increment number target. eg change `1` to `2`.
- `"decrement <TARGET>"`: decrement number target. eg change `2` to `1`.
### Insert empty lines
- `"drink <TARGET>"`: Inserts a new line above the target line, and moves the cursor to the newly created line
Expand Down
2 changes: 2 additions & 0 deletions packages/common/src/types/command/ActionDescriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const simpleActionNames = [
"clearAndSetSelection",
"copyToClipboard",
"cutToClipboard",
"decrement",
"deselect",
"editNewLineAfter",
"editNewLineBefore",
Expand All @@ -21,6 +22,7 @@ const simpleActionNames = [
"findInWorkspace",
"foldRegion",
"followLink",
"increment",
"indentLine",
"insertCopyAfter",
"insertCopyBefore",
Expand Down
2 changes: 2 additions & 0 deletions packages/cursorless-engine/src/CommandHistory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ function sanitizeActionInPlace(action: ActionDescriptor): void {
case "clearAndSetSelection":
case "copyToClipboard":
case "cutToClipboard":
case "decrement":
case "deselect":
case "editNewLineAfter":
case "editNewLineBefore":
Expand All @@ -152,6 +153,7 @@ function sanitizeActionInPlace(action: ActionDescriptor): void {
case "findInWorkspace":
case "foldRegion":
case "followLink":
case "increment":
case "indentLine":
case "insertCopyAfter":
case "insertCopyBefore":
Expand Down
5 changes: 4 additions & 1 deletion packages/cursorless-engine/src/actions/Actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,12 @@ import Remove from "./Remove";
import Replace from "./Replace";
import Rewrap from "./Rewrap";
import { ScrollToBottom, ScrollToCenter, ScrollToTop } from "./Scroll";
import { SetSpecialTarget } from "./SetSpecialTarget";
import {
SetSelection,
SetSelectionAfter,
SetSelectionBefore,
} from "./SetSelection";
import { SetSpecialTarget } from "./SetSpecialTarget";
import ShowParseTree from "./ShowParseTree";
import {
CopyToClipboard,
Expand All @@ -61,6 +61,7 @@ import ToggleBreakpoint from "./ToggleBreakpoint";
import Wrap from "./Wrap";
import WrapWithSnippet from "./WrapWithSnippet";
import { ActionRecord } from "./actions.types";
import { Decrement, Increment } from "./incrementDecrement";

/**
* Keeps a map from action names to objects that implement the given action
Expand All @@ -77,6 +78,7 @@ export class Actions implements ActionRecord {
clearAndSetSelection = new Clear(this);
copyToClipboard = new CopyToClipboard(this.rangeUpdater);
cutToClipboard = new CutToClipboard(this);
decrement = new Decrement(this);
deselect = new Deselect();
editNew = new EditNew(this.rangeUpdater, this);
editNewLineAfter: EditNewAfter = new EditNewAfter(
Expand All @@ -96,6 +98,7 @@ export class Actions implements ActionRecord {
generateSnippet = new GenerateSnippet();
getText = new GetText();
highlight = new Highlight();
increment = new Increment(this);
indentLine = new IndentLine(this.rangeUpdater);
insertCopyAfter = new InsertCopyAfter(
this.rangeUpdater,
Expand Down
110 changes: 110 additions & 0 deletions packages/cursorless-engine/src/actions/incrementDecrement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import { Range, TextEditor } from "@cursorless/common";
import { PlainTarget } from "../processTargets/targets";
import { SelectionWithEditor } from "../typings/Types";
import { Destination, Target } from "../typings/target.types";
import { MatchedText, matchText } from "../util/regex";
import { runForEachEditor } from "../util/targetUtils";
import { Actions } from "./Actions";
import { ActionReturnValue } from "./actions.types";

const REGEX = /-?\d+(\.\d+)?/g;

class IncrementDecrement {
constructor(
private actions: Actions,
private isIncrement: boolean,
) {
this.run = this.run.bind(this);
}

async run(targets: Target[]): Promise<ActionReturnValue> {
const thatSelections: SelectionWithEditor[] = [];

await runForEachEditor(
targets,
(target) => target.editor,
async (editor, targets) => {
const selections = await this.runOnEditor(editor, targets);
thatSelections.push(...selections);
},
);

return { thatSelections };
}

private async runOnEditor(
editor: TextEditor,
targets: Target[],
): Promise<SelectionWithEditor[]> {
const { document } = editor;
const destinations: Destination[] = [];
const replaceWith: string[] = [];

for (const target of targets) {
const offset = document.offsetAt(target.contentRange.start);
const text = target.contentText;
const matches = matchText(text, REGEX);

for (const match of matches) {
destinations.push(createDestination(editor, offset, match));
replaceWith.push(updateNumber(this.isIncrement, match.text));
}
}

const { thatSelections } = await this.actions.replace.run(
destinations,
replaceWith,
);

return thatSelections!;
}
}

export class Increment extends IncrementDecrement {
constructor(actions: Actions) {
super(actions, true);
}
}

export class Decrement extends IncrementDecrement {
constructor(actions: Actions) {
super(actions, false);
}
}

function createDestination(
editor: TextEditor,
offset: number,
match: MatchedText,
): Destination {
const target = new PlainTarget({
editor,
isReversed: false,
contentRange: new Range(
editor.document.positionAt(offset + match.index),
editor.document.positionAt(offset + match.index + match.text.length),
),
});
return target.toDestination("to");
}

function updateNumber(isIncrement: boolean, text: string): string {
return text.includes(".")
? updateFloat(isIncrement, text).toString()
: updateInteger(isIncrement, text).toString();
}

function updateInteger(isIncrement: boolean, text: string): number {
const original = parseInt(text);
const diff = 1;
return original + (isIncrement ? diff : -diff);
}

function updateFloat(isIncrement: boolean, text: string): number {
const original = parseFloat(text);
const isPercentage = Math.abs(original) <= 1.0;
const diff = isPercentage ? 0.1 : 1;
const updated = original + (isIncrement ? diff : -diff);
// Remove precision problems that would add a lot of extra digits
return parseFloat(updated.toPrecision(15)) / 1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export const actions = {
deselect: "give",
highlight: "highlight",
showHover: "hover",
increment: "increment",
decrement: "decrement",
indentLine: "indent",
showDebugHover: "inspect",
setSelectionAfter: "post",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
languageId: plaintext
command:
version: 6
spokenForm: decrement file
action:
name: decrement
target:
type: primitive
modifiers:
- type: containingScope
scopeType: {type: document}
usePrePhraseSnapshot: true
initialState:
documentContents: |-
foo
0
1
0.5
1.5
-0
-1
-0.5
-1.5
0rem
1rem
0.5rem
1.5rem
-0rem
-1rem
-0.5rem
-1.5rem
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
marks: {}
finalState:
documentContents: |-
foo
-1
0
0.4
0.5
-1
-2
-0.6
-2.5
-1rem
0rem
0.4rem
0.5rem
-1rem
-2rem
-0.6rem
-2.5rem
selections:
- anchor: {line: 0, character: 0}
active: {line: 0, character: 0}
thatMark:
- type: UntypedTarget
contentRange:
start: {line: 2, character: 0}
end: {line: 2, character: 2}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 3, character: 0}
end: {line: 3, character: 1}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 4, character: 0}
end: {line: 4, character: 3}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 5, character: 0}
end: {line: 5, character: 3}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 7, character: 0}
end: {line: 7, character: 2}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 8, character: 0}
end: {line: 8, character: 2}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 9, character: 0}
end: {line: 9, character: 4}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 10, character: 0}
end: {line: 10, character: 4}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 12, character: 0}
end: {line: 12, character: 2}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 13, character: 0}
end: {line: 13, character: 1}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 14, character: 0}
end: {line: 14, character: 3}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 15, character: 0}
end: {line: 15, character: 3}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 17, character: 0}
end: {line: 17, character: 2}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 18, character: 0}
end: {line: 18, character: 2}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 19, character: 0}
end: {line: 19, character: 4}
isReversed: false
hasExplicitRange: true
- type: UntypedTarget
contentRange:
start: {line: 20, character: 0}
end: {line: 20, character: 4}
isReversed: false
hasExplicitRange: true
Loading

3 comments on commit aaf645a

@tankorsmash
Copy link

@tankorsmash tankorsmash commented on aaf645a Mar 5, 2024

Choose a reason for hiding this comment

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

I have pulled latest main and I can do incrementing and decrementing but it's not in the cursorless cheatsheet

@pokey
Copy link
Member

@pokey pokey commented on aaf645a Mar 11, 2024

Choose a reason for hiding this comment

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

strange; it's in my cheatsheet. This is when you say "cursorless cheatsheet"? Are you on latest cursorless-talon?

@tankorsmash
Copy link

@tankorsmash tankorsmash commented on aaf645a Mar 11, 2024

Choose a reason for hiding this comment

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

Ah it regenerates the sheet when you run the command! I've just had the cheatsheet.html bookmarked and opened it that way. It's there after running the 'cursorless cheatsheet' command, thank you!

Please sign in to comment.