summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-27 22:34:56 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit7ba54de4bd498c2a56ea3d0d43c88356759f7b1f (patch)
tree2e661d1785ef6e74c158ca6b2e1aa96b763ec7bc /src/pardes.zig
parent52049344c1992850ea712e029eb5c4a88a7dfe19 (diff)
downloadpardes-7ba54de4bd498c2a56ea3d0d43c88356759f7b1f.tar.gz
pardes-7ba54de4bd498c2a56ea3d0d43c88356759f7b1f.zip
Move saving and loading a whole editor out of pardes.zig into dump.zig
Pure move, no behaviour change (acme keeps dump and load in rows.c): dumpState, restore, initFromDump and initDump go verbatim to the end of dump.zig, after the dump format they read and write. Inside dump.zig the moved code's `dump.` prefix drops, so `Pane` there is the dump record; the one editor pane it names is spelled `pardes.panes.Pane`, and its other `panes.X` references become `pardes.panes.X` because dump.zig's own tests use `panes` as a local name. The methods become free functions taking `p: *Pardes`: `p.dumpState()` becomes `dump.dumpState(p)`, `core.restore(bytes)` becomes `dump.restore(core, bytes)` and `Pardes.initFromDump(..)` becomes `dump.initFromDump(..)`, in pardes.zig, the shells, layout.zig, Terminal.zig, builtins.zig and the tests (38 receiver rewrites plus the initFromDump calls). The tag-tail restore helpers stay with the tag code. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig260
1 files changed, 22 insertions, 238 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index 75766827..44f0d2ea 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -779,8 +779,8 @@ test "LocationsConfig command reports partial updates and survives restore" {
try std.testing.expectEqualStrings("LocationsConfig context:5 tscontext:on tslocations:off layout:stacked\n", file.content);
}
try std.testing.expectEqual(@as(usize, 1), reports);
- try p.dumpState();
- const restored = try Pardes.initFromDump(p.gpa, .{ .tty_only = true }, p.dump_out.?);
+ try dump.dumpState(p);
+ const restored = try dump.initFromDump(p.gpa, .{ .tty_only = true }, p.dump_out.?);
defer restored.deinit();
try std.testing.expectEqual(expected, restored.locations_config);
}
@@ -829,8 +829,8 @@ test "TreeContext tag style preserves message rows and its opt-out survives rest
try std.testing.expectEqual(@as(u16, 0), p.surface.body_layers[0].rows);
try std.testing.expectEqual(@as(u16, 0), pane.body_rows);
try std.testing.expectEqual(FontRole.body, p.surface.at(r.x + config.GUTTER, p.bodyTop(r)).style.font_role);
- try p.dumpState();
- const restored = try Pardes.initFromDump(p.gpa, .{ .tty_only = true }, p.dump_out.?);
+ try dump.dumpState(p);
+ const restored = try dump.initFromDump(p.gpa, .{ .tty_only = true }, p.dump_out.?);
defer restored.deinit();
try std.testing.expect(!restored.settings.tree_context_tag_style);
}
@@ -858,8 +858,8 @@ test "TreeContext command and custom tag survive a workspace round trip" {
try std.testing.expect(pane.file.?.tree_context);
try std.testing.expect(pane.appendTag(" Save Keep Del"));
pane.tag_init = true;
- try p.dumpState();
- const restored = try Pardes.initFromDump(p.gpa, .{ .tty_only = true }, p.dump_out.?);
+ try dump.dumpState(p);
+ const restored = try dump.initFromDump(p.gpa, .{ .tty_only = true }, p.dump_out.?);
defer restored.deinit();
const saved = restored.panes[0].?;
try std.testing.expect(saved.file.?.tree_context);
@@ -1397,7 +1397,7 @@ test "image dump restores source bytes renderer choices and exact custom tail" {
pane.image.?.ascii = false;
try std.testing.expect(pane.appendTag(" Keep Del"));
pane.tag_init = true;
- try p.dumpState();
+ try dump.dumpState(p);
var first_dump = try dump.readZon(gpa, p.dump_out.?, "image-first-dump");
defer first_dump.deinit();
@@ -1407,7 +1407,7 @@ test "image dump restores source bytes renderer choices and exact custom tail" {
try std.testing.expect(!first.panes[0].image.?.ascii);
try std.testing.expectEqualStrings(" Keep Del", first.panes[0].tag_tail.?);
- const restored = try Pardes.initFromDump(gpa, .{ .tty_only = true }, p.dump_out.?);
+ const restored = try dump.initFromDump(gpa, .{ .tty_only = true }, p.dump_out.?);
defer restored.deinit();
const restored_pane = restored.panes[0].?;
try std.testing.expect(restored_pane.image.?.glyph_art);
@@ -1417,7 +1417,7 @@ test "image dump restores source bytes renderer choices and exact custom tail" {
try std.testing.expectEqualStrings(" Keep Del", restored_pane.tagSlice());
try std.testing.expectEqualSlices(u8, source, restored_pane.image.?.raw);
- try restored.dumpState();
+ try dump.dumpState(restored);
var parsed = try dump.readZon(gpa, restored.dump_out.?, "image-redump");
defer parsed.deinit();
const redump = parsed.value;
@@ -1767,13 +1767,13 @@ test "owned cwd restore either copies the directory or preserves the old core" {
const p = try Pardes.init(allocator.allocator(), .{ .tty_only = true });
defer p.deinit();
p.setCwd(0, "/retained/terminal/directory");
- try p.dumpState();
+ try dump.dumpState(p);
const original = p.panes[0].?;
var completed = false;
for (0..256) |failure| {
allocator.has_induced_failure = false;
allocator.fail_index = allocator.alloc_index + failure;
- const replacement = p.restore(p.dump_out.?) catch {
+ const replacement = dump.restore(p, p.dump_out.?) catch {
try std.testing.expect(allocator.has_induced_failure);
try std.testing.expectEqual(original, p.panes[0].?);
try std.testing.expectEqualStrings("/retained/terminal/directory", original.cwdSlice());
@@ -2305,9 +2305,9 @@ test "startup config overrides restored styling before initFromDump returns" {
const source = try Pardes.init(std.testing.allocator, .{ .startup_config = "Theme dark\n" });
defer source.deinit();
try std.testing.expectEqualStrings("dark", source.theme().name);
- try source.dumpState();
+ try dump.dumpState(source);
- const restored = try Pardes.initFromDump(
+ const restored = try dump.initFromDump(
std.testing.allocator,
.{ .startup_config = "Theme acme\n" },
source.dump_out.?,
@@ -2324,9 +2324,9 @@ test "restored sessions animate layout changes after bootstrap" {
if (platform == .web) return;
const source = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 16 });
defer source.deinit();
- try source.dumpState();
+ try dump.dumpState(source);
- const restored = try Pardes.initFromDump(
+ const restored = try dump.initFromDump(
std.testing.allocator,
.{ .tty_only = true, .cols = 80, .rows = 16, .startup_config = "PanelSlide\n" },
source.dump_out.?,
@@ -2355,9 +2355,9 @@ test "restored terminals keep monotonic nonzero pane identities" {
// Filter is a fresh presentation default, not emulator/dump state. Even a
// pane toggled off in the source session comes back with the default on.
source.panes[1].?.tty_filter = false;
- try source.dumpState();
+ try dump.dumpState(source);
- const restored = try Pardes.initFromDump(gpa, .{}, source.dump_out.?);
+ const restored = try dump.initFromDump(gpa, .{}, source.dump_out.?);
defer restored.deinit();
var ids: [MAX_PANES]usize = undefined;
@@ -2721,14 +2721,14 @@ test "editable workspace and column tags replace selections and persist" {
p.update(.{ .paste = "Grep " });
try std.testing.expect(std.mem.startsWith(u8, p.columnTag(0), "Grep New"));
try std.testing.expectEqualStrings("untouched\n", p.panes[0].?.file.?.content);
- try p.dumpState();
- const restored = try p.restore(p.dump_out.?);
+ try dump.dumpState(p);
+ const restored = try dump.restore(p, p.dump_out.?);
defer restored.deinit();
try std.testing.expectEqualStrings(p.global_tag.slice(""), restored.global_tag.slice(""));
try std.testing.expectEqualStrings(p.columnTag(0), restored.columnTag(0));
try std.testing.expect(p.col_tags[0].set(""));
- try p.dumpState();
- const empty = try p.restore(p.dump_out.?);
+ try dump.dumpState(p);
+ const empty = try dump.restore(p, p.dump_out.?);
defer empty.deinit();
try std.testing.expectEqualStrings("", empty.columnTag(0));
}
@@ -5819,223 +5819,7 @@ pub const Pardes = struct {
// ---- dump / load (acme-style: `pardes -l state.zon` restores a session) ----
- pub fn dumpState(p: *Pardes) !void {
- const arena = p.scratch.allocator();
- var slot_to_pane: [MAX_PANES]?usize = @splat(null);
- var dump_panes: [MAX_PANES]dump.Pane = undefined;
- var panes_len: usize = 0;
- for (p.panes, 0..) |slot, id| {
- const pane = slot orelse continue;
- slot_to_pane[id] = panes_len;
- const tag = try p.tagText(arena, pane);
- const body = try body_layer.bodyText(p, arena, pane);
- const scroll: usize = @intCast(@max(0, pane.scroll()));
- var dp: dump.Pane = if (pane.file) |*f| try panes.File.dumpPane(
- arena,
- pane,
- f,
- tag,
- body,
- scroll,
- if (f.output) |o| panes.Output.word(o.from) else "",
- if (f.output) |*o| o.arg() else "",
- ) else if (pane.hasPdf()) blk: {
- if (comptime !pdf_enabled) unreachable;
- const pv = &pane.pdf.?;
- break :blk try panes.Image.dumpPane(p, arena, pane, tag, body, pv.page, pv.path, &.{});
- } else if (pane.image) |iv|
- try panes.Image.dumpPane(p, arena, pane, tag, body, scroll, iv.path, iv.raw)
- else
- try panes.Terminal.dumpPane(pane, arena, tag, body, scroll);
- dp.tag_tail = if (pane.tag_init) pane.tagSlice() else null;
- dump_panes[panes_len] = dp;
- panes_len += 1;
- }
-
- var columns: [MAX_COLS]dump.Column = undefined;
- var columns_len: usize = 0;
- var column_ids: [MAX_COLS][MAX_PANES]usize = undefined;
- for (0..p.ncol) |c| {
- var ids_len: usize = 0;
- for (0..p.col_n[c]) |k| if (slot_to_pane[p.col_panes[c][k]]) |compact| {
- column_ids[c][ids_len] = compact;
- ids_len += 1;
- };
- if (ids_len > 0) {
- columns[columns_len] = .{
- .tag = if (p.col_tags[c].custom) p.columnTag(c) else null,
- .weight = @as(f32, @floatFromInt(p.col_weight[c])) / @as(f32, @floatFromInt(column_weight_unit)),
- .panes = column_ids[c][0..ids_len],
- };
- columns_len += 1;
- }
- }
-
- var mounts: [filesystem.max_mounts]dump.Mount = undefined;
- for (p.fs.mounts.items, 0..) |mount, i| mounts[i] = .{ .name = mount.name, .dial = mount.dial };
- const state: dump.State = .{
- .screen = .{ .cols = p.screen_w, .rows = p.screen_h },
- .active = slot_to_pane[p.active] orelse 0,
- .topbar = config.topbar_str,
- .topbar_custom = if (p.global_tag.custom) p.global_tag.slice("") else null,
- .theme = p.theme().name,
- .locations_config = p.locations_config,
- .tree_context_tag_style = p.settings.tree_context_tag_style,
- .columns = columns[0..columns_len],
- .panes = dump_panes[0..panes_len],
- .mounts = mounts[0..p.fs.mounts.items.len],
- };
- try dump.validate(state);
- var out: std.Io.Writer.Allocating = .init(p.gpa);
- errdefer out.deinit();
- try std.zon.stringify.serialize(state, .{ .whitespace = true }, &out.writer);
- if (p.dump_out) |d| p.gpa.free(d);
- p.dump_out = try out.toOwnedSlice();
- p.emit(.write_dump);
- }
-
- pub fn restore(p: *Pardes, zon_bytes: []const u8) !*Pardes {
- var opts = p.opts;
- opts.cols = p.screen_w;
- opts.rows = p.screen_h;
- return initDump(p.gpa, opts, zon_bytes, p);
- }
-
- pub fn initFromDump(gpa: std.mem.Allocator, opts: Options, zon_bytes: []const u8) !*Pardes {
- return initDump(gpa, opts, zon_bytes, null);
- }
-
- fn initDump(gpa: std.mem.Allocator, opts: Options, zon_bytes: []const u8, previous: ?*const Pardes) !*Pardes {
- const image_gpa = opts.image_allocator orelse gpa;
- const pdf_gpa = opts.pdf_allocator orelse gpa;
- const tree_sitter_gpa = opts.tree_sitter_allocator orelse gpa;
- const p = try gpa.create(Pardes);
- p.* = .{
- .gpa = gpa,
- .image_gpa = image_gpa,
- .pdf_gpa = pdf_gpa,
- .tree_sitter_gpa = tree_sitter_gpa,
- .opts = opts,
- .fs = .{},
- .screen_w = opts.cols,
- .screen_h = opts.rows,
- .scratch = .init(gpa),
- .frame_arena = .init(opts.frame_allocator orelse gpa),
- .fallback = .{ .gpa = gpa },
- };
- errdefer p.deinit();
- if (previous) |old| {
- p.next_serial = old.next_serial;
- p.lsp_seq = old.lsp_seq;
- p.pipe_seq = old.pipe_seq;
- p.cell_pixels = old.cell_pixels;
- p.row_metrics = old.row_metrics;
- p.native_images = old.native_images;
- }
- for (opts.mounts) |mount| try p.fs.mount(gpa, mount.name, mount.dial);
- p.opts.mounts = &.{};
- var parsed = try dump.readZon(gpa, zon_bytes, "load");
- defer parsed.deinit();
- const st = parsed.value;
- p.locations_config = st.locations_config;
- p.settings.tree_context_tag_style = st.tree_context_tag_style;
- for (st.mounts) |mount| {
- var already_mounted = false;
- for (p.fs.mounts.items) |existing| {
- if (!std.mem.eql(u8, mount.name, existing.name)) continue;
- if (!std.mem.eql(u8, mount.dial, existing.dial)) return error.MountConflict;
- already_mounted = true;
- break;
- }
- if (!already_mounted) try p.fs.mount(gpa, mount.name, mount.dial);
- }
-
- for (themes, 0..) |t, i| {
- if (std.mem.eql(u8, t.name, st.theme)) p.settings.theme = @intCast(i);
- }
- for (st.panes, 0..) |src, i| {
- if (i >= MAX_PANES) break;
- const pane: *Pane = switch (src.kind) {
- .terminal => terminal: {
- const t = src.terminal.?;
- // A host that runs shells gets them back: the old history
- // above a new shell in the directory it was in. The web
- // shell and an isolated build only ever replay it.
- const live = comptime (terminal_panes and platform != .web and !isolated);
- const revive = live and t.cwd.len <= effect_path_cap;
- const restored = try panes.Terminal.restore(p, src, revive);
- p.installPane(i, restored);
- try restored.setOwnedCwd(t.cwd);
- if (std.mem.startsWith(u8, src.tag, "TTY ")) restored.mode = .tty;
- if (revive) p.emit(.{ .spawn = .{ .pane = @intCast(i), .cwd = .from(t.cwd) } });
- break :terminal restored;
- },
- .file => try panes.File.restore(p, i, src),
- .image => restore_image: {
- const im = src.image.?;
- if (panes.Pdf.isPath(im.path)) {
- var raw: []u8 = if (im.bytes_b64.len > 0)
- try dump.decodeBytes(gpa, im.bytes_b64)
- else
- &.{};
- errdefer if (raw.len > 0) gpa.free(raw);
- if (comptime pdf_enabled) {
- if (panes.Pdf.openPane(p, i, im.path, src.scroll + 1) catch null) |restored| {
- if (raw.len > 0) gpa.free(raw);
- raw = &.{};
- restored.cols = @max(1, src.cols);
- restored.rows = @max(1, src.rows);
- break :restore_image restored;
- }
- }
- // If the source path is unavailable, retain its bytes
- // as an ordinary file rather than dropping the pane.
- const path = try gpa.dupe(u8, im.path);
- errdefer gpa.free(path);
- const content = if (raw.len > 0) raw else try gpa.dupe(u8, "");
- raw = &.{};
- errdefer if (content.len > 0) gpa.free(content);
- const history = try panes.File.History.create(gpa);
- errdefer gpa.destroy(history);
- const restored = try p.newDocPane(i);
- restored.file = .{ .path = path, .content = content, .history = history };
- restored.cur_pinned = true;
- restored.cols = @max(1, src.cols);
- restored.rows = @max(1, src.rows);
- p.emit(.{ .watch = .{ .pane = @intCast(i), .on = true, .mode = .baseline_disk } });
- break :restore_image restored;
- }
- break :restore_image try panes.Image.restore(p, i, src);
- },
- };
- p.restoreDumpTail(pane, src);
- pane.vweight = src.vweight;
- pane.collapsed = src.collapsed;
- }
- p.ncol = @min(st.columns.len, MAX_COLS);
- p.col_serial = @splat(0);
- if (st.topbar_custom) |bar| _ = p.global_tag.set(bar);
- for (st.columns[0..p.ncol], 0..) |col, c| {
- if (col.tag) |bar| _ = p.col_tags[c].set(bar);
- const scaled = @as(f64, @floatCast(col.weight)) * @as(f64, @floatFromInt(column_weight_unit));
- const bounded = @min(scaled, @as(f64, @floatFromInt(max_column_weight)));
- p.col_weight[c] = @max(1, @as(u64, @intFromFloat(@round(bounded))));
- p.col_n[c] = @min(col.panes.len, MAX_PANES);
- for (col.panes[0..p.col_n[c]], 0..) |pid, k| p.col_panes[c][k] = pid;
- }
- p.active = @min(st.active, MAX_PANES - 1);
- p.sync();
- exec.applyStartupConfig(p);
- colors.finishThemeInitialization(p);
- p.sync();
- p.presentation.enabled = true;
- _ = p.takeHaptic(); // see init: a restored session is not a gesture
- try p.fs.log.buf.ensureTotalCapacityPrecise(p.gpa, p.fs.log.cap);
- ctlfs.events.announce(p);
- return p;
- }
-
- fn restoreDumpTail(p: *Pardes, pane: *Pane, src: dump.Pane) void {
+ pub fn restoreDumpTail(p: *Pardes, pane: *Pane, src: dump.Pane) void {
if (src.tag_tail) |tail| {
if (!pane.appendTag(tail)) return;
pane.tag_init = true;