summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--docs/config.md9
-rw-r--r--docs/themes.md5
-rw-r--r--src/pardes.zig39
-rw-r--r--src/themes/atelier.zig1
-rw-r--r--src/themes/clay.zig1
-rw-r--r--src/themes/daybreak.zig1
-rw-r--r--src/themes/dusk.zig1
-rw-r--r--src/themes/forge.zig1
-rw-r--r--src/themes/forge_black.zig1
-rw-r--r--src/themes/forge_soft.zig1
-rw-r--r--src/themes/harvest.zig1
-rw-r--r--src/themes/ink.zig1
-rw-r--r--src/themes/lagoon.zig1
-rw-r--r--src/themes/orchard.zig1
-rw-r--r--src/themes/orchard_black.zig1
-rw-r--r--src/themes/paper.zig1
-rw-r--r--src/themes/solarium.zig1
-rw-r--r--src/themes/spectrum.zig1
-rw-r--r--test/panes.zig117
19 files changed, 183 insertions, 2 deletions
diff --git a/docs/config.md b/docs/config.md
index e8c25ceb..899d4cc4 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -254,8 +254,8 @@ format, and it is `pardes.Theme` serialised by `std.zon.stringify`: the theme
name, thirteen required RGB roles (`sel_bg`, `sel_fg`, `tag_bg`, `tag_fg`,
`box`, `box_dim`, `kw`, `str`, `num`, `comment`, `lineno`, `scroll_track`,
`scroll_thumb`), nullable `bg`/`fg`, and either a 16-color RGB `palette` or
-`null`. Nine optional nullable RGB roles extend this format:
-`tag_active_bg`, `tag_active_fg`, `border`, `search_bg`, `search_fg`,
+`null`. Optional nullable RGB roles extend this format:
+`tag_active_bg`, `tag_active_fg`, `tag_name_fg`, `border`, `lineno_active`, `search_bg`, `search_fg`,
`diagnostic_error`, `diagnostic_warning`, `diagnostic_info`, and
`diagnostic_hint`. Missing new roles use backward-compatible defaults, so
previously exported files remain valid. [Theme customization](themes.md)
@@ -263,6 +263,11 @@ describes each role and its fallback. RGB values are three-byte arrays, and
hex literals are accepted. The original fields remain required; there is no
inheritance or partial override layer.
+`tag_name_fg` gives the filename at the end of a pane's path a separate
+foreground on both normal and active tags. When omitted or `null`, it uses
+the corresponding tag foreground. Directory text and tag commands retain
+their regular colors; selecting filename text uses the selection colors.
+
`Filter` in a terminal's tag projects that pane's ANSI colors through the
active theme, and does it in two stages. The default foreground and background
roles are mapped FIRST, because ghostty-vt generates the whole 256-color
diff --git a/docs/themes.md b/docs/themes.md
index 7d71d0dd..d2cad95b 100644
--- a/docs/themes.md
+++ b/docs/themes.md
@@ -37,6 +37,10 @@ tints. Active pane and column tags use a restrained tone-on-tone shift, not
a light/dark inversion: dark palettes keep dark tags, and light palettes
keep light tags. A slight text lift and the focus marker retain the cue
without turning the entire command strip into a highlight.
+The filename at the end of a pane's path uses a subtle accent tint, mixed
+from the tag text and each palette's accent. The directory and commands keep
+their usual foreground. Filename text meets the same contrast threshold on
+both normal and active tag backgrounds.
Comments are colorful first-class text, while line numbers stay quiet;
the current line number gets a small color emphasis and bold weight, never a bright tag-colored
block. These are appearance changes only;
@@ -98,6 +102,7 @@ so existing exported themes remain valid.
| Optional role | Purpose | When absent |
| --- | --- | --- |
| `tag_active_bg`, `tag_active_fg` | Focused pane tag surface and text | `tag_bg`, `tag_fg` |
+| `tag_name_fg` | Filename at the end of the pane's path, on normal and active tags | The corresponding normal or active tag foreground |
| `border` | Quiet separators | `scroll_track` |
| `lineno_active` | Restrained current line number foreground | `lineno` |
| `search_bg`, `search_fg` | Search matches, independent of selection | `sel_bg`, `sel_fg` |
diff --git a/src/pardes.zig b/src/pardes.zig
index 537b212a..3c0205d2 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -2894,6 +2894,8 @@ pub const Theme = struct {
// editor palettes keep working; ChromeTheme resolves their legacy defaults.
tag_active_bg: ?[3]u8 = null,
tag_active_fg: ?[3]u8 = null,
+ /// Quiet filename/output-name accent; directories and commands keep tag ink.
+ tag_name_fg: ?[3]u8 = null,
border: ?[3]u8 = null,
search_bg: ?[3]u8 = null,
search_fg: ?[3]u8 = null,
@@ -2987,6 +2989,9 @@ test "native themes keep text and UI roles readable on their own surfaces" {
try std.testing.expect(themeContrast(th.comment, th.bg.?) >= themeContrast(th.lineno, th.bg.?) + 2);
try std.testing.expect(themeContrast(th.tag_fg, th.tag_bg) >= minimum);
try std.testing.expect(themeContrast(chrome.tag_active_fg, chrome.tag_active_bg) >= minimum);
+ try std.testing.expect(th.tag_name_fg != null);
+ try std.testing.expect(themeContrast(chrome.tag_name_fg, th.tag_bg) >= minimum);
+ try std.testing.expect(themeContrast(chrome.tag_active_name_fg, chrome.tag_active_bg) >= minimum);
// Focus stays on the same tonal surface, rather than inverting a
// whole command strip. Text remains readable in both states.
try std.testing.expect(themeContrast(chrome.tag_active_bg, th.tag_bg) <= 1.6);
@@ -3071,9 +3076,12 @@ test "legacy ThemeFile documents inherit Pardes UI roles without new fields" {
const parsed = try std.zon.parse.fromSliceAlloc(Theme, std.testing.allocator, source, null, .{});
defer std.zon.parse.free(std.testing.allocator, parsed);
try std.testing.expectEqual(@as(?[3]u8, null), parsed.tag_active_bg);
+ try std.testing.expectEqual(@as(?[3]u8, null), parsed.tag_name_fg);
try std.testing.expectEqual(@as(?[3]u8, null), parsed.search_bg);
const chrome = ChromeTheme.fromTheme(&parsed);
try std.testing.expectEqual(parsed.tag_bg, chrome.tag_active_bg);
+ try std.testing.expectEqual(parsed.tag_fg, chrome.tag_name_fg);
+ try std.testing.expectEqual(chrome.tag_active_fg, chrome.tag_active_name_fg);
try std.testing.expectEqual(parsed.sel_bg, chrome.search_bg);
try std.testing.expectEqual(parsed.fg.?, chrome.diagnostic_error);
}
@@ -3134,6 +3142,8 @@ pub const ChromeTheme = struct {
tag_fg: [3]u8,
tag_active_bg: [3]u8,
tag_active_fg: [3]u8,
+ tag_name_fg: [3]u8,
+ tag_active_name_fg: [3]u8,
border: [3]u8,
search_bg: [3]u8,
search_fg: [3]u8,
@@ -3153,6 +3163,8 @@ pub const ChromeTheme = struct {
.tag_fg = th.tag_fg,
.tag_active_bg = th.tag_active_bg orelse th.tag_bg,
.tag_active_fg = th.tag_active_fg orelse th.tag_fg,
+ .tag_name_fg = th.tag_name_fg orelse th.tag_fg,
+ .tag_active_name_fg = th.tag_name_fg orelse (th.tag_active_fg orelse th.tag_fg),
.border = th.border orelse th.scroll_track,
.search_bg = th.search_bg orelse th.sel_bg,
.search_fg = th.search_fg orelse th.sel_fg,
@@ -7090,6 +7102,22 @@ pub const Pardes = struct {
return out;
}
+ fn tagNameRange(pane: *const Pane, prefix_len: usize) ?struct { lo: usize, hi: usize } {
+ var start: usize = 0;
+ const name = if (pane.file) |file|
+ if (pane.tag_name_len) |len| pane.tag_name[0..len] else file.path
+ else if (pane.image) |state| blk: {
+ // Image tags put renderer controls before the path.
+ start = prefix_len -| state.path.len;
+ break :blk state.path;
+ } else if (comptime pdf_enabled) blk: {
+ break :blk if (pane.pdf) |state| state.path else return null;
+ } else return null;
+ const basename = if (std.mem.lastIndexOfScalar(u8, name, '/')) |slash| slash + 1 else 0;
+ if (basename == name.len) return null;
+ return .{ .lo = start + basename, .hi = start + name.len };
+ }
+
pub fn seedTail(_: *Pardes, pane: *Pane) void {
if (pane.tag_init) return;
const tail = curTail(pane);
@@ -13094,6 +13122,17 @@ pub const Pardes = struct {
tag.len;
const tag_start = @min(tag_cut, panes.File.rawAtDisplay(tag, pane.tag_scroll));
_ = s.print(tx, tag_y, tw, tag[tag_start..tag_cut], tag_style);
+ if (tagNameRange(pane, tag.len - curTail(pane).len)) |name| {
+ const lo = @max(name.lo, tag_start);
+ const hi = @min(name.hi, tag_cut);
+ if (lo < hi) {
+ var col = panes.File.rawDisplayCol(tag, lo) -| pane.tag_scroll;
+ const end = panes.File.rawDisplayCol(tag, hi) -| pane.tag_scroll;
+ const name_fg = if (active and p.settings.focus_tint) chrome.tag_active_name_fg else chrome.tag_name_fg;
+ while (col < end and col < tw) : (col += 1)
+ s.at(tx + @as(u16, @intCast(col)), tag_y).style.fg = .{ .rgb = name_fg };
+ }
+ }
if (p.look_hover_preview) |preview| {
if (preview.pane == id and preview.serial == pane.serial) if (preview.sel) |sel| if (@min(sel.r0, sel.r1) < BOX_H) {
const preview_bg = mix(page_rgb, mix(page_rgb, th.sel_bg));
diff --git a/src/themes/atelier.zig b/src/themes/atelier.zig
index 8b45623b..ba1ca0cf 100644
--- a/src/themes/atelier.zig
+++ b/src/themes/atelier.zig
@@ -7,6 +7,7 @@ pub const theme = .{
.fg = .{ 0x29, 0x3f, 0x35 },
.tag_bg = .{ 0xe4, 0xf0, 0xed },
.tag_fg = .{ 0x3d, 0x60, 0x55 },
+ .tag_name_fg = .{ 0x3d, 0x66, 0x5a },
.tag_active_bg = .{ 0xd3, 0xe7, 0xdf },
.tag_active_fg = .{ 0x35, 0x58, 0x4d },
.box = .{ 0x3e, 0x7a, 0x68 },
diff --git a/src/themes/clay.zig b/src/themes/clay.zig
index bf9e7027..80a689d8 100644
--- a/src/themes/clay.zig
+++ b/src/themes/clay.zig
@@ -8,6 +8,7 @@ pub const theme = .{
.fg = .{ 0xf3, 0xe5, 0xbc },
.tag_bg = .{ 0x34, 0x3f, 0x36 },
.tag_fg = .{ 0xd7, 0xd6, 0xb6 },
+ .tag_name_fg = .{ 0xc5, 0xd0, 0xa8 },
.tag_active_bg = .{ 0x42, 0x4f, 0x40 },
.tag_active_fg = .{ 0xe3, 0xe2, 0xc2 },
.box = .{ 0x8e, 0xc0, 0x7c },
diff --git a/src/themes/daybreak.zig b/src/themes/daybreak.zig
index 28c0ac0a..07fc981b 100644
--- a/src/themes/daybreak.zig
+++ b/src/themes/daybreak.zig
@@ -7,6 +7,7 @@ pub const theme = .{
.fg = .{ 0x14, 0x2d, 0x40 },
.tag_bg = .{ 0xed, 0xf2, 0xf7 },
.tag_fg = .{ 0x29, 0x46, 0x5e },
+ .tag_name_fg = .{ 0x20, 0x4c, 0x6b },
.tag_active_bg = .{ 0xdf, 0xea, 0xf3 },
.tag_active_fg = .{ 0x21, 0x3e, 0x56 },
.box = .{ 0x07, 0x5f, 0x91 },
diff --git a/src/themes/dusk.zig b/src/themes/dusk.zig
index e4ba012b..f3a7bcb6 100644
--- a/src/themes/dusk.zig
+++ b/src/themes/dusk.zig
@@ -7,6 +7,7 @@ pub const theme = .{
.fg = .{ 0xc3, 0xb6, 0xc1 },
.tag_bg = .{ 0x2c, 0x26, 0x30 },
.tag_fg = .{ 0xc6, 0xb6, 0xc5 },
+ .tag_name_fg = .{ 0xce, 0xb5, 0xba },
.tag_active_bg = .{ 0x3b, 0x2f, 0x3b },
.tag_active_fg = .{ 0xd2, 0xc2, 0xd1 },
.box = .{ 0xe4, 0xb3, 0x9b },
diff --git a/src/themes/forge.zig b/src/themes/forge.zig
index ee23640d..0d8c58d0 100644
--- a/src/themes/forge.zig
+++ b/src/themes/forge.zig
@@ -8,6 +8,7 @@ pub const theme = .{
.fg = .{ 0xe5, 0xe5, 0xe5 },
.tag_bg = .{ 0x22, 0x25, 0x2b },
.tag_fg = .{ 0xc5, 0xc9, 0xd2 },
+ .tag_name_fg = .{ 0xbd, 0xc8, 0xd9 },
.tag_active_bg = .{ 0x2d, 0x34, 0x40 },
.tag_active_fg = .{ 0xd1, 0xd5, 0xde },
.box = .{ 0xa5, 0xc4, 0xee },
diff --git a/src/themes/forge_black.zig b/src/themes/forge_black.zig
index d9605e97..16c9700a 100644
--- a/src/themes/forge_black.zig
+++ b/src/themes/forge_black.zig
@@ -8,6 +8,7 @@ pub const theme = .{
.fg = .{ 0xf6, 0xf6, 0xf6 },
.tag_bg = .{ 0x22, 0x25, 0x2b },
.tag_fg = .{ 0xc5, 0xc9, 0xd2 },
+ .tag_name_fg = .{ 0xbd, 0xc8, 0xd9 },
.tag_active_bg = .{ 0x2d, 0x34, 0x40 },
.tag_active_fg = .{ 0xd1, 0xd5, 0xde },
.box = .{ 0xa5, 0xc4, 0xee },
diff --git a/src/themes/forge_soft.zig b/src/themes/forge_soft.zig
index 0d882fbb..97ed5b23 100644
--- a/src/themes/forge_soft.zig
+++ b/src/themes/forge_soft.zig
@@ -8,6 +8,7 @@ pub const theme = .{
.fg = .{ 0xbf, 0xc3, 0xcc },
.tag_bg = .{ 0x2a, 0x2d, 0x34 },
.tag_fg = .{ 0xc5, 0xc9, 0xd2 },
+ .tag_name_fg = .{ 0xbd, 0xc8, 0xd9 },
.tag_active_bg = .{ 0x37, 0x3e, 0x49 },
.tag_active_fg = .{ 0xd1, 0xd5, 0xde },
.box = .{ 0xa5, 0xc4, 0xee },
diff --git a/src/themes/harvest.zig b/src/themes/harvest.zig
index 0536ffa8..d22e2a52 100644
--- a/src/themes/harvest.zig
+++ b/src/themes/harvest.zig
@@ -8,6 +8,7 @@ pub const theme = .{
.fg = .{ 0xf3, 0xf2, 0xcc },
.tag_bg = .{ 0x34, 0x3b, 0x2e },
.tag_fg = .{ 0xd5, 0xd8, 0xb8 },
+ .tag_name_fg = .{ 0xd4, 0xd0, 0xad },
.tag_active_bg = .{ 0x42, 0x4d, 0x38 },
.tag_active_fg = .{ 0xe1, 0xe4, 0xc4 },
.box = .{ 0xcf, 0xba, 0x8b },
diff --git a/src/themes/ink.zig b/src/themes/ink.zig
index d34f8fae..71dbd328 100644
--- a/src/themes/ink.zig
+++ b/src/themes/ink.zig
@@ -7,6 +7,7 @@ pub const theme = .{
.fg = .{ 0xf3, 0xf7, 0xfc },
.tag_bg = .{ 0x15, 0x21, 0x2c },
.tag_fg = .{ 0xd5, 0xe4, 0xf1 },
+ .tag_name_fg = .{ 0xc1, 0xe1, 0xf4 },
.tag_active_bg = .{ 0x22, 0x33, 0x42 },
.tag_active_fg = .{ 0xe1, 0xee, 0xf9 },
.box = .{ 0x86, 0xd8, 0xff },
diff --git a/src/themes/lagoon.zig b/src/themes/lagoon.zig
index cc444ff1..33411f88 100644
--- a/src/themes/lagoon.zig
+++ b/src/themes/lagoon.zig
@@ -8,6 +8,7 @@ pub const theme = .{
.fg = .{ 0xba, 0xc9, 0xce },
.tag_bg = .{ 0x29, 0x44, 0x3f },
.tag_fg = .{ 0xbb, 0xd7, 0xcb },
+ .tag_name_fg = .{ 0xae, 0xd3, 0xc5 },
.tag_active_bg = .{ 0x34, 0x57, 0x4d },
.tag_active_fg = .{ 0xc7, 0xe3, 0xd7 },
.box = .{ 0x89, 0xc7, 0xb4 },
diff --git a/src/themes/orchard.zig b/src/themes/orchard.zig
index 17ac801a..2ed5c14d 100644
--- a/src/themes/orchard.zig
+++ b/src/themes/orchard.zig
@@ -7,6 +7,7 @@ pub const theme = .{
.fg = .{ 0xe5, 0xee, 0xe5 },
.tag_bg = .{ 0x1d, 0x28, 0x24 },
.tag_fg = .{ 0xb2, 0xc3, 0xb8 },
+ .tag_name_fg = .{ 0xb1, 0xc5, 0xae },
.tag_active_bg = .{ 0x26, 0x36, 0x2d },
.tag_active_fg = .{ 0xbe, 0xcf, 0xc4 },
.box = .{ 0xad, 0xcc, 0x91 },
diff --git a/src/themes/orchard_black.zig b/src/themes/orchard_black.zig
index bf53c66a..e3b2ef27 100644
--- a/src/themes/orchard_black.zig
+++ b/src/themes/orchard_black.zig
@@ -7,6 +7,7 @@ pub const theme = .{
.fg = .{ 0xf2, 0xf8, 0xef },
.tag_bg = .{ 0x1d, 0x28, 0x24 },
.tag_fg = .{ 0xb2, 0xc3, 0xb8 },
+ .tag_name_fg = .{ 0xb1, 0xc5, 0xae },
.tag_active_bg = .{ 0x26, 0x36, 0x2d },
.tag_active_fg = .{ 0xbe, 0xcf, 0xc4 },
.box = .{ 0xad, 0xcc, 0x91 },
diff --git a/src/themes/paper.zig b/src/themes/paper.zig
index 7bfb891d..bcb3a513 100644
--- a/src/themes/paper.zig
+++ b/src/themes/paper.zig
@@ -7,6 +7,7 @@ pub const theme = .{
.fg = .{ 0x30, 0x3b, 0x3b },
.tag_bg = .{ 0xe8, 0xea, 0xe3 },
.tag_fg = .{ 0x52, 0x62, 0x5a },
+ .tag_name_fg = .{ 0x4c, 0x64, 0x59 },
.tag_active_bg = .{ 0xdc, 0xe5, 0xdc },
.tag_active_fg = .{ 0x4a, 0x5a, 0x52 },
.box = .{ 0x3c, 0x6b, 0x56 },
diff --git a/src/themes/solarium.zig b/src/themes/solarium.zig
index 84866830..9a7e7fab 100644
--- a/src/themes/solarium.zig
+++ b/src/themes/solarium.zig
@@ -8,6 +8,7 @@ pub const theme = .{
.fg = .{ 0xa7, 0xb7, 0xae },
.tag_bg = .{ 0x12, 0x3f, 0x43 },
.tag_fg = .{ 0xc7, 0xd4, 0xbd },
+ .tag_name_fg = .{ 0xca, 0xce, 0xaa },
.tag_active_bg = .{ 0x21, 0x4e, 0x50 },
.tag_active_fg = .{ 0xd3, 0xe0, 0xc9 },
.box = .{ 0xd5, 0xbc, 0x72 },
diff --git a/src/themes/spectrum.zig b/src/themes/spectrum.zig
index 8c43fada..70645771 100644
--- a/src/themes/spectrum.zig
+++ b/src/themes/spectrum.zig
@@ -8,6 +8,7 @@ pub const theme = .{
.fg = .{ 0xfc, 0xfc, 0xfa },
.tag_bg = .{ 0x3b, 0x38, 0x30 },
.tag_fg = .{ 0xdd, 0xd8, 0xbb },
+ .tag_name_fg = .{ 0xe6, 0xd8, 0xa6 },
.tag_active_bg = .{ 0x4d, 0x47, 0x36 },
.tag_active_fg = .{ 0xe9, 0xe4, 0xc7 },
.box = .{ 0xff, 0xd8, 0x66 },
diff --git a/test/panes.zig b/test/panes.zig
index 97dc3927..a7e59923 100644
--- a/test/panes.zig
+++ b/test/panes.zig
@@ -518,6 +518,122 @@ const JumpSelectionTests = struct {
}
};
+const TagNameTintTests = struct {
+ const name_fg = [3]u8{ 231, 137, 43 };
+ const tag_fg = [3]u8{ 111, 122, 133 };
+
+ fn configure(p: *Pardes) void {
+ var theme = p.theme().*;
+ theme.tag_name_fg = name_fg;
+ theme.tag_fg = tag_fg;
+ p.custom_theme = theme;
+ p.chrome_animation.snap(pardes.ChromeTheme.fromTheme(p.theme()));
+ p.settings.focus_tint = false;
+ p.presentation.enabled = false;
+ }
+
+ fn check(p: *Pardes, id: usize, name: []const u8) !usize {
+ const pane = p.panes[id].?;
+ var arena = std.heap.ArenaAllocator.init(p.gpa);
+ defer arena.deinit();
+ const surface = try p.render(arena.allocator());
+ const text = try p.tagText(arena.allocator(), pane);
+ const base = std.mem.lastIndexOfScalar(u8, name, '/') orelse return error.MissingDirectory;
+ const rect = p.rects[id];
+ const y = if (p.settings.tag_bottom) rect.y + rect.h - pardes.BOX_H else rect.y;
+ const left = rect.x + config.GUTTER;
+ var visible_name: usize = 0;
+ var at: usize = 0;
+ while (at < text.len) : (at = modal.nextGrapheme(text, at)) {
+ const start: i32 = @as(i32, @intCast(panes.File.rawDisplayCol(text, at))) - pane.tag_scroll;
+ const end = start + @as(i32, @intCast(panes.File.displayWidth(text[at..modal.nextGrapheme(text, at)])));
+ var col = @max(0, start);
+ while (col < end and col < rect.w - config.GUTTER) : (col += 1) {
+ const in_name = at > base and at < name.len;
+ const fg = if (in_name) name_fg else tag_fg;
+ try std.testing.expectEqual(pardes.Color{ .rgb = fg }, surface.at(left + @as(u16, @intCast(col)), y).style.fg);
+ if (in_name) visible_name += 1;
+ }
+ }
+ return visible_name;
+ }
+
+ test "tag filename tint excludes directories dirty marker and output commands" {
+ for ([_]bool{ false, true }) |bottom| {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 24 });
+ defer p.deinit();
+ const pane = try p.setTestFile("body\n");
+ p.gpa.free(pane.file.?.path);
+ pane.file.?.path = try p.gpa.dupe(u8, "/project/็•Œfile.zig");
+ pane.file.?.revision += 1;
+ configure(p);
+ defer p.custom_theme = null;
+ p.settings.tag_bottom = bottom;
+ try std.testing.expect((try check(p, 0, pane.file.?.path)) > 0);
+ try panes.Output.fillResults(p, 0, "/project", .search, "tint", try p.gpa.dupe(u8, "no matches\n"), null);
+ const id = pane.search_pane.?;
+ p.update(.tick);
+ try std.testing.expect((try check(p, id, p.panes[id].?.file.?.path)) > 0);
+ }
+ }
+
+ test "tag filename tint follows scrolled Unicode staged names and yields to selection" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 38, .rows = 14 });
+ defer p.deinit();
+ const pane = try p.setTestFile("body\n");
+ const old = pane.file.?.path.len;
+ configure(p);
+ defer p.custom_theme = null;
+ p.update(.{ .key = .{ .cp = ':' } });
+ p.update(.{ .key = .{ .cp = 'i' } });
+ pane.tag_anchor = 0;
+ pane.tag_col = @intCast(old - 1);
+ pane.tag_sel = true;
+ const name = "/a/very/long/directory/that/extends/past/the/tag/็•Œe\u{301}name-with-a-long-tail.zig";
+ p.update(.{ .paste = name });
+ try std.testing.expectEqualStrings(name, pane.tag_name[0..pane.tag_name_len.?]);
+ pane.tag_col = @intCast(std.mem.indexOf(u8, name, "็•Œ").?);
+ pane.tag_sel = false;
+ try std.testing.expect((try check(p, 0, name)) > 0);
+ try std.testing.expect(pane.tag_scroll > 0);
+ pane.tag_anchor = pane.tag_col;
+ pane.tag_sel = true;
+ pane.tag_scroll += 4; // bring both cells of the selected wide glyph into view
+ var arena = std.heap.ArenaAllocator.init(p.gpa);
+ defer arena.deinit();
+ const surface = try p.render(arena.allocator());
+ const x = p.rects[0].x + config.GUTTER + @as(u16, @intCast(panes.File.rawDisplayCol(name, pane.tag_col) - pane.tag_scroll));
+ const y = if (p.settings.tag_bottom) p.rects[0].y + p.rects[0].h - pardes.BOX_H else p.rects[0].y;
+ try std.testing.expectEqual(pardes.Color{ .rgb = p.theme().sel_fg }, surface.at(x, y).style.fg);
+ try std.testing.expectEqual(pardes.Color{ .rgb = p.theme().sel_fg }, surface.at(x + 1, y).style.fg);
+ }
+
+ test "tag filename tint legacy themes retain distinct active foreground" {
+ const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 70, .rows = 14 });
+ defer p.deinit();
+ const pane = try p.setTestFile("body\n");
+ p.gpa.free(pane.file.?.path);
+ pane.file.?.path = try p.gpa.dupe(u8, "/dir/file.zig");
+ configure(p);
+ defer p.custom_theme = null;
+ p.custom_theme.?.tag_name_fg = null;
+ const active_fg = [3]u8{ 175, 183, 194 };
+ p.custom_theme.?.tag_active_fg = active_fg;
+ p.chrome_animation.snap(pardes.ChromeTheme.fromTheme(p.theme()));
+ var arena = std.heap.ArenaAllocator.init(p.gpa);
+ defer arena.deinit();
+ for ([_]bool{ false, true }) |active| {
+ p.settings.focus_tint = active;
+ const surface = try p.render(arena.allocator());
+ const x = p.rects[0].x + config.GUTTER;
+ const y = if (p.settings.tag_bottom) p.rects[0].y + p.rects[0].h - pardes.BOX_H else p.rects[0].y;
+ const fg = if (active) active_fg else tag_fg;
+ try std.testing.expectEqual(pardes.Color{ .rgb = fg }, surface.at(x + 1, y).style.fg);
+ try std.testing.expectEqual(pardes.Color{ .rgb = fg }, surface.at(x + 5, y).style.fg);
+ }
+ }
+};
+
test "pane filename edits stage commit and cancel without changing body or command tail" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 70, .rows = 12 });
defer p.deinit();
@@ -3259,6 +3375,7 @@ const TerminalTests = struct {
};
test {
+ _ = TagNameTintTests;
_ = TreeContextTests;
_ = TtySelectionTests;
_ = ClickCursorTests;