Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/save-view-preferences-on-quit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"hunkdiff": minor
---

Offer to save changed view preferences to the user config on quit, including theme, layout, line numbers, wrapping, hunk headers, agent notes, and copy decorations. The prompt also includes a “never ask” option that persists `prompt_save_view_preferences = false`.
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ CLI input

- `App` should remain the orchestration shell for app state, navigation, layout mode, theme, filtering, and pane coordination.
- Pane rendering should live in dedicated components.
- Confirmation prompts with a small set of choices should reuse `ConfirmDialog` (body rows plus a clickable key-legend action row) instead of composing `ModalFrame` with a hand-rolled footer; keyboard handling for its actions stays in `useAppKeyboardShortcuts`.
- New UI work should extend existing components or add new ones, not grow `App` back into a monolith.
- Shared formatting, ids, and small derivations belong in helper modules, not repeated inline.
- Prefer one implementation path per feature instead of separate "old" and "new" codepaths that duplicate behavior.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,12 +128,14 @@ line_numbers = true
wrap_lines = false
menu_bar = true
agent_notes = false
prompt_save_view_preferences = true
transparent_background = false
```

`theme = "auto"` and `--theme auto` query the terminal background at startup, choose `github-light-default` for light backgrounds and `github-dark-default` for dark backgrounds, and fall back to `github-dark-default` if the terminal does not answer.
Older theme ids such as `graphite` and `paper` remain accepted as compatibility aliases.
`exclude_untracked` affects Git/Sapling working-tree `hunk diff` sessions only.
`prompt_save_view_preferences = false` disables the quit prompt for saving changed view preferences.
`transparent_background` can also be written as `transparentBackground`.

Custom themes can inherit from any built-in theme and override only the colors you care about:
Expand Down
116 changes: 114 additions & 2 deletions src/core/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { afterEach, describe, expect, test } from "bun:test";
import { mkdtempSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import type { CliInput } from "./types";
import { resolveConfiguredCliInput } from "./config";
import {
diffPersistedViewPreferences,
resolveConfiguredCliInput,
saveGlobalViewPreferences,
saveViewPreferencesPromptPreference,
} from "./config";
import { loadAppBootstrap } from "./loaders";

const tempDirs: string[] = [];
Expand Down Expand Up @@ -46,6 +51,109 @@ afterEach(() => {
cleanupTempDirs();
});

describe("config persistence", () => {
test("writes accepted view preferences to user config without disturbing tables", () => {
const home = createTempDir("hunk-save-config-home-");
const configPath = join(home, ".config", "hunk", "config.toml");
mkdirSync(join(home, ".config", "hunk"), { recursive: true });
writeFileSync(
configPath,
[
"# personal defaults",
'theme = "github-dark-default"',
"wrap_lines = false",
"",
"[custom_theme]",
'label = "Keep me"',
].join("\n"),
);

const savedPath = saveGlobalViewPreferences(
{
mode: "split",
theme: "dracula",
showLineNumbers: false,
wrapLines: true,
showHunkHeaders: false,
showMenuBar: false,
showAgentNotes: true,
copyDecorations: true,
},
{ env: { HOME: home } },
);

expect(savedPath).toBe(configPath);
expect(readFileSync(configPath, "utf8")).toBe(
[
"# personal defaults",
'theme = "dracula"',
"wrap_lines = true",
'mode = "split"',
"line_numbers = false",
"hunk_headers = false",
"menu_bar = false",
"agent_notes = true",
"copy_decorations = true",
"",
"[custom_theme]",
'label = "Keep me"',
"",
].join("\n"),
);
});

test("writes the view preferences prompt setting without disturbing tables", () => {
const home = createTempDir("hunk-save-config-home-");
const configPath = join(home, ".config", "hunk", "config.toml");
mkdirSync(join(home, ".config", "hunk"), { recursive: true });
writeFileSync(configPath, ["# personal defaults", "", "[custom_theme]"].join("\n"));

const savedPath = saveViewPreferencesPromptPreference(false, { env: { HOME: home } });

expect(savedPath).toBe(configPath);
expect(readFileSync(configPath, "utf8")).toBe(
[
"# personal defaults",
"prompt_save_view_preferences = false",
"",
"[custom_theme]",
"",
].join("\n"),
);
});

test("diffs view preference snapshots as the TOML assignments a save would rewrite", () => {
const initial = {
mode: "auto",
theme: "github-dark-default",
showLineNumbers: false,
wrapLines: false,
showHunkHeaders: false,
showMenuBar: true,
showAgentNotes: true,
copyDecorations: false,
} as const;

expect(diffPersistedViewPreferences(initial, { ...initial })).toEqual([]);
expect(
diffPersistedViewPreferences(initial, {
...initial,
mode: "split",
theme: "github-dark-dimmed",
showLineNumbers: true,
}),
).toEqual([
{
configKey: "theme",
previousValue: '"github-dark-default"',
nextValue: '"github-dark-dimmed"',
},
{ configKey: "mode", previousValue: '"auto"', nextValue: '"split"' },
{ configKey: "line_numbers", previousValue: "false", nextValue: "true" },
]);
});
});

describe("config resolution", () => {
test("merges global, repo, pager, command, and CLI overrides in the right order", () => {
const home = createTempDir("hunk-config-home-");
Expand All @@ -60,6 +168,7 @@ describe("config resolution", () => {
"line_numbers = false",
"transparentBackground = true",
"color_moved = true",
"prompt_save_view_preferences = false",
"",
"[patch]",
'mode = "split"',
Expand Down Expand Up @@ -88,6 +197,7 @@ describe("config resolution", () => {
});

expect(resolved.repoConfigPath).toBe(join(repo, ".hunk", "config.toml"));
expect(resolved.viewPreferencesConfigPath).toBe(join(repo, ".hunk", "config.toml"));
expect(resolved.input.options).toMatchObject({
pager: true,
mode: "stack",
Expand All @@ -97,6 +207,7 @@ describe("config resolution", () => {
menuBar: false,
hunkHeaders: false,
agentNotes: true,
promptSaveViewPreferences: false,
transparentBackground: true,
colorMoved: true,
});
Expand Down Expand Up @@ -273,6 +384,7 @@ describe("config resolution", () => {
});

expect(resolved.repoConfigPath).toBeUndefined();
expect(resolved.viewPreferencesConfigPath).toBe(join(home, ".config", "hunk", "config.toml"));
expect(resolved.input.options.theme).toBe("github-dark-default");
});

Expand Down
Loading
Loading