From 3ca3d8385e5768a6babe4b91674f5f69f90b896b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Geis?= Date: Sat, 2 Dec 2023 01:37:25 +0900 Subject: [PATCH] commands/api: fix rotation logic (fixes #322) --- src/api/data/commands.yaml | 12 +- src/api/edit/index.ts | 40 +++-- src/api/selections.ts | 44 ++++-- src/commands/README.md | 16 +- src/commands/layouts/azerty.fr.md | 16 +- src/commands/layouts/qwerty.md | 16 +- src/commands/selections.rotate.ts | 8 +- test/suite/api.test.ts | 28 ++-- test/suite/commands/selections-rotate.md | 73 +++++++++ test/suite/commands/selections-rotate.test.ts | 138 ++++++++++++++++++ 10 files changed, 319 insertions(+), 72 deletions(-) create mode 100644 test/suite/commands/selections-rotate.md create mode 100644 test/suite/commands/selections-rotate.test.ts diff --git a/src/api/data/commands.yaml b/src/api/data/commands.yaml index 372961b6..17f6d0f1 100644 --- a/src/api/data/commands.yaml +++ b/src/api/data/commands.yaml @@ -2368,7 +2368,7 @@ selections.rotate.both: keys: qwerty: |- - `a-(` (kakoune: normal) + `a-)` (kakoune: normal) doc: en: | @@ -2379,7 +2379,7 @@ selections.rotate.both: | Title | Identifier | Keybinding | Command | | ----------------------------------- | -------------- | ----------------------- | ------------------------------------------------ | - | Rotate selections counter-clockwise | `both.reverse` | `a-)` (kakoune: normal) | `[".selections.rotate.both", { reverse: true }]` | + | Rotate selections counter-clockwise | `both.reverse` | `a-(` (kakoune: normal) | `[".selections.rotate.both", { reverse: true }]` | selections.rotate.both.reverse: title: @@ -2390,7 +2390,7 @@ selections.rotate.both.reverse: keys: qwerty: |- - `a-)` (kakoune: normal) + `a-(` (kakoune: normal) selections.rotate.contents: title: @@ -2419,7 +2419,7 @@ selections.rotate.selections: keys: qwerty: |- - `(` (kakoune: normal) + `)` (kakoune: normal) doc: en: | @@ -2430,7 +2430,7 @@ selections.rotate.selections: | Title | Identifier | Keybinding | Command | | ----------------------------------------------------- | -------------------- | --------------------- | ------------------------------------------------------ | - | Rotate selections counter-clockwise (selections only) | `selections.reverse` | `)` (kakoune: normal) | `[".selections.rotate.selections", { reverse: true }]` | + | Rotate selections counter-clockwise (selections only) | `selections.reverse` | `(` (kakoune: normal) | `[".selections.rotate.selections", { reverse: true }]` | selections.rotate.selections.reverse: title: @@ -2441,7 +2441,7 @@ selections.rotate.selections.reverse: keys: qwerty: |- - `)` (kakoune: normal) + `(` (kakoune: normal) selections.save: title: diff --git a/src/api/edit/index.ts b/src/api/edit/index.ts index 61ed93c4..9c9f48c3 100644 --- a/src/api/edit/index.ts +++ b/src/api/edit/index.ts @@ -3,6 +3,7 @@ import * as vscode from "vscode"; import { Context, edit } from "../context"; import * as Positions from "../positions"; import * as Selections from "../selections"; +import { Direction } from "../types"; import * as TrackedSelection from "../../utils/tracked-selection"; const enum Constants { @@ -722,10 +723,10 @@ export declare namespace replaceByIndex { * * After: * ``` - * b c a - * ^ 1 - * ^ 2 - * ^ 0 + * c a b + * ^ 2 + * ^ 0 + * ^ 1 * ``` */ export function rotate(by: number, selections?: readonly vscode.Selection[]) { @@ -752,13 +753,13 @@ export function rotate(by: number, selections?: readonly vscode.Selection[]) { * * After: * ``` - * b c a + * c a b * ^ 0 * ^ 1 * ^ 2 * ``` */ -export function rotateContents( +export async function rotateContents( by: number, selections: readonly vscode.Selection[] = Context.current.selections, ) { @@ -768,13 +769,24 @@ export function rotateContents( by = (by % len) + len; if (by === len) { - return Context.wrap(Promise.resolve(selections.slice())); + return selections.slice(); } - return replaceByIndex( - (i, _, document) => document.getText(selections[(i + by) % len]), - selections, + const sortedSelections = Selections.sort(Direction.Forward, selections.slice()); + const rotatedSortedSelections = + Array.from({ length: len }, (_, i) => sortedSelections[(i + by) % len]); + const rotatedSortedSelectionsAfterEdit = await replaceByIndex( + (i, _, document) => document.getText(sortedSelections[i]), + rotatedSortedSelections, ); + + // We want to return the new selections (which may have changed because sizes + // of selections may be different), but we need to revert their indices first. + return selections.map((selection) => { + const rotatedSortedIndex = rotatedSortedSelections.indexOf(selection); + + return rotatedSortedSelectionsAfterEdit[rotatedSortedIndex]; + }); } /** @@ -798,11 +810,11 @@ export function rotateContents( * After: * ``` * a b c - * ^ 1 - * ^ 2 - * ^ 0 + * ^ 2 + * ^ 0 + * ^ 1 * ``` */ export function rotateSelections(by: number, selections?: readonly vscode.Selection[]) { - Selections.set(Selections.rotate(by, selections)); + return Selections.set(Selections.rotate(by, selections)); } diff --git a/src/api/selections.ts b/src/api/selections.ts index 634396b6..10ef15a7 100644 --- a/src/api/selections.ts +++ b/src/api/selections.ts @@ -611,15 +611,15 @@ export function updateWithFallbackByIndex< * Before: * ``` * foo bar baz - * ^^^ 0 ^^^ 2 - * ^^^ 1 + * ^^^ 1 ^^^ 0 + * ^^^ 2 * ``` * * After: * ``` * foo bar baz - * ^^^ 1 ^^^ 0 - * ^^^ 2 + * ^^^ 0 ^^^ 2 + * ^^^ 1 * ``` * * ### Example @@ -631,8 +631,8 @@ export function updateWithFallbackByIndex< * Before: * ``` * foo bar baz - * ^^^ 0 ^^^ 2 - * ^^^ 1 + * ^^^ 1 ^^^ 0 + * ^^^ 2 * ``` * * After: @@ -655,13 +655,37 @@ export function rotate( return selections.slice(); } - const newSelections = new Array(selections.length); + // Figure out how much the main selection should rotate (in terms of indices) + // to rotate (visually) by `by`. + const sortedIndices = Array.from({ length: len }, (_, i) => i) + .sort((a, b) => sortTopToBottom(selections[a], selections[b])); + + return Array.from({ length: len }, (_, i) => { + const indexInSortedArray = sortedIndices.indexOf(i); + const rotatedIndexInSortedArray = (indexInSortedArray + by) % len; + const rotatedIndex = sortedIndices[rotatedIndexInSortedArray]; + + return selections[rotatedIndex]; + }); + const mainIndexInSortedArray = sortedIndices.indexOf(0); + const rotatedMainIndexInSortedArray = (mainIndexInSortedArray + by) % len; + const rotatedMainIndex = sortedIndices[rotatedMainIndexInSortedArray]; + + // Apply that rotation with other selections. + by = rotatedMainIndex; + + // Sort selections to rotate them in the order in which they appear in the + // document, _not_ in the order in which they appear in the array. + const results = new Array(len); for (let i = 0; i < len; i++) { - newSelections[(i + by) % len] = selections[i]; + //results[(i + by) % len] = selections[i]; + results[i] = selections[(i + by) % len]; } - - return newSelections; + return results; + return Array.from({ length: len }, (_, i) => (i + by) % len) + .sort((a, b) => sortTopToBottom(selections[a], selections[b])) + .map((i) => selections[i]); } /** diff --git a/src/commands/README.md b/src/commands/README.md index 3d760e56..0ad8a6a0 100644 --- a/src/commands/README.md +++ b/src/commands/README.md @@ -220,12 +220,12 @@ selections are empty selections.toggleIndicesToggle selection indicesEnter (editorTextFocus && dance.mode == 'normal') selections.trimLinesTrim linesAlt+X (editorTextFocus && dance.mode == 'normal') selections.trimWhitespaceTrim whitespaceShift+- (editorTextFocus && dance.mode == 'normal') -selections.rotateselections.rotate.bothRotate selections clockwiseShift+Alt+9 (editorTextFocus && dance.mode == 'normal') +selections.rotateselections.rotate.bothRotate selections clockwiseShift+Alt+0 (editorTextFocus && dance.mode == 'normal') selections.rotate.contentsRotate selections clockwise (contents only) -selections.rotate.selectionsRotate selections clockwise (selections only)Shift+9 (editorTextFocus && dance.mode == 'normal') -selections.rotate.both.reverseRotate selections counter-clockwiseShift+Alt+0 (editorTextFocus && dance.mode == 'normal') +selections.rotate.selectionsRotate selections clockwise (selections only)Shift+0 (editorTextFocus && dance.mode == 'normal') +selections.rotate.both.reverseRotate selections counter-clockwiseShift+Alt+9 (editorTextFocus && dance.mode == 'normal') selections.rotate.contents.reverseRotate selections counter-clockwise (contents only) -selections.rotate.selections.reverseRotate selections counter-clockwise (selections only)Shift+0 (editorTextFocus && dance.mode == 'normal') +selections.rotate.selections.reverseRotate selections counter-clockwise (selections only)Shift+9 (editorTextFocus && dance.mode == 'normal') viewview.lineReveals a position based on the main cursor @@ -1708,13 +1708,13 @@ The following keybinding is also available: | Title | Identifier | Keybinding | Command | | ----------------------------------- | -------------- | ----------------------- | ------------------------------------------------ | -| Rotate selections counter-clockwise | `both.reverse` | `a-)` (kakoune: normal) | `[".selections.rotate.both", { reverse: true }]` | +| Rotate selections counter-clockwise | `both.reverse` | `a-(` (kakoune: normal) | `[".selections.rotate.both", { reverse: true }]` | This command: - may be repeated with a given number of repetitions. - takes an argument `reverse` of type `boolean`. -Default keybinding: `a-(` (kakoune: normal) +Default keybinding: `a-)` (kakoune: normal) @@ -1743,13 +1743,13 @@ The following keybinding is also available: | Title | Identifier | Keybinding | Command | | ----------------------------------------------------- | -------------------- | --------------------- | ------------------------------------------------------ | -| Rotate selections counter-clockwise (selections only) | `selections.reverse` | `)` (kakoune: normal) | `[".selections.rotate.selections", { reverse: true }]` | +| Rotate selections counter-clockwise (selections only) | `selections.reverse` | `(` (kakoune: normal) | `[".selections.rotate.selections", { reverse: true }]` | This command: - may be repeated with a given number of repetitions. - takes an argument `reverse` of type `boolean`. -Default keybinding: `(` (kakoune: normal) +Default keybinding: `)` (kakoune: normal) ## [`view`](./view.ts) diff --git a/src/commands/layouts/azerty.fr.md b/src/commands/layouts/azerty.fr.md index ab0ef942..70bee02d 100644 --- a/src/commands/layouts/azerty.fr.md +++ b/src/commands/layouts/azerty.fr.md @@ -205,12 +205,12 @@ selections are empty selections.toggleIndicesToggle selection indicesEnter (editorTextFocus && dance.mode == 'normal') selections.trimLinesTrim linesAlt+X (editorTextFocus && dance.mode == 'normal') selections.trimWhitespaceTrim whitespaceShift+- (editorTextFocus && dance.mode == 'normal') -selections.rotateselections.rotate.bothRotate selections clockwiseShift+Alt+9 (editorTextFocus && dance.mode == 'normal') +selections.rotateselections.rotate.bothRotate selections clockwiseShift+Alt+0 (editorTextFocus && dance.mode == 'normal') selections.rotate.contentsRotate selections clockwise (contents only) -selections.rotate.selectionsRotate selections clockwise (selections only)Shift+9 (editorTextFocus && dance.mode == 'normal') -selections.rotate.both.reverseRotate selections counter-clockwiseShift+Alt+0 (editorTextFocus && dance.mode == 'normal') +selections.rotate.selectionsRotate selections clockwise (selections only)Shift+0 (editorTextFocus && dance.mode == 'normal') +selections.rotate.both.reverseRotate selections counter-clockwiseShift+Alt+9 (editorTextFocus && dance.mode == 'normal') selections.rotate.contents.reverseRotate selections counter-clockwise (contents only) -selections.rotate.selections.reverseRotate selections counter-clockwise (selections only)Shift+0 (editorTextFocus && dance.mode == 'normal') +selections.rotate.selections.reverseRotate selections counter-clockwise (selections only)Shift+9 (editorTextFocus && dance.mode == 'normal') viewview.lineReveals a position based on the main cursor @@ -1693,13 +1693,13 @@ The following keybinding is also available: | Title | Identifier | Keybinding | Command | | ----------------------------------- | -------------- | ----------------------- | ------------------------------------------------ | -| Rotate selections counter-clockwise | `both.reverse` | `a-)` (kakoune: normal) | `[".selections.rotate.both", { reverse: true }]` | +| Rotate selections counter-clockwise | `both.reverse` | `a-(` (kakoune: normal) | `[".selections.rotate.both", { reverse: true }]` | This command: - may be repeated with a given number of repetitions. - takes an argument `reverse` of type `boolean`. -Default keybinding: `a-(` (kakoune: normal) +Default keybinding: `a-)` (kakoune: normal) @@ -1728,13 +1728,13 @@ The following keybinding is also available: | Title | Identifier | Keybinding | Command | | ----------------------------------------------------- | -------------------- | --------------------- | ------------------------------------------------------ | -| Rotate selections counter-clockwise (selections only) | `selections.reverse` | `)` (kakoune: normal) | `[".selections.rotate.selections", { reverse: true }]` | +| Rotate selections counter-clockwise (selections only) | `selections.reverse` | `(` (kakoune: normal) | `[".selections.rotate.selections", { reverse: true }]` | This command: - may be repeated with a given number of repetitions. - takes an argument `reverse` of type `boolean`. -Default keybinding: `(` (kakoune: normal) +Default keybinding: `)` (kakoune: normal) ## [`view`](../view.ts) diff --git a/src/commands/layouts/qwerty.md b/src/commands/layouts/qwerty.md index 81751aa8..ed44489d 100644 --- a/src/commands/layouts/qwerty.md +++ b/src/commands/layouts/qwerty.md @@ -205,12 +205,12 @@ selections are empty selections.toggleIndicesToggle selection indicesEnter (editorTextFocus && dance.mode == 'normal') selections.trimLinesTrim linesAlt+X (editorTextFocus && dance.mode == 'normal') selections.trimWhitespaceTrim whitespaceShift+- (editorTextFocus && dance.mode == 'normal') -selections.rotateselections.rotate.bothRotate selections clockwiseShift+Alt+9 (editorTextFocus && dance.mode == 'normal') +selections.rotateselections.rotate.bothRotate selections clockwiseShift+Alt+0 (editorTextFocus && dance.mode == 'normal') selections.rotate.contentsRotate selections clockwise (contents only) -selections.rotate.selectionsRotate selections clockwise (selections only)Shift+9 (editorTextFocus && dance.mode == 'normal') -selections.rotate.both.reverseRotate selections counter-clockwiseShift+Alt+0 (editorTextFocus && dance.mode == 'normal') +selections.rotate.selectionsRotate selections clockwise (selections only)Shift+0 (editorTextFocus && dance.mode == 'normal') +selections.rotate.both.reverseRotate selections counter-clockwiseShift+Alt+9 (editorTextFocus && dance.mode == 'normal') selections.rotate.contents.reverseRotate selections counter-clockwise (contents only) -selections.rotate.selections.reverseRotate selections counter-clockwise (selections only)Shift+0 (editorTextFocus && dance.mode == 'normal') +selections.rotate.selections.reverseRotate selections counter-clockwise (selections only)Shift+9 (editorTextFocus && dance.mode == 'normal') viewview.lineReveals a position based on the main cursor @@ -1693,13 +1693,13 @@ The following keybinding is also available: | Title | Identifier | Keybinding | Command | | ----------------------------------- | -------------- | ----------------------- | ------------------------------------------------ | -| Rotate selections counter-clockwise | `both.reverse` | `a-)` (kakoune: normal) | `[".selections.rotate.both", { reverse: true }]` | +| Rotate selections counter-clockwise | `both.reverse` | `a-(` (kakoune: normal) | `[".selections.rotate.both", { reverse: true }]` | This command: - may be repeated with a given number of repetitions. - takes an argument `reverse` of type `boolean`. -Default keybinding: `a-(` (kakoune: normal) +Default keybinding: `a-)` (kakoune: normal) @@ -1728,13 +1728,13 @@ The following keybinding is also available: | Title | Identifier | Keybinding | Command | | ----------------------------------------------------- | -------------------- | --------------------- | ------------------------------------------------------ | -| Rotate selections counter-clockwise (selections only) | `selections.reverse` | `)` (kakoune: normal) | `[".selections.rotate.selections", { reverse: true }]` | +| Rotate selections counter-clockwise (selections only) | `selections.reverse` | `(` (kakoune: normal) | `[".selections.rotate.selections", { reverse: true }]` | This command: - may be repeated with a given number of repetitions. - takes an argument `reverse` of type `boolean`. -Default keybinding: `(` (kakoune: normal) +Default keybinding: `)` (kakoune: normal) ## [`view`](../view.ts) diff --git a/src/commands/selections.rotate.ts b/src/commands/selections.rotate.ts index 3be18a57..e6daf188 100644 --- a/src/commands/selections.rotate.ts +++ b/src/commands/selections.rotate.ts @@ -9,13 +9,13 @@ declare module "./selections.rotate"; /** * Rotate selections clockwise. * - * @keys `a-(` (kakoune: normal) + * @keys `a-)` (kakoune: normal) * * The following keybinding is also available: * * | Title | Identifier | Keybinding | Command | * | ----------------------------------- | -------------- | ----------------------- | ------------------------------------------------ | - * | Rotate selections counter-clockwise | `both.reverse` | `a-)` (kakoune: normal) | `[".selections.rotate.both", { reverse: true }]` | + * | Rotate selections counter-clockwise | `both.reverse` | `a-(` (kakoune: normal) | `[".selections.rotate.both", { reverse: true }]` | */ export function both(_: Context, repetitions: number, reverse: Argument = false) { if (reverse) { @@ -45,13 +45,13 @@ export function contents(_: Context, repetitions: number, reverse: Argument = false) { if (reverse) { diff --git a/test/suite/api.test.ts b/test/suite/api.test.ts index 072edf20..2d813e8c 100644 --- a/test/suite/api.test.ts +++ b/test/suite/api.test.ts @@ -661,10 +661,10 @@ suite("API tests", function () { ^ 2 `), after = ExpectedDocument.parseIndented(14, String.raw` - b c a - ^ 1 - ^ 2 - ^ 0 + c a b + ^ 2 + ^ 0 + ^ 1 `); await before.apply(editor); @@ -686,7 +686,7 @@ suite("API tests", function () { ^ 2 `), after = ExpectedDocument.parseIndented(14, String.raw` - b c a + c a b ^ 0 ^ 1 ^ 2 @@ -712,9 +712,9 @@ suite("API tests", function () { `), after = ExpectedDocument.parseIndented(14, String.raw` a b c - ^ 1 - ^ 2 - ^ 0 + ^ 2 + ^ 0 + ^ 1 `); await before.apply(editor); @@ -1346,13 +1346,13 @@ suite("API tests", function () { context = new Context(editorState, cancellationToken), before = ExpectedDocument.parseIndented(14, String.raw` foo bar baz - ^^^ 0 ^^^ 2 - ^^^ 1 + ^^^ 1 ^^^ 0 + ^^^ 2 `), after = ExpectedDocument.parseIndented(14, String.raw` foo bar baz - ^^^ 1 ^^^ 0 - ^^^ 2 + ^^^ 0 ^^^ 2 + ^^^ 1 `); await before.apply(editor); @@ -1369,8 +1369,8 @@ suite("API tests", function () { context = new Context(editorState, cancellationToken), before = ExpectedDocument.parseIndented(14, String.raw` foo bar baz - ^^^ 0 ^^^ 2 - ^^^ 1 + ^^^ 1 ^^^ 0 + ^^^ 2 `), after = ExpectedDocument.parseIndented(14, String.raw` foo bar baz diff --git a/test/suite/commands/selections-rotate.md b/test/suite/commands/selections-rotate.md new file mode 100644 index 00000000..bc13445f --- /dev/null +++ b/test/suite/commands/selections-rotate.md @@ -0,0 +1,73 @@ +# 1 + +``` +0 3 1 2 +^ 0 ^ 1 + ^ 3 ^ 2 +``` + +## 1 rotate-by-1 +[up](#1) + +- .selections.rotate.both + +``` +2 0 3 1 +^ 2 ^ 3 + ^ 0 ^ 1 +``` + +## 1 rotate-contents-by-1 +[up](#1) + +- .selections.rotate.contents + +``` +2 0 3 1 +^ 0 ^ 1 + ^ 3 ^ 2 +``` + +## 1 rotate-selections-by-1 +[up](#1) + +- .selections.rotate.selections + +``` +0 3 1 2 +^ 2 ^ 3 + ^ 0 ^ 1 +``` + +## 1 rotate-by-1-reverse +[up](#1) + +- .selections.rotate.both.reverse + +``` +3 1 2 0 +^ 3 ^ 2 + ^ 1 ^ 0 +``` + +## 1 rotate-contents-by-1-reverse +[up](#1) + +- .selections.rotate.contents.reverse + +``` +3 1 2 0 +^ 0 ^ 1 + ^ 3 ^ 2 +``` + +## 1 rotate-selections-by-1-reverse +[up](#1) + +- .selections.rotate.selections.reverse + +``` +0 3 1 2 +^ 3 ^ 2 + ^ 1 ^ 0 +``` diff --git a/test/suite/commands/selections-rotate.test.ts b/test/suite/commands/selections-rotate.test.ts new file mode 100644 index 00000000..a113d5f2 --- /dev/null +++ b/test/suite/commands/selections-rotate.test.ts @@ -0,0 +1,138 @@ +import * as vscode from "vscode"; + +import { executeCommand, ExpectedDocument, groupTestsByParentName } from "../utils"; + +suite("./test/suite/commands/selections-rotate.md", function () { + // Set up document. + let document: vscode.TextDocument, + editor: vscode.TextEditor; + + this.beforeAll(async () => { + document = await vscode.workspace.openTextDocument({ language: "plaintext" }); + editor = await vscode.window.showTextDocument(document); + editor.options.insertSpaces = true; + editor.options.tabSize = 2; + + await executeCommand("dance.dev.setSelectionBehavior", { mode: "normal", value: "caret" }); + }); + + this.afterAll(async () => { + await executeCommand("workbench.action.closeActiveEditor"); + }); + + test("1 > rotate-by-1", async function () { + // Set-up document to be in expected initial state. + await ExpectedDocument.apply(editor, 6, String.raw` + 0 3 1 2 + ^ 0 ^ 1 + ^ 3 ^ 2 + `); + + // Perform all operations. + await executeCommand("dance.selections.rotate.both"); + + // Ensure document is as expected. + ExpectedDocument.assertEquals(editor, "./test/suite/commands/selections-rotate.md:9:1", 6, String.raw` + 2 0 3 1 + ^ 2 ^ 3 + ^ 0 ^ 1 + `); + }); + + test("1 > rotate-contents-by-1", async function () { + // Set-up document to be in expected initial state. + await ExpectedDocument.apply(editor, 6, String.raw` + 0 3 1 2 + ^ 0 ^ 1 + ^ 3 ^ 2 + `); + + // Perform all operations. + await executeCommand("dance.selections.rotate.contents"); + + // Ensure document is as expected. + ExpectedDocument.assertEquals(editor, "./test/suite/commands/selections-rotate.md:20:1", 6, String.raw` + 2 0 3 1 + ^ 0 ^ 1 + ^ 3 ^ 2 + `); + }); + + test("1 > rotate-selections-by-1", async function () { + // Set-up document to be in expected initial state. + await ExpectedDocument.apply(editor, 6, String.raw` + 0 3 1 2 + ^ 0 ^ 1 + ^ 3 ^ 2 + `); + + // Perform all operations. + await executeCommand("dance.selections.rotate.selections"); + + // Ensure document is as expected. + ExpectedDocument.assertEquals(editor, "./test/suite/commands/selections-rotate.md:31:1", 6, String.raw` + 0 3 1 2 + ^ 2 ^ 3 + ^ 0 ^ 1 + `); + }); + + test("1 > rotate-by-1-reverse", async function () { + // Set-up document to be in expected initial state. + await ExpectedDocument.apply(editor, 6, String.raw` + 0 3 1 2 + ^ 0 ^ 1 + ^ 3 ^ 2 + `); + + // Perform all operations. + await executeCommand("dance.selections.rotate.both.reverse"); + + // Ensure document is as expected. + ExpectedDocument.assertEquals(editor, "./test/suite/commands/selections-rotate.md:42:1", 6, String.raw` + 3 1 2 0 + ^ 3 ^ 2 + ^ 1 ^ 0 + `); + }); + + test("1 > rotate-contents-by-1-reverse", async function () { + // Set-up document to be in expected initial state. + await ExpectedDocument.apply(editor, 6, String.raw` + 0 3 1 2 + ^ 0 ^ 1 + ^ 3 ^ 2 + `); + + // Perform all operations. + await executeCommand("dance.selections.rotate.contents.reverse"); + + // Ensure document is as expected. + ExpectedDocument.assertEquals(editor, "./test/suite/commands/selections-rotate.md:53:1", 6, String.raw` + 3 1 2 0 + ^ 0 ^ 1 + ^ 3 ^ 2 + `); + }); + + test("1 > rotate-selections-by-1-reverse", async function () { + // Set-up document to be in expected initial state. + await ExpectedDocument.apply(editor, 6, String.raw` + 0 3 1 2 + ^ 0 ^ 1 + ^ 3 ^ 2 + `); + + // Perform all operations. + await executeCommand("dance.selections.rotate.selections.reverse"); + + // Ensure document is as expected. + ExpectedDocument.assertEquals(editor, "./test/suite/commands/selections-rotate.md:64:1", 6, String.raw` + 0 3 1 2 + ^ 3 ^ 2 + ^ 1 ^ 0 + `); + }); + + groupTestsByParentName(this); +});