diff --git a/.changeset/current-line-cursor.md b/.changeset/current-line-cursor.md new file mode 100644 index 000000000..7023fed7f --- /dev/null +++ b/.changeset/current-line-cursor.md @@ -0,0 +1,5 @@ +--- +"hunkdiff": minor +--- + +Highlight the current line and move it with `j`/`k`, use `h`/`l` to select the old or new side in split mode, and press `c` to add a note exactly where the cursor sits. Set `cursor_line` to `number` for a quieter line-number marker, or `off` to restore plain row scrolling. diff --git a/.changeset/sunny-ads-hang.md b/.changeset/sunny-ads-hang.md new file mode 100644 index 000000000..8318991d7 --- /dev/null +++ b/.changeset/sunny-ads-hang.md @@ -0,0 +1,5 @@ +--- +"hunkdiff": minor +--- + +Add `sidebar = "auto" | "shown" | "hidden"` configuration and CLI flags to control the sidebar in non-pager mode. diff --git a/README.md b/README.md index ddaf6c024..c8ff7cc09 100644 --- a/README.md +++ b/README.md @@ -130,9 +130,10 @@ vcs = "git" # git, jj, sl watch = false exclude_untracked = false line_numbers = true -tab_width = 4 # tab stops, 1-16 +tab_width = 4 # tab stops, 1-16 wrap_lines = false menu_bar = true +sidebar = "auto" # "auto", "shown", "hidden" agent_notes = false prompt_save_view_preferences = true transparent_background = false diff --git a/docs/keybindings.md b/docs/keybindings.md index dcf37d7af..56ce93ff2 100644 --- a/docs/keybindings.md +++ b/docs/keybindings.md @@ -67,10 +67,12 @@ The built-in commands and the keys they ship with: | `hunk.review.jumpToBottom` | Jump to end | `G`, `end` | | `hunk.review.scrollCodeLeft` | Scroll code left (shifted scrolls fast) | `left`, `shift+left` | | `hunk.review.scrollCodeRight` | Scroll code right (shifted scrolls fast) | `right`, `shift+right` | +| `hunk.review.selectOldSide` | Select old side of current line | `h` | +| `hunk.review.selectNewSide` | Select new side of current line | `l` | | `hunk.view.toggleSidebar` | Toggle sidebar | `s` | | `hunk.view.toggleMenuBar` | Toggle menu bar | `M` | | `hunk.view.toggleHunkHeaders` | Toggle hunk headers | `m` | -| `hunk.view.toggleLineNumbers` | Toggle line numbers | `l` | +| `hunk.view.toggleLineNumbers` | Toggle line numbers | _(none)_ | | `hunk.view.toggleLineWrap` | Toggle line wrapping | `w` | | `hunk.view.toggleAgentNotes` | Toggle agent notes | `a` | | `hunk.view.toggleCopyDecorations` | Toggle copy decorations | _(none)_ | @@ -78,6 +80,9 @@ The built-in commands and the keys they ship with: | `hunk.view.layoutSplit` | Split layout | `1` | | `hunk.view.layoutStack` | Stack layout | `2` | | `hunk.view.layoutAuto` | Auto layout | `0` | +| `hunk.view.cursorLineRow` | Highlight the current row | _(none)_ | +| `hunk.view.cursorLineNumber` | Mark the current line number | _(none)_ | +| `hunk.view.cursorLineOff` | Hide the current-line marker | _(none)_ | Commands marked _(none)_ ship without a key: they are menu items today, and binding one gives it a shortcut like any other. diff --git a/src/core/cli.test.ts b/src/core/cli.test.ts index 33cf1bc81..e42c43b10 100644 --- a/src/core/cli.test.ts +++ b/src/core/cli.test.ts @@ -151,6 +151,19 @@ describe("parseCli", () => { }); }); + test("parses the current-line style and rejects an unknown one", async () => { + const parsed = await parseCli(["bun", "hunk", "diff", "--cursor-line", "number"]); + + expect(parsed).toMatchObject({ + kind: "vcs", + options: { cursorLine: "number" }, + }); + + await expect(parseCli(["bun", "hunk", "diff", "--cursor-line", "sparkles"])).rejects.toThrow( + "Invalid cursor line style: sparkles", + ); + }); + test("accepts --experimental before the review command", async () => { const parsed = await parseCli(["bun", "hunk", "--experimental", "diff"]); @@ -178,6 +191,16 @@ describe("parseCli", () => { }); }); + test("parses sidebar toggles", async () => { + const shown = await parseCli(["bun", "hunk", "diff", "--sidebar"]); + const hidden = await parseCli(["bun", "hunk", "diff", "--no-sidebar"]); + const unset = await parseCli(["bun", "hunk", "diff"]); + + expect(shown).toMatchObject({ kind: "vcs", options: { sidebar: "shown" } }); + expect(hidden).toMatchObject({ kind: "vcs", options: { sidebar: "hidden" } }); + expect(unset.kind === "vcs" ? unset.options.sidebar : "unset").toBeUndefined(); + }); + test("parses staged git-style diff aliases", async () => { const staged = await parseCli(["bun", "hunk", "diff", "--staged"]); const cached = await parseCli(["bun", "hunk", "diff", "--cached"]); diff --git a/src/core/cli.ts b/src/core/cli.ts index 38efebbb9..23e174f48 100644 --- a/src/core/cli.ts +++ b/src/core/cli.ts @@ -4,6 +4,7 @@ import { Command, Option } from "commander"; import type { CliInput, CommonOptions, + CursorLine, HelpCommandInput, LayoutMode, PagerCommandInput, @@ -38,7 +39,7 @@ import { resolveCliVersion } from "./version"; export interface CliReferenceOption { readonly flag: string; readonly description: string; - readonly parse?: "layout" | "positiveInt" | "tabWidth" | "collect"; + readonly parse?: "layout" | "cursorLine" | "positiveInt" | "tabWidth" | "collect"; readonly defaultValue?: string; /** Default applied directly by Commander (as opposed to a config-resolved default). */ readonly commanderDefault?: string; @@ -59,6 +60,11 @@ export interface CliReferenceCommand { /** Review flags registered on every full-screen review command. */ export const COMMON_REVIEW_OPTIONS = [ { flag: "--mode ", description: "layout mode: auto, split, stack", parse: "layout" }, + { + flag: "--cursor-line