summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-06 20:43:40 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commit513938c1cfb216a3bc4a3559cf7731b8e44a952f (patch)
tree7b711c30ea6ebc255a1d691223b2cb93cd7dedf2 /src/pardes.zig
parentd0ba070a87c54c538e5f788485391b0af927d0fb (diff)
downloadpardes-513938c1cfb216a3bc4a3559cf7731b8e44a952f.tar.gz
pardes-513938c1cfb216a3bc4a3559cf7731b8e44a952f.zip
dumps: per-user data dir + timestamped names; Restore builtin loads a dump into the live session. dump.outPath resolves $PARDES_DUMP verbatim (the snapshot harness pins /tmp/pardes-dump.zon so dump/load/restore goldens stay deterministic), else $XDG_DATA_HOME|~/.local/share /pardes/pardes-YYYYMMDD-HHMMSS.zon (dir mkdir-ed; wall clock via libc clock_gettime — std.time.timestamp is gone in 0.16). After write_dump the shell reports the written path back (core.setLastDump) and the topbar grows "Restore <path>": middle-clicking Restore reloads the last dump, and "Restore <path>" executed anywhere takes the path as its first argument (onSelect prefix match). Core stays sans-IO: the builtin stashes restore_req; each shell consumes takeRestore() after drainEffects and swaps in initFromDump at the current screen size (pre-sized: dump panes never greet), tearing down the live ptys (tty: cancel reader + close + gen bump; gui: SIGKILL, the gen-guarded eof closes the old fds). Web ignores it. New restore.snap golden: KEEPME survives the restore, GONESOON echoed after Dump does not, the topbar word appears then resets. 29/29.
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig55
1 files changed, 50 insertions, 5 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index 9842362f..a2cfe1c6 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -44,7 +44,7 @@ const file_pane_tail = " " ++ file_pane_builtins_str;
// Builtins: executing the name (middle-click / Tab) runs the action through the
// ONE dispatcher (onSelect + actOnSelection), no matter where the name appears.
-const SelectAction = enum { none, send, newcol, delcol, del, save, tutor, kill, debug, colors, nextcolor, dump };
+const SelectAction = enum { none, send, newcol, delcol, del, save, tutor, kill, debug, colors, nextcolor, dump, restore };
const Builtin = struct { name: []const u8, action: SelectAction };
const builtins = [_]Builtin{
.{ .name = "Kill", .action = .kill },
@@ -54,6 +54,7 @@ const builtins = [_]Builtin{
.{ .name = "Colors", .action = .colors },
.{ .name = "NextColor", .action = .nextcolor },
.{ .name = "Dump", .action = .dump },
+ .{ .name = "Restore", .action = .restore },
.{ .name = "Save", .action = .save },
.{ .name = "Del", .action = .del },
.{ .name = "Delcol", .action = .delcol },
@@ -66,6 +67,8 @@ fn onSelect(button: Mouse.Button, text: []const u8) SelectAction {
inline for (builtins) |b| {
if (std.mem.eql(u8, cmd, b.name)) return b.action;
}
+ // Restore takes the dump path as its first argument
+ if (std.mem.startsWith(u8, cmd, "Restore ") or std.mem.startsWith(u8, cmd, "Restore\t")) return .restore;
return .send;
}
@@ -340,8 +343,9 @@ pub const Effect = union(enum) {
/// write this pane's file content to its path; the shell reads both off
/// the core (content is unbounded, effects are fixed-size values)
save_file: struct { pane: u8 },
- /// a serialized state dump is ready in core.dump_out; write it to
- /// dump.default_path (acme-style: another instance loads it with -l)
+ /// a serialized state dump is ready in core.dump_out; write it to the
+ /// path dump.outPath resolves (acme-style: another instance loads it
+ /// with -l, or the Restore builtin loads it into this one)
write_dump,
/// the yank register changed; the shell reads it off the core (OSC 52 out)
set_clipboard,
@@ -663,6 +667,13 @@ pub const Pardes = struct {
yank: ?[]u8 = null,
/// the last serialized dump (gpa-owned), read by the write_dump effect
dump_out: ?[]u8 = null,
+ /// where the shell wrote the last dump (shell reports back after
+ /// write_dump); shown in the topbar as `Restore <path>`
+ last_dump: ?[]const u8 = null,
+ /// a Restore builtin wants this dump loaded into the live session; the
+ /// shell consumes it via takeRestore each frame (core stays sans-IO)
+ restore_req: ?[]const u8 = null,
+ restore_buf: [1024]u8 = undefined,
surface: Surface = .{},
/// per-update scratch (paneCursorLines, selection text); reset each update
@@ -710,6 +721,7 @@ pub const Pardes = struct {
const gpa = p.gpa;
if (p.yank) |y| gpa.free(y);
if (p.dump_out) |d| gpa.free(d);
+ if (p.last_dump) |d| gpa.free(d);
if (p.chord_arg) |a| gpa.free(a);
p.effects.deinit(gpa);
p.scratch.deinit();
@@ -717,6 +729,29 @@ pub const Pardes = struct {
gpa.destroy(p);
}
+ /// shell feedback after write_dump: remember where the dump landed (the
+ /// topbar grows a `Restore <path>` word pointing at it)
+ pub fn setLastDump(p: *Pardes, path: []const u8) void {
+ const copy = p.gpa.dupe(u8, path) catch return;
+ if (p.last_dump) |old| p.gpa.free(old);
+ p.last_dump = copy;
+ }
+
+ /// the shell polls this each frame: a pending Restore's dump path, or null
+ pub fn takeRestore(p: *Pardes) ?[]const u8 {
+ const r = p.restore_req;
+ p.restore_req = null;
+ return r;
+ }
+
+ /// the topbar line: the fixed builtins, plus `Restore <path>` once a dump
+ /// exists — render and click dispatch must agree on this exact string
+ fn topbar(p: *Pardes, buf: []u8) []const u8 {
+ if (p.last_dump) |d|
+ return std.fmt.bufPrint(buf, topbar_str ++ " Restore {s}", .{d}) catch topbar_str;
+ return topbar_str;
+ }
+
fn deinitPane(p: *Pardes, pane: *Pane) void {
if (pane.image) |*iv| {
p.gpa.free(iv.path);
@@ -2374,7 +2409,8 @@ pub const Pardes = struct {
// global tagbar: MIDDLE runs the command under the click
// (a stray left click must never Kill)
if (m.button == .middle) {
- const word = wordAtCol(topbar_str, mcol);
+ var tb_buf: [1200]u8 = undefined;
+ const word = wordAtCol(p.topbar(&tb_buf), mcol);
if (word.len > 0) p.actOnSelection(.middle, p.active, word, null);
}
return;
@@ -2870,6 +2906,14 @@ pub const Pardes = struct {
.colors => p.colors_on = !p.colors_on,
.nextcolor => p.theme_idx = (p.theme_idx + 1) % themes.len,
.dump => p.dumpState() catch {},
+ .restore => {
+ var it = std.mem.tokenizeAny(u8, std.mem.trim(u8, txt, " \t\r\n"), " \t");
+ _ = it.next(); // the word "Restore"
+ const path = it.next() orelse (p.last_dump orelse return);
+ if (path.len > p.restore_buf.len) return;
+ @memcpy(p.restore_buf[0..path.len], path);
+ p.restore_req = p.restore_buf[0..path.len];
+ },
.kill => {
p.quit = true;
p.emit(.quit);
@@ -3335,7 +3379,8 @@ pub const Pardes = struct {
// global tagbar: full width, top row
s.fill(0, 0, s.cols, TOPBAR_H, .{ .bg = .{ .rgb = th.tag_bg } });
- _ = s.print(0, 0, s.cols, topbar_str, .{ .fg = .{ .rgb = th.tag_fg }, .bg = .{ .rgb = th.tag_bg } });
+ var tb_buf: [1200]u8 = undefined;
+ _ = s.print(0, 0, s.cols, p.topbar(&tb_buf), .{ .fg = .{ .rgb = th.tag_fg }, .bg = .{ .rgb = th.tag_bg } });
// resize-handle hint / drag previews: a dash overlay that keeps the
// underlying colors (border drags + hover), or the move indicator