diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-27 22:34:56 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 7ba54de4bd498c2a56ea3d0d43c88356759f7b1f (patch) | |
| tree | 2e661d1785ef6e74c158ca6b2e1aa96b763ec7bc /src/pardes.zig | |
| parent | 52049344c1992850ea712e029eb5c4a88a7dfe19 (diff) | |
| download | pardes-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.zig | 260 |
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; |
