From 939c369aee270067d9f030e2b234d93bae549988 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Wed, 30 Sep 2026 19:26:12 -0300 Subject: After a ThemeFile, the root ctl and DumpConfig say ThemeFile and its path, so what they report writes back Both said Theme and the name inside the .zon, which no Theme knows, so writing the report back to /ctl failed with "no theme". While a loaded ThemeFile is the theme in effect they now say the word that loaded it. A Dump carries the line too, so a Restore reloads the file. Co-Authored-By: Claude Opus 5.5 --- src/Output.zig | 1 + src/colors.zig | 8 ++++++++ src/config.zig | 8 +++++++- src/ninep/ctl.zig | 39 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 54 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/Output.zig b/src/Output.zig index 01797cbe..47fcc7e4 100644 --- a/src/Output.zig +++ b/src/Output.zig @@ -870,6 +870,7 @@ pub fn openConfig(p: *Pardes, id: usize) !void { .startup_config_path = p.opts.startup_config_path, .platform = @tagName(pardes.platform), .theme_name = p.theme().name, + .theme_file = pardes.colors.loadedThemeFile(p), .compiled_default_shell = config.defaultShell(), .hover_delay_frames = config.look_preview_delay_frames, .native_images = p.native_images, diff --git a/src/colors.zig b/src/colors.zig index c78043cc..8d541c13 100644 --- a/src/colors.zig +++ b/src/colors.zig @@ -735,6 +735,14 @@ pub fn requestThemeFile(p: *Pardes, id: usize, argument: []const u8) void { p.emit(.{ .theme_file = .{ .generation = generation, .on = true } }); } +/// The .zon a ThemeFile loaded, while it is the theme in effect: what +/// /ctl and DumpConfig say in place of `Theme `, since the name +/// inside the file is no theme `Theme` knows. +pub fn loadedThemeFile(p: *const Pardes) ?[]const u8 { + const path = p.theme_file_path.get(); + return if (p.custom_theme != null and path.len > 0) path else null; +} + pub const ThemeFileRequest = struct { path: []const u8, pane: u8, diff --git a/src/config.zig b/src/config.zig index 592281d3..a7377cfa 100644 --- a/src/config.zig +++ b/src/config.zig @@ -1267,6 +1267,9 @@ pub const Runtime = struct { startup_config_path: ?[]const u8, platform: []const u8, theme_name: []const u8, + /// The ThemeFile in effect, said as `ThemeFile ` in place + /// of the theme's name. + theme_file: ?[]const u8 = null, compiled_default_shell: []const u8, hover_delay_frames: ?u16, /// Where dumps go when `DumpDir` is unset; empty without a host @@ -1305,7 +1308,10 @@ pub const Runtime = struct { }; try out.print("{s} {s}\n", .{ setting.word, onOff(value) }); }, - .theme => try out.print("{s} {s}\n", .{ setting.word, context.theme_name }), + .theme => if (context.theme_file) |path| + try out.print("ThemeFile {s}\n", .{path}) + else + try out.print("{s} {s}\n", .{ setting.word, context.theme_name }), .window_opacity => { if (setting.enabled(context.capabilities)) try out.print("{s} {d}\n", .{ setting.word, state.window_opacity }) diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 902a9647..bc27f72b 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -554,7 +554,9 @@ pub fn writeSettingsOf(p: *Pardes, s: *const config.Runtime, w: *std.Io.Writer) const word = setting.word; switch (setting.action) { .toggle => |field| try w.print("{s} {s}\n", .{ word, if (@field(s, @tagName(field))) "on" else "off" }), - .theme => try w.print("{s} {s}\n", .{ word, if (s == &p.settings) p.theme().name else pardes.themes[s.theme].name }), + .theme => if (s == &p.settings) { + if (pardes.colors.loadedThemeFile(p)) |path| try w.print("ThemeFile {s}\n", .{path}) else try w.print("{s} {s}\n", .{ word, p.theme().name }); + } else try w.print("{s} {s}\n", .{ word, pardes.themes[s.theme].name }), .shell => { const chosen = s.shell.requested.get(); try w.print("{s} {s}\n", .{ word, if (chosen.len > 0) chosen else config.defaultShell() }); @@ -1829,6 +1831,41 @@ test "an error's words give the errno a mount reads: EINVAL for what is malforme } } +test "after a ThemeFile the root ctl and DumpConfig say ThemeFile and its path, which writes back" { + if (comptime !pardes.hosted) return; + const gpa = testing.allocator; + const p = try withFile(gpa, "x\n"); + defer p.deinit(); + const root_ctl = @intFromEnum(tree.TopFile.ctl); + const path = "/tmp/pardes-mine-theme.zon"; + try testing.expectEqual(Status.ok, wr(p, root_ctl, "ThemeFile " ++ path ++ "\n").reply.status); + var custom = pardes.themes[0]; + custom.name = "mine"; + var encoded: std.Io.Writer.Allocating = .init(gpa); + defer encoded.deinit(); + try std.zon.stringify.serialize(custom, .{}, &encoded.writer); + try testing.expect(pardes.colors.loadThemeFile(p, p.theme_file_generation, encoded.written(), false)); + const settings = rd(p, root_ctl, 0, 1 << 16).bytes; + try testing.expect(std.mem.indexOf(u8, settings, "ThemeFile " ++ path ++ "\n") != null); + try testing.expect(std.mem.indexOf(u8, settings, "Theme mine") == null); + var storage: [16384]u8 = undefined; + var out: std.Io.Writer = .fixed(&storage); + try config.Runtime.writeReport(&out, .{ + .startup_config_path = null, + .platform = @tagName(pardes.platform), + .theme_name = p.theme().name, + .theme_file = pardes.colors.loadedThemeFile(p), + .compiled_default_shell = config.defaultShell(), + .hover_delay_frames = null, + .native_images = false, + .capabilities = builtins.capabilities, + .state = &p.settings, + }); + try testing.expect(std.mem.indexOf(u8, out.buffered(), "ThemeFile " ++ path ++ "\n") != null); + // Written back, it is the same request again, not "no theme mine". + try testing.expectEqual(Status.ok, wr(p, root_ctl, "ThemeFile " ++ path ++ "\n").reply.status); +} + test "Joincol with no column to the right and Theme with no such theme say so" { const gpa = testing.allocator; const p = try withFile(gpa, "x\n"); -- cgit v1.3