From c6be02adeb930834aeb7d8c484e89106023550d1 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Fri, 2 Oct 2026 00:53:57 -0300 Subject: A Zerox twin's save no longer makes its other twin say reloaded: a watch that finds the pane holding the disk's text already takes it as seen, silently Co-Authored-By: Claude Opus 5.5 --- src/file_watch.zig | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'src') diff --git a/src/file_watch.zig b/src/file_watch.zig index 46764a73..4aba6c39 100644 --- a/src/file_watch.zig +++ b/src/file_watch.zig @@ -469,6 +469,14 @@ pub fn reloadPane( .text => |accepted| if (accepted == hash) return false, .pdf => {}, } + // The pane holds the disk's text already (a Zerox twin saved, its + // other twin watching the same file): nothing reloaded, nothing said. + if (std.mem.eql(u8, file.content, bytes)) { + if (watches[id]) |*live| if (live.serial == watched.serial) { + live.generation = .{ .text = hash }; + }; + return false; + } if (!core.reloadWatchedFile(@intCast(id), bytes)) return false; const current = core.panes[id] orelse return false; if (current.serial != watched.serial) return false; @@ -713,6 +721,38 @@ test "restored file watches preserve snapshots and accept later disk changes" { } } +test "a Zerox twin's save wakes its other twin's watch, which says nothing: their text is the file's already" { + if (comptime !supported or !filesystem.platform_has_fs) return; + const io = std.testing.io; + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + try tmp.dir.writeFile(io, .{ .sub_path = "twinned", .data = "before\n" }); + var path_buf: [256]u8 = undefined; + const path = try std.fmt.bufPrint(&path_buf, ".zig-cache/tmp/{s}/twinned", .{tmp.sub_path}); + const core = try pardes.Pardes.init(std.testing.allocator, .{ .tty_only = true }); + defer core.deinit(); + const pane = try core.setTestFile("before\n"); + core.gpa.free(pane.file.?.path); + pane.file.?.path = try core.gpa.dupe(u8, path); + try std.testing.expect(core.executeBuiltinLine(0, "Zerox")); + const twin: u8 = for (core.panes, 0..) |slot, id| { + if (id != 0) if (slot) |other| if (other.file != null) break @intCast(id); + } else return error.NoTwin; + const fd = init(true); + if (fd < 0) return error.NoWatcher; + defer _ = libc.close(fd); + var watches: Table = @splat(null); + defer watchPane(fd, &watches, twin, null, core.panes[twin].?.serial, .{ .text = 0 }); + watchPane(fd, &watches, twin, path, core.panes[twin].?.serial, .{ .text = std.hash.Wyhash.hash(0, "before\n") }); + // Edited in the first and saved: the file is the twins' text now. + pardes.panes.File.setContent(core, &pane.file.?, try core.gpa.dupe(u8, "after\n")); + try tmp.dir.writeFile(io, .{ .sub_path = "twinned", .data = "after\n" }); + _ = reloadPane(core, io, &watches, twin, true); + const other = core.panes[twin].?; + try std.testing.expect(std.mem.indexOf(u8, other.msg[0..other.msg_len], "reloaded") == null); + try std.testing.expectEqualStrings("after\n", other.file.?.content); +} + test "ordinary file watches still reconcile changes between open and watch" { if (comptime !supported or !filesystem.platform_has_fs) return; const io = std.testing.io; -- cgit v1.3