diff options
| author | Gabriel Schneider <[email protected]> | 2026-10-01 02:37:39 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 02:37:39 -0300 |
| commit | 6c0e9c5a6b6d4b0aa77f084fdac5eb4f6d1f482b (patch) | |
| tree | 7567624fe00948134ab5ee59f6cd081872318bce /src | |
| parent | 803f2a2678a8aa8f89b0663a84a60721d36f5831 (diff) | |
| download | pardes-6c0e9c5a6b6d4b0aa77f084fdac5eb4f6d1f482b.tar.gz pardes-6c0e9c5a6b6d4b0aa77f084fdac5eb4f6d1f482b.zip | |
A ThemeFile that fails to load changes nothing: /ctl and DumpConfig keep naming the file last loaded, or none, so they always replay
The request's path was taken as the theme file before the host had
read it, so a file not found or not ZON left /ctl saying ThemeFile
<that path>, and writing the report back failed again. The path a load
succeeds with is now kept apart; a failure puts the request back to it
(and watches it again), or to none.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/colors.zig | 21 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 26 | ||||
| -rw-r--r-- | src/pardes.zig | 3 |
3 files changed, 49 insertions, 1 deletions
diff --git a/src/colors.zig b/src/colors.zig index b75cdc3d..d04c009e 100644 --- a/src/colors.zig +++ b/src/colors.zig @@ -9,6 +9,7 @@ const animation = @import("animation.zig"); const Color = @import("surface.zig").Color; const FontRole = @import("surface.zig").FontRole; const config = @import("config.zig"); +const limits = @import("memory.zig").limits; const pdf_enabled = panes.Pdf.enabled; const platform = pardes.platform; const theme_animation = @import("pardes_config").theme_animation; @@ -739,7 +740,7 @@ pub fn requestThemeFile(p: *Pardes, id: usize, argument: []const u8) void { /// /ctl and DumpConfig say in place of `Theme <name>`, 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(); + const path = p.theme_file_loaded.get(); return if (p.custom_theme != null and path.len > 0) path else null; } @@ -769,6 +770,20 @@ fn themeFileError(p: *Pardes, request: ThemeFileRequest, err: anyerror) void { var cut: [200]u8 = undefined; const path = @import("ninep/tree.zig").fitErr(request.path, &cut); p.reportError(request.pane, std.fmt.bufPrint(&said, "theme file {s}", .{path}) catch "theme file", err); + revertThemeFile(p); +} + +/// A ThemeFile that did not load changes nothing: the request goes back to +/// the file last loaded (watched again), or to none, so /ctl and DumpConfig +/// still read what replays. A bad save of the loaded file itself keeps it. +fn revertThemeFile(p: *Pardes) void { + const loaded = p.theme_file_loaded.get(); + if (std.mem.eql(u8, loaded, p.theme_file_path.get())) return; + var keep: [limits.host_path_cap]u8 = undefined; + @memcpy(keep[0..loaded.len], loaded); + if (loaded.len == 0) p.theme_file_path.clear() else _ = p.theme_file_path.set(keep[0..loaded.len]); + const generation = nextThemeFileGeneration(p); + p.emit(.{ .theme_file = .{ .generation = generation, .on = loaded.len > 0 } }); } pub fn loadThemeFile(p: *Pardes, generation: u32, bytes: []const u8, animate: bool) bool { @@ -804,6 +819,9 @@ pub fn loadThemeFile(p: *Pardes, generation: u32, bytes: []const u8, animate: bo const old = p.custom_theme; p.custom_theme = parsed; if (old) |theme_value| std.zon.parse.free(p.gpa, theme_value); + var settled: [limits.host_path_cap]u8 = undefined; + @memcpy(settled[0..request.path.len], request.path); + _ = p.theme_file_loaded.set(settled[0..request.path.len]); invalidateThemeDependentRasters(p); _ = p.scratch.reset(.retain_capacity); p.sync(); @@ -912,6 +930,7 @@ pub fn setThemeIndex(p: *Pardes, index: usize) void { p.custom_theme = null; std.zon.parse.free(p.gpa, theme_value); } + p.theme_file_loaded.clear(); if (p.theme_file_path.get().len > 0) { p.theme_file_path.clear(); const generation = nextThemeFileGeneration(p); diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index a4979f7d..e963da3f 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -2034,6 +2034,32 @@ test "DumpConfig's whole report writes back to the root ctl, line by line, with } } +test "a ThemeFile that fails to load changes nothing: the root ctl still names the one loaded, or none" { + 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); + // None loaded: a failed one leaves Theme as it was. + _ = wr(p, root_ctl, "ThemeFile /tmp/pardes-no-such-theme.zon\n"); + pardes.colors.failThemeFile(p, p.theme_file_generation, error.FileNotFound); + try testing.expect(std.mem.indexOf(u8, rd(p, root_ctl, 0, 1 << 16).bytes, "ThemeFile") == null); + try testing.expectEqualStrings("", p.theme_file_path.get()); + // One loaded, then another that fails: still the first, on every read. + const good = "/tmp/pardes-good-theme.zon"; + _ = wr(p, root_ctl, "ThemeFile " ++ good ++ "\n"); + var custom = pardes.themes[0]; + custom.name = "good"; + 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)); + _ = wr(p, root_ctl, "ThemeFile /tmp/pardes-bad-theme.zon\n"); + try testing.expect(!pardes.colors.loadThemeFile(p, p.theme_file_generation, ".{ .name = ", false)); + try testing.expect(std.mem.indexOf(u8, rd(p, root_ctl, 0, 1 << 16).bytes, "ThemeFile " ++ good ++ "\n") != null); + try testing.expectEqualStrings(good, p.theme_file_path.get()); +} + 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; diff --git a/src/pardes.zig b/src/pardes.zig index 5d0ab7e8..945fcafa 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -4351,6 +4351,9 @@ pub const Pardes = struct { /// bodyChrome's: the theme it was worked out for, and it. body_chrome: ?struct { theme: Theme, chrome: ChromeTheme } = null, theme_file_path: config.Runtime.Text(limits.host_path_cap) = .{}, + /// The ThemeFile last loaded whole: what /ctl and DumpConfig name, and + /// what a failed ThemeFile goes back to (colors.revertThemeFile). + theme_file_loaded: config.Runtime.Text(limits.host_path_cap) = .{}, theme_file_generation: u32 = 0, theme_file_pane: u8 = 0, chrome_animation: colors.ChromeAnimation = colors.ChromeAnimation.init(colors.initial_chrome), |
