diff --git a/src/commands/move.ts b/src/commands/move.ts index 8156a16206..999edeb1aa 100644 --- a/src/commands/move.ts +++ b/src/commands/move.ts @@ -299,7 +299,7 @@ export class EndOfBuffer extends EmacsCommand { } } -function movePrimaryCursorIntoVisibleRange( +export function movePrimaryCursorIntoVisibleRange( textEditor: TextEditor, isInMarkMode: boolean, emacsController: IEmacsController, @@ -351,12 +351,10 @@ export class ScrollUpCommand extends EmacsCommand { } if (Configuration.instance.strictEmacsMove) { - return vscode.commands - .executeCommand<void>("editorScroll", { - to: "down", - by: "page", - }) - .then(() => movePrimaryCursorIntoVisibleRange(textEditor, isInMarkMode, this.emacsController)); + return vscode.commands.executeCommand<void>("editorScroll", { + to: "down", + by: "page", + }); } else { return vscode.commands.executeCommand<void>(isInMarkMode ? "cursorPageDownSelect" : "cursorPageDown"); } @@ -378,12 +376,10 @@ export class ScrollDownCommand extends EmacsCommand { } if (Configuration.instance.strictEmacsMove) { - return vscode.commands - .executeCommand<void>("editorScroll", { - to: "up", - by: "page", - }) - .then(() => movePrimaryCursorIntoVisibleRange(textEditor, isInMarkMode, this.emacsController)); + return vscode.commands.executeCommand<void>("editorScroll", { + to: "up", + by: "page", + }); } else { return vscode.commands.executeCommand<void>(isInMarkMode ? "cursorPageUpSelect" : "cursorPageUp"); } diff --git a/src/emulator.ts b/src/emulator.ts index 87ba791907..1d15d88777 100644 --- a/src/emulator.ts +++ b/src/emulator.ts @@ -171,6 +171,16 @@ export class EmacsEmulator implements IEmacsController, vscode.Disposable { this.commandRegistry.register(new MoveCommands.EndOfBuffer(this)); this.commandRegistry.register(new MoveCommands.ScrollUpCommand(this)); this.commandRegistry.register(new MoveCommands.ScrollDownCommand(this)); + vscode.window.onDidChangeTextEditorVisibleRanges( + () => { + if (Configuration.instance.strictEmacsMove) { + // Keep the primary cursor in the visible range when scrolling + MoveCommands.movePrimaryCursorIntoVisibleRange(this.textEditor, this.isInMarkMode, this); + } + }, + this, + this.disposables, + ); this.commandRegistry.register(new MoveCommands.ForwardParagraph(this)); this.commandRegistry.register(new MoveCommands.BackwardParagraph(this)); this.commandRegistry.register(new EditCommands.DeleteBackwardChar(this)); diff --git a/src/test/suite/commands/move.test.ts b/src/test/suite/commands/move.test.ts index 4d7f625eac..e9201cf499 100644 --- a/src/test/suite/commands/move.test.ts +++ b/src/test/suite/commands/move.test.ts @@ -301,6 +301,31 @@ suite("scroll-up/down-command", () => { ); assertCursorsEqual(activeTextEditor, [activeTextEditor.visibleRanges[0]?.start.line as number, 0]); }); + + test("it scrolls with the specified number of lines by the prefix argument and moves the cursor if it goes outside the visible range, keeping the selection", async () => { + setEmptyCursors(activeTextEditor, [visibleRange.start.line, 0]); // This line will be outside the visible range after scrolling. + + const initVisibleStartLine = visibleRange.start.line; + const initCursorPosition = activeTextEditor.selection.active; + + emulator.setMarkCommand(); + + await emulator.universalArgument(); + await emulator.subsequentArgumentDigit(12); + await emulator.runCommand("scrollUpCommand"); + + assert.equal( + activeTextEditor.visibleRanges[0]?.start.line, + initVisibleStartLine + 12, + "Expected the visibleRange has been scrolled 2 lines", + ); + assertSelectionsEqual(activeTextEditor, [ + initCursorPosition.line, + initCursorPosition.character, + activeTextEditor.visibleRanges[0]?.start.line as number, + 0, + ]); + }); }); suite("scroll-down-command", () => { @@ -366,6 +391,31 @@ suite("scroll-up/down-command", () => { ); assertCursorsEqual(activeTextEditor, [activeTextEditor.visibleRanges[0]?.end.line as number, 0]); }); + + test("it scrolls with the specified number of lines by the prefix argument and moves the cursor if it goes outside the visible range, keeping the selection", async () => { + setEmptyCursors(activeTextEditor, [visibleRange.end.line, 0]); // This line will be outside the visible range after scrolling. + + const initVisibleStartLine = visibleRange.start.line; + const initCursorPosition = activeTextEditor.selection.active; + + emulator.setMarkCommand(); + + await emulator.universalArgument(); + await emulator.subsequentArgumentDigit(12); + await emulator.runCommand("scrollDownCommand"); + + assert.equal( + activeTextEditor.visibleRanges[0]?.start.line, + initVisibleStartLine - 12, + "Expected the visibleRange has been scrolled 2 lines", + ); + assertSelectionsEqual(activeTextEditor, [ + initCursorPosition.line, + initCursorPosition.character, + activeTextEditor.visibleRanges[0]?.end.line as number, + 0, + ]); + }); }); }); diff --git a/src/test/suite/keep-cursor-range.test.ts b/src/test/suite/keep-cursor-range.test.ts new file mode 100644 index 0000000000..8a75174916 --- /dev/null +++ b/src/test/suite/keep-cursor-range.test.ts @@ -0,0 +1,50 @@ +import assert from "assert"; +import * as vscode from "vscode"; +import { EmacsEmulator } from "../../emulator"; +import { cleanUpWorkspace, setupWorkspace, setEmptyCursors } from "./utils"; +import { Configuration } from "../../configuration/configuration"; + +suite("onDidChangeTextEditorVisibleRanges event listener with strictEmacsMove", () => { + let activeTextEditor: vscode.TextEditor; + let emulator: EmacsEmulator; + + setup(async () => { + const initialText = "x\n".repeat(1000); + activeTextEditor = await setupWorkspace(initialText); + emulator = new EmacsEmulator(activeTextEditor); // `EmacsEmulator`'s constructor registers the event listener + }); + + teardown(async () => { + await cleanUpWorkspace(); + emulator.dispose(); + }); + + setup(() => {}); + teardown(() => { + Configuration.reload(); + }); + + test("it should keep cursor position in the visible range when scrolling when strictEmacsMove = true", async () => { + Configuration.instance.strictEmacsMove = true; + + setEmptyCursors(activeTextEditor, [0, 0]); + + await vscode.commands.executeCommand("editorScroll", { to: "down", by: "page", value: 3 }); + + assert.strictEqual(activeTextEditor.selection.active.line, activeTextEditor.visibleRanges[0]!.start.line); + + Configuration.reload(); + }); + + test("it shouldn't keep cursor position in the visible range when scrolling when strictEmacsMove = false", async () => { + Configuration.instance.strictEmacsMove = false; + + setEmptyCursors(activeTextEditor, [0, 0]); + + await vscode.commands.executeCommand("editorScroll", { to: "down", by: "page", value: 3 }); + + assert.strictEqual(activeTextEditor.selection.active.line, 0); + + Configuration.reload(); + }); +});