Skip to content

Commit e9b0d58

Browse files
authored
feat(ui): add Zenburn built-in theme
1 parent 34cfc48 commit e9b0d58

11 files changed

Lines changed: 128 additions & 34 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ All notable user-visible changes to Hunk are documented in this file.
77
### Added
88

99
- Show the newly selected theme in the footer status bar when switching themes.
10+
- Added a Zenburn built-in theme (`theme = "zenburn"`), a warm low-contrast dark palette inspired by Jani Nurminen's original Zenburn. It also works as a custom-theme `base`.
1011

1112
### Changed
1213

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ You can persist preferences to a config file:
119119
Example:
120120

121121
```toml
122-
theme = "graphite" # graphite, midnight, paper, ember, catppuccin-latte, catppuccin-mocha, custom
122+
theme = "graphite" # graphite, midnight, paper, ember, catppuccin-latte, catppuccin-mocha, zenburn, custom
123123
mode = "auto" # auto, split, stack
124124
vcs = "git" # git, jj
125125
watch = false
@@ -137,7 +137,7 @@ Custom themes can inherit from any built-in base theme and override only the col
137137
theme = "custom"
138138

139139
[custom_theme]
140-
base = "graphite" # graphite, midnight, paper, ember, catppuccin-latte, catppuccin-mocha
140+
base = "graphite" # graphite, midnight, paper, ember, catppuccin-latte, catppuccin-mocha, zenburn
141141
label = "My Theme"
142142
accent = "#7fd1ff"
143143
panel = "#10161d"

docs/opentui-component.md

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -190,19 +190,19 @@ If you need direct access to Pierre's parser, `parsePatchFiles(...)` is still re
190190

191191
## Common props
192192

193-
| Prop | Type | Default | Notes |
194-
| -------------------- | -------------------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------- |
195-
| `layout` | `"split" \| "stack"` | `"split"` | Chooses side-by-side or stacked rendering. |
196-
| `width` | `number` || Required content width in terminal columns. |
197-
| `theme` | `"graphite" \| "midnight" \| "paper" \| "ember" \| "catppuccin-latte" \| "catppuccin-mocha"` | `"graphite"` | Matches Hunk's built-in themes. |
198-
| `showLineNumbers` | `boolean` | `true` | Toggles line-number columns. |
199-
| `showHunkHeaders` | `boolean` | `true` | Toggles `@@ ... @@` hunk header rows. |
200-
| `showFileSeparators` | `boolean` | `true` | Toggles separator rows between files in `HunkReviewStream`. |
201-
| `wrapLines` | `boolean` | `false` | Wraps long lines instead of clipping horizontally. |
202-
| `horizontalOffset` | `number` | `0` | Scroll offset for non-wrapped code rows. |
203-
| `highlight` | `boolean` | `true` | Enables syntax highlighting. |
204-
| `selectedHunkIndex` | `number` | `0` | Highlights one hunk as the active target for single-file components. |
205-
| `scrollable` | `boolean` | `true` | `HunkDiffView` only; primitives should be wrapped in OpenTUI scrollbox when needed. |
193+
| Prop | Type | Default | Notes |
194+
| -------------------- | --------------------------------------------------------------------------------------------------------- | ------------ | ----------------------------------------------------------------------------------- |
195+
| `layout` | `"split" \| "stack"` | `"split"` | Chooses side-by-side or stacked rendering. |
196+
| `width` | `number` || Required content width in terminal columns. |
197+
| `theme` | `"graphite" \| "midnight" \| "paper" \| "ember" \| "catppuccin-latte" \| "catppuccin-mocha" \| "zenburn"` | `"graphite"` | Matches Hunk's built-in themes. |
198+
| `showLineNumbers` | `boolean` | `true` | Toggles line-number columns. |
199+
| `showHunkHeaders` | `boolean` | `true` | Toggles `@@ ... @@` hunk header rows. |
200+
| `showFileSeparators` | `boolean` | `true` | Toggles separator rows between files in `HunkReviewStream`. |
201+
| `wrapLines` | `boolean` | `false` | Wraps long lines instead of clipping horizontally. |
202+
| `horizontalOffset` | `number` | `0` | Scroll offset for non-wrapped code rows. |
203+
| `highlight` | `boolean` | `true` | Enables syntax highlighting. |
204+
| `selectedHunkIndex` | `number` | `0` | Highlights one hunk as the active target for single-file components. |
205+
| `scrollable` | `boolean` | `true` | `HunkDiffView` only; primitives should be wrapped in OpenTUI scrollbox when needed. |
206206

207207
## Other exports
208208

src/core/config.test.ts

Lines changed: 23 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -144,24 +144,29 @@ describe("config resolution", () => {
144144
});
145145
});
146146

147-
test.each(["graphite", "midnight", "paper", "ember", "catppuccin-latte", "catppuccin-mocha"])(
148-
"accepts custom theme base id: %s",
149-
(base) => {
150-
const home = createTempDir("hunk-config-home-");
151-
mkdirSync(join(home, ".config", "hunk"), { recursive: true });
152-
writeFileSync(
153-
join(home, ".config", "hunk", "config.toml"),
154-
["[custom_theme]", `base = "${base}"`].join("\n"),
155-
);
156-
157-
const resolved = resolveConfiguredCliInput(createPatchPagerInput(), {
158-
cwd: createTempDir("hunk-config-cwd-"),
159-
env: { HOME: home },
160-
});
147+
test.each([
148+
"graphite",
149+
"midnight",
150+
"paper",
151+
"ember",
152+
"catppuccin-latte",
153+
"catppuccin-mocha",
154+
"zenburn",
155+
])("accepts custom theme base id: %s", (base) => {
156+
const home = createTempDir("hunk-config-home-");
157+
mkdirSync(join(home, ".config", "hunk"), { recursive: true });
158+
writeFileSync(
159+
join(home, ".config", "hunk", "config.toml"),
160+
["[custom_theme]", `base = "${base}"`].join("\n"),
161+
);
161162

162-
expect(resolved.customTheme).toEqual({ base });
163-
},
164-
);
163+
const resolved = resolveConfiguredCliInput(createPatchPagerInput(), {
164+
cwd: createTempDir("hunk-config-cwd-"),
165+
env: { HOME: home },
166+
});
167+
168+
expect(resolved.customTheme).toEqual({ base });
169+
});
165170

166171
test("rejects invalid custom theme base ids", () => {
167172
const home = createTempDir("hunk-config-home-");
@@ -177,7 +182,7 @@ describe("config resolution", () => {
177182
env: { HOME: home },
178183
}),
179184
).toThrow(
180-
"Expected custom_theme.base to be one of: graphite, midnight, paper, ember, catppuccin-latte, catppuccin-mocha.",
185+
"Expected custom_theme.base to be one of: graphite, midnight, paper, ember, catppuccin-latte, catppuccin-mocha, zenburn.",
181186
);
182187
});
183188

src/core/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const BUILT_IN_THEME_IDS = [
1919
"ember",
2020
"catppuccin-latte",
2121
"catppuccin-mocha",
22+
"zenburn",
2223
] as const;
2324
const HEX_COLOR_PATTERN = /^#[0-9a-f]{6}$/i;
2425
const CUSTOM_THEME_COLOR_KEYS = [

src/opentui/HunkDiffView.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ describe("OpenTUI public components", () => {
162162
"ember",
163163
"catppuccin-latte",
164164
"catppuccin-mocha",
165+
"zenburn",
165166
]);
166167
});
167168
});

src/opentui/themes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export const HUNK_DIFF_THEME_NAMES = [
55
"ember",
66
"catppuccin-latte",
77
"catppuccin-mocha",
8+
"zenburn",
89
] as const;
910

1011
export type HunkDiffThemeName = (typeof HUNK_DIFF_THEME_NAMES)[number];

src/ui/lib/ui-lib.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,15 @@ describe("ui helpers", () => {
195195
menus.theme
196196
.filter((entry): entry is Extract<MenuEntry, { kind: "item" }> => entry.kind === "item")
197197
.map((entry) => entry.label),
198-
).toEqual(["Graphite", "Midnight", "Paper", "Ember", "Catppuccin Latte", "Catppuccin Mocha"]);
198+
).toEqual([
199+
"Graphite",
200+
"Midnight",
201+
"Paper",
202+
"Ember",
203+
"Catppuccin Latte",
204+
"Catppuccin Mocha",
205+
"Zenburn",
206+
]);
199207
expect(
200208
menus.theme.some(
201209
(entry) => entry.kind === "item" && entry.label === "Graphite" && entry.checked,
@@ -249,6 +257,7 @@ describe("ui helpers", () => {
249257
"Ember",
250258
"Catppuccin Latte",
251259
"Catppuccin Mocha",
260+
"Zenburn",
252261
"My Theme",
253262
]);
254263
expect(

src/ui/themes.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,4 +90,21 @@ describe("themes", () => {
9090
punctuation: CATPPUCCIN_PALETTES.mocha.overlay2,
9191
});
9292
});
93+
94+
test("resolves Zenburn by theme id with its tuned dark palette", () => {
95+
const zenburn = resolveTheme("zenburn", null);
96+
97+
expect(zenburn.id).toBe("zenburn");
98+
expect(zenburn.label).toBe("Zenburn");
99+
expect(zenburn.appearance).toBe("dark");
100+
expect(zenburn.background).toBe("#3f3f3f");
101+
expect(zenburn.text).toBe("#dcdccc");
102+
expect(zenburn.syntaxColors).toMatchObject({
103+
keyword: "#f0dfaf",
104+
string: "#dca3a3",
105+
comment: "#60b48a",
106+
function: "#94bff3",
107+
type: "#94bff3",
108+
});
109+
});
93110
});

src/ui/themes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { MIDNIGHT_THEME } from "./themes/midnight";
77
import { PAPER_THEME } from "./themes/paper";
88
import { withLazySyntaxStyle } from "./themes/syntax";
99
import type { AppTheme, ThemeBase } from "./themes/types";
10+
import { ZENBURN_THEME } from "./themes/zenburn";
1011

1112
export { CATPPUCCIN_PALETTES } from "./themes/catppuccin";
1213
export type { AppTheme, SyntaxColors, ThemeBase } from "./themes/types";
@@ -18,6 +19,7 @@ export const THEMES: AppTheme[] = [
1819
EMBER_THEME,
1920
CATPPUCCIN_LATTE_THEME,
2021
CATPPUCCIN_MOCHA_THEME,
22+
ZENBURN_THEME,
2123
];
2224

2325
/** Return the built-in theme by id so config-defined themes can inherit from it. */

0 commit comments

Comments
 (0)