summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 23:48:17 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commita6c4ab97de9f9624d4d4d37a4f63cae342e314fb (patch)
tree0dc35fd6821344602556ef94cfdaf174ba9911e5
parentc284475c8681ad57c32497c7948e0b020281f4ed (diff)
downloadpardes-a6c4ab97de9f9624d4d4d37a4f63cae342e314fb.tar.gz
pardes-a6c4ab97de9f9624d4d4d37a4f63cae342e314fb.zip
A dump keeps the settings changed from a fresh session's, and Restore sets them
A session running with Placement pardes came back from Restore as acme: a dump kept the layout and the theme but no setting. It now keeps every setting line that differs from a fresh session's, as the root ctl reads it, and a Restore applies them; the theme stays where it was kept, the font the frontend's, and REPL bindings are still dropped. The dump golden gains the three the snapshot harness sets. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--docs/config.md5
-rw-r--r--src/dump.zig54
-rw-r--r--src/ninep/ctl.zig18
-rw-r--r--test/snapshots/dump.golden5
4 files changed, 75 insertions, 7 deletions
diff --git a/docs/config.md b/docs/config.md
index 434a1778..43b289cb 100644
--- a/docs/config.md
+++ b/docs/config.md
@@ -637,6 +637,11 @@ uses the argument as a path as before. Absolute paths and argument-free Restore
retain their existing behavior. `Dump` still honors `$PARDES_DUMP` and otherwise
writes a timestamped file in the default directory.
+A dump keeps the settings that differ from a fresh session's (`Placement
+pardes`, `Verbose off`, a shader), as the root ctl reads them, and a Restore
+sets them again; the theme is kept with the layout, and the font stays the
+frontend's. REPL bindings are not kept.
+
A restored terminal comes back live: its recorded output (the last MiB of it)
is replayed as history, a dim `── restored history ──` line marks where it
ends, and a new shell starts below it in the directory the old one was in,
diff --git a/src/dump.zig b/src/dump.zig
index 5ebb3c8d..77e2c2ba 100644
--- a/src/dump.zig
+++ b/src/dump.zig
@@ -200,6 +200,11 @@ pub const State = struct {
columns: []const Column = &.{},
panes: []const Pane = &.{},
mounts: []const Mount = &.{},
+ /// The settings that differ from a fresh session's, each a line as the
+ /// root ctl takes it (`Placement pardes`); the theme and the locations
+ /// config are kept on their own, and the font is the frontend's. Absent
+ /// in older dumps.
+ settings: []const []const u8 = &.{},
};
/// A header is a text like any other: it may hold newlines, but not a
@@ -680,6 +685,7 @@ pub fn dumpState(p: *Pardes) !void {
.columns = columns[0..columns_len],
.panes = dump_panes[0..panes_len],
.mounts = mounts[0..p.fs.mounts.items.len],
+ .settings = try changedSettings(p, arena),
};
try validate(state);
var out: std.Io.Writer.Allocating = .init(p.gpa);
@@ -690,6 +696,37 @@ pub fn dumpState(p: *Pardes) !void {
p.emit(.write_dump);
}
+/// The setting lines that differ from a fresh session's, as the root ctl
+/// reads them, less the ones a dump keeps apart.
+fn changedSettings(p: *Pardes, arena: std.mem.Allocator) ![]const []const u8 {
+ var now: std.Io.Writer.Allocating = .init(arena);
+ var fresh: std.Io.Writer.Allocating = .init(arena);
+ const defaults: config.Runtime = .{};
+ try pardes.ctlfs.ctl.writeSettingsOf(p, &p.settings, &now.writer);
+ try pardes.ctlfs.ctl.writeSettingsOf(p, &defaults, &fresh.writer);
+ var out: std.ArrayList([]const u8) = .empty;
+ var lines = std.mem.splitScalar(u8, now.written(), '\n');
+ while (lines.next()) |line| {
+ if (line.len == 0) continue;
+ const word = line[0 .. std.mem.indexOfScalar(u8, line, ' ') orelse line.len];
+ if (std.mem.eql(u8, word, "Theme") or std.mem.eql(u8, word, "Font")) continue;
+ var same = std.mem.splitScalar(u8, fresh.written(), '\n');
+ while (same.next()) |other| {
+ if (std.mem.eql(u8, other, line)) break;
+ } else try out.append(arena, line);
+ }
+ return out.items;
+}
+
+/// Applies a dump's setting lines, each as the root ctl would.
+fn applySettings(p: *Pardes, lines: []const []const u8) void {
+ for (lines) |line| {
+ const sp = std.mem.indexOfScalar(u8, line, ' ');
+ const setting = config.Runtime.find(line[0 .. sp orelse line.len]) orelse continue;
+ _ = p.settings.apply(setting, if (sp) |s| line[s + 1 ..] else null);
+ }
+}
+
/// The replacement core for a Restore of the dump at `from`, whose log says
/// so after its panes' `new`s, for a client that reconnects to read.
pub fn restore(p: *Pardes, zon_bytes: []const u8, from: []const u8) !*Pardes {
@@ -745,6 +782,7 @@ fn initDump(gpa: std.mem.Allocator, opts: Options, zon_bytes: []const u8, previo
const st = parsed.value;
p.locations_config = st.locations_config;
p.settings.tree_context_tag_style = st.tree_context_tag_style;
+ applySettings(p, st.settings);
for (st.mounts) |mount| {
var already_mounted = false;
for (p.fs.mounts.items) |existing| {
@@ -849,3 +887,19 @@ fn initDump(gpa: std.mem.Allocator, opts: Options, zon_bytes: []const u8, previo
ctlfs.events.announce(p);
return p;
}
+
+test "a dump keeps the settings that differ from a fresh session's, and a restore applies them" {
+ const gpa = std.testing.allocator;
+ const p = try Pardes.init(gpa, .{ .tty_only = true });
+ defer p.deinit();
+ try std.testing.expect(p.executeBuiltinLine(p.active, "Placement pardes"));
+ try std.testing.expect(p.executeBuiltinLine(p.active, "Verbose off"));
+ try dumpState(p);
+ try std.testing.expect(std.mem.indexOf(u8, p.dump_out.?, "\"Placement pardes\"") != null);
+ // What is left at its default is not written.
+ try std.testing.expect(std.mem.indexOf(u8, p.dump_out.?, "\"BootShell keep\"") == null);
+ const restored = try initFromDump(gpa, .{ .tty_only = true }, p.dump_out.?);
+ defer restored.deinit();
+ try std.testing.expectEqual(config.Runtime.Placement.pardes, restored.settings.placement);
+ try std.testing.expect(!restored.settings.verbose);
+}
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 4faa7df7..11de3c1c 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -333,13 +333,22 @@ const e_prompt = "control message needs its argument";
/// What is not a setting -- platform, themes, where the config came from --
/// is /status's or the Config window's.
fn writeSettings(p: *Pardes, w: *std.Io.Writer) !void {
- const s = &p.settings;
+ try writeSettingsOf(p, &p.settings, w);
+ try w.writeAll("LocationsConfig ");
+ try p.locations_config.write(w);
+ try w.writeByte('\n');
+ if (comptime builtins.Pet.enabled) try w.print("Pet {s}\n", .{@tagName(p.settings.pet)});
+}
+
+/// The setting words of `s`, one a line, as the root ctl reads them; a Dump
+/// keeps those that differ from a fresh session's (dump.zig).
+pub fn writeSettingsOf(p: *Pardes, s: *const config.Runtime, w: *std.Io.Writer) !void {
inline for (config.Runtime.settings) |setting| {
if (comptime !setting.enabled(builtins.capabilities)) continue;
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, p.theme().name }),
+ .theme => try w.print("{s} {s}\n", .{ word, if (s == &p.settings) p.theme().name else 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.default_shell });
@@ -360,7 +369,6 @@ fn writeSettings(p: *Pardes, w: *std.Io.Writer) !void {
inline else => |field| try w.print("{s} {s}\n", .{ word, @tagName(@field(s, @tagName(field))) }),
},
.message_ms => |which| try w.print("{s} {d}\n", .{ word, @field(s, "message_" ++ @tagName(which) ++ "_ms") }),
- // Bare, it names the default directory.
// The directory in effect, as Config reports it; bare only when
// there is none (no home to put the default in).
.dump_dir => {
@@ -377,10 +385,6 @@ fn writeSettings(p: *Pardes, w: *std.Io.Writer) !void {
.grip_width => try w.print("{s} {d}\n", .{ word, s.grip_width }),
}
}
- try w.writeAll("LocationsConfig ");
- try p.locations_config.write(w);
- try w.writeByte('\n');
- if (comptime builtins.Pet.enabled) try w.print("Pet {s}\n", .{@tagName(s.pet)});
}
pub fn rootLen(p: *Pardes) u64 {
diff --git a/test/snapshots/dump.golden b/test/snapshots/dump.golden
index deed689c..277d3d34 100644
--- a/test/snapshots/dump.golden
+++ b/test/snapshots/dump.golden
@@ -98,5 +98,10 @@
},
},
.mounts = .{},
+ .settings = .{
+ "MessageAnimation off",
+ "MessageLinger 0",
+ "Shell bash",
+ },
}
== end file