Add customizable terminal colors (global theme + per-pane overrides)
Four editable colors (background/foreground/cursor/selection) via a new ColorPanel modal with built-in presets and live preview. Global default persists to localStorage and syncs across windows; per-pane overrides ride on LeafNode.colorOverride in the workspace tree. Titlebar 🎨 button edits the global theme; per-pane 🎨 chip overrides a single pane. Subsumes the prior uncommitted softened-foreground tweak into lib/theme.ts. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1febf2e096
commit
7e624a3f96
10 changed files with 938 additions and 5 deletions
79
src/lib/theme.test.ts
Normal file
79
src/lib/theme.test.ts
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
import { describe, it, expect } from "vitest";
|
||||
import {
|
||||
resolvePaneColors,
|
||||
toXtermTheme,
|
||||
DEFAULT_PANE_COLORS,
|
||||
COLOR_PRESETS,
|
||||
type PaneColors,
|
||||
} from "./theme";
|
||||
|
||||
describe("resolvePaneColors", () => {
|
||||
it("falls back to defaults when nothing is set", () => {
|
||||
expect(resolvePaneColors(undefined, undefined)).toEqual(DEFAULT_PANE_COLORS);
|
||||
});
|
||||
|
||||
it("uses global values over defaults", () => {
|
||||
const global: PaneColors = { background: "#111111", cursor: "#abcdef" };
|
||||
const r = resolvePaneColors(global, undefined);
|
||||
expect(r.background).toBe("#111111");
|
||||
expect(r.cursor).toBe("#abcdef");
|
||||
// Unset fields still come from defaults.
|
||||
expect(r.foreground).toBe(DEFAULT_PANE_COLORS.foreground);
|
||||
expect(r.selection).toBe(DEFAULT_PANE_COLORS.selection);
|
||||
});
|
||||
|
||||
it("per-pane override wins over global, field by field", () => {
|
||||
const global: PaneColors = { background: "#111111", foreground: "#222222" };
|
||||
const override: PaneColors = { background: "#999999" };
|
||||
const r = resolvePaneColors(global, override);
|
||||
expect(r.background).toBe("#999999"); // override wins
|
||||
expect(r.foreground).toBe("#222222"); // inherits global
|
||||
expect(r.cursor).toBe(DEFAULT_PANE_COLORS.cursor); // inherits default
|
||||
});
|
||||
|
||||
it("always returns all four fields defined", () => {
|
||||
const r = resolvePaneColors({}, {});
|
||||
expect(Object.keys(r).sort()).toEqual([
|
||||
"background",
|
||||
"cursor",
|
||||
"foreground",
|
||||
"selection",
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("toXtermTheme", () => {
|
||||
it("maps resolved colours onto the xterm ITheme shape", () => {
|
||||
const theme = toXtermTheme({
|
||||
background: "#0c0c0c",
|
||||
foreground: "#c5c8c6",
|
||||
cursor: "#ffffff",
|
||||
selection: "#3a3a3a",
|
||||
});
|
||||
expect(theme.background).toBe("#0c0c0c");
|
||||
expect(theme.foreground).toBe("#c5c8c6");
|
||||
expect(theme.cursor).toBe("#ffffff");
|
||||
// selection maps to xterm 5.x's renamed property.
|
||||
expect(theme.selectionBackground).toBe("#3a3a3a");
|
||||
// cursorAccent is pinned to the background for block-cursor legibility.
|
||||
expect(theme.cursorAccent).toBe("#0c0c0c");
|
||||
});
|
||||
|
||||
it("keeps the fixed softened white/brightWhite slice", () => {
|
||||
const theme = toXtermTheme(DEFAULT_PANE_COLORS);
|
||||
expect(theme.white).toBe("#c5c8c6");
|
||||
expect(theme.brightWhite).toBe("#e0e0e0");
|
||||
});
|
||||
});
|
||||
|
||||
describe("COLOR_PRESETS", () => {
|
||||
it("starts with the tiletopia default and every preset is fully specified", () => {
|
||||
expect(COLOR_PRESETS[0].name).toBe("Tiletopia Dark");
|
||||
expect(COLOR_PRESETS[0].colors).toEqual(DEFAULT_PANE_COLORS);
|
||||
for (const p of COLOR_PRESETS) {
|
||||
for (const key of ["background", "foreground", "cursor", "selection"] as const) {
|
||||
expect(p.colors[key]).toMatch(/^#[0-9a-fA-F]{6}$/);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue