Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix race movePrimaryCursorIntoVisibleRange (#2011) #2110

Merged
merged 14 commits into from
Nov 29, 2024
22 changes: 9 additions & 13 deletions src/commands/move.ts
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ export class EndOfBuffer extends EmacsCommand {
}
}

function movePrimaryCursorIntoVisibleRange(
export function movePrimaryCursorIntoVisibleRange(
textEditor: TextEditor,
isInMarkMode: boolean,
emacsController: IEmacsController,
Expand Down Expand Up @@ -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");
}
Expand All @@ -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");
}
Expand Down
10 changes: 10 additions & 0 deletions src/emulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
50 changes: 50 additions & 0 deletions src/test/suite/commands/move.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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,
]);
});
});
});

Expand Down
50 changes: 50 additions & 0 deletions src/test/suite/keep-cursor-range.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
});