From 8fcb9e73a4d163c40d4e0563fd2ac12a63fae296 Mon Sep 17 00:00:00 2001 From: Ben Vinegar Date: Tue, 25 Aug 2026 20:00:47 -0400 Subject: [PATCH] feat(ui): add Ctrl-D and Ctrl-U scrolling aliases --- .changeset/ctrl-half-page-navigation.md | 5 +++++ docs/keybindings.md | 4 ++-- src/core/run/commandCatalog.ts | 4 ++-- src/ui/lib/appCommands.test.ts | 12 ++++++++++++ test/pty/pager.test.ts | 4 ++-- 5 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 .changeset/ctrl-half-page-navigation.md diff --git a/.changeset/ctrl-half-page-navigation.md b/.changeset/ctrl-half-page-navigation.md new file mode 100644 index 000000000..c89afa89c --- /dev/null +++ b/.changeset/ctrl-half-page-navigation.md @@ -0,0 +1,5 @@ +--- +"hunkdiff": patch +--- + +Add Ctrl-D and Ctrl-U aliases for half-page review scrolling. diff --git a/docs/keybindings.md b/docs/keybindings.md index 992e697bf..369e10ef1 100644 --- a/docs/keybindings.md +++ b/docs/keybindings.md @@ -51,8 +51,8 @@ The built-in commands and the keys they ship with: | `hunk.review.alignCurrentLineTop` | Align current line to viewport top | _(none)_ | | `hunk.review.editSelectedFile` | Open the selected file in your editor | `e` | | `hunk.review.focusFilter` | Focus the file filter | `/` | -| `hunk.review.halfPageDown` | Scroll down half a page | `d` | -| `hunk.review.halfPageUp` | Scroll up half a page | `u` | +| `hunk.review.halfPageDown` | Scroll down half a page | `d`, `ctrl+d` | +| `hunk.review.halfPageUp` | Scroll up half a page | `u`, `ctrl+u` | | `hunk.review.jumpToBottom` | Jump to end | `G`, `end` | | `hunk.review.jumpToTop` | Jump to start | `g`, `home` | | `hunk.review.nextAnnotatedFile` | Next annotated file | _(none)_ | diff --git a/src/core/run/commandCatalog.ts b/src/core/run/commandCatalog.ts index 0011247a4..5444d86db 100644 --- a/src/core/run/commandCatalog.ts +++ b/src/core/run/commandCatalog.ts @@ -181,7 +181,7 @@ const BUILTIN_COMMANDS = [ id: "hunk.review.halfPageDown", title: "Scroll down half a page", category: "review", - defaultKeys: ["d"], + defaultKeys: ["d", "ctrl+d"], locus: "client-local", verticalDirection: 1, publicToExtensions: true, @@ -190,7 +190,7 @@ const BUILTIN_COMMANDS = [ id: "hunk.review.halfPageUp", title: "Scroll up half a page", category: "review", - defaultKeys: ["u"], + defaultKeys: ["u", "ctrl+u"], locus: "client-local", verticalDirection: -1, publicToExtensions: true, diff --git a/src/ui/lib/appCommands.test.ts b/src/ui/lib/appCommands.test.ts index 188538cfa..7e0ddd04b 100644 --- a/src/ui/lib/appCommands.test.ts +++ b/src/ui/lib/appCommands.test.ts @@ -95,7 +95,9 @@ describe("built-in command chords", () => { expect(press({ name: "up" })).toBe("hunk.review.stepUp"); expect(press({ name: "k", sequence: "k" })).toBe("hunk.review.stepUp"); expect(press({ name: "d", sequence: "d" })).toBe("hunk.review.halfPageDown"); + expect(press({ name: "d", ctrl: true })).toBe("hunk.review.halfPageDown"); expect(press({ name: "u", sequence: "u" })).toBe("hunk.review.halfPageUp"); + expect(press({ name: "u", ctrl: true })).toBe("hunk.review.halfPageUp"); expect(ran).toEqual([ "scrollDiff:1,viewport", "scrollDiff:1,viewport", @@ -108,6 +110,8 @@ describe("built-in command chords", () => { "stepDiffLine:-1", "stepDiffLine:-1", "scrollDiff:1,half", + "scrollDiff:1,half", + "scrollDiff:-1,half", "scrollDiff:-1,half", ]); }); @@ -256,6 +260,14 @@ describe("builtinCommandKeyDefaults", () => { "space", "f", ]); + expect(defaults.find((entry) => entry.id === "hunk.review.halfPageDown")?.defaultKeys).toEqual([ + "d", + "ctrl+d", + ]); + expect(defaults.find((entry) => entry.id === "hunk.review.halfPageUp")?.defaultKeys).toEqual([ + "u", + "ctrl+u", + ]); // The menu-only commands ship unbound, and are reported so users can bind them. expect( defaults diff --git a/test/pty/pager.test.ts b/test/pty/pager.test.ts index 553530dcb..77cb850b1 100644 --- a/test/pty/pager.test.ts +++ b/test/pty/pager.test.ts @@ -82,7 +82,7 @@ describe("PTY pager", () => { expect(initial).toContain("before_01"); expect(initial).not.toContain("before_12"); - await session.press("d"); + await session.press(["ctrl", "d"]); const halfPaged = await harness.waitForSnapshot( session, (text) => !text.includes("before_01"), @@ -91,7 +91,7 @@ describe("PTY pager", () => { expect(halfPaged).not.toContain("before_01"); - await session.press("u"); + await session.press(["ctrl", "u"]); const halfPageRestored = await harness.waitForSnapshot( session, (text) => text.includes("before_01"),