summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig59
1 files changed, 59 insertions, 0 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index 93ea1016..c1f8df44 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -555,6 +555,11 @@ pub const Event = union(enum) {
/// stale answer (the pane was closed, or a newer query superseded it) and
/// is dropped.
lsp_resp: struct { id: u32, rows: []const u8 },
+ /// a file the shell was asked to watch changed on disk; `bytes` are its
+ /// new contents, borrowed for this call exactly like `output`. A shell
+ /// with no filesystem (the browser) or no watcher simply never sends one —
+ /// nothing in the core waits for it.
+ file_changed: struct { pane: u8, bytes: []const u8 },
paste: []const u8,
pinch: f32,
touch_scroll: f32,
@@ -582,6 +587,12 @@ pub const Effect = union(enum) {
/// core (like save_file) and must SNAPSHOT them before the worker starts —
/// the core keeps editing while this is in flight.
lsp: struct { id: u32, kind: lsp.Kind, pane: u8, offset: u32, arg: Buf(128) },
+ /// start (`on`) or stop watching this pane's file on disk. Starting, the
+ /// shell reads the path off the core exactly like save_file does; stopping
+ /// carries nothing, because by the time an `off` is drained the pane is
+ /// already freed — the shell remembers what it watches per pane id.
+ /// Only real files ask for this: an output buffer has no file behind it.
+ watch: struct { pane: u8, on: bool },
quit,
fn Buf(comptime n: usize) type {
@@ -1151,6 +1162,14 @@ pub const Pardes = struct {
if (iv.grid.len > 0) p.gpa.free(iv.grid);
}
if (pane.file) |*f| {
+ // a watched file is going away: tell the shell to drop it. The id
+ // comes from the slot, which every caller still has pointing at
+ // `pane` (they null it right after) — deinitPane has never needed
+ // one before and threading it through seven call sites for this is
+ // worse than the scan.
+ if (f.output == null) for (p.panes, 0..) |slot, i| {
+ if (slot == pane) p.emit(.{ .watch = .{ .pane = @intCast(i), .on = false } });
+ };
p.gpa.free(f.path);
p.gpa.free(f.content);
if (f.highlights.len > 0) p.gpa.free(f.highlights);
@@ -1283,6 +1302,7 @@ pub const Pardes = struct {
},
.eof => |e| p.removePane(e.pane),
.lsp_resp => |r| p.lspResponse(r.id, r.rows),
+ .file_changed => |fc| p.fileChanged(fc.pane, fc.bytes),
.key => |key| p.handleKey(key),
.mouse => |m| p.handleMouse(m),
.paste => |bytes| {
@@ -4271,6 +4291,38 @@ pub const Pardes = struct {
term_pane.restoreSnap(p, pane, pane.ed_redo.pop() orelse return);
}
+ /// Something outside pardes rewrote a file we have open. Commit the buffer
+ /// we are holding — unsaved edits and all — onto the undo stack, then swap
+ /// in the bytes from disk.
+ ///
+ /// THAT IS THE WHOLE DESIGN, not an implementation detail. An external
+ /// update is pushed exactly like an edit the user typed, so `u` walks back
+ /// to the buffer as it was a keystroke ago and unsaved work is never lost.
+ /// It is also why the feature is nearly free: pardes never has to merge its
+ /// buffer against the new file, never has to ask "reload? keep? diff?", and
+ /// there is no third state between "what is on disk" and "what I typed" —
+ /// the undo stack, which already exists, IS the history of both.
+ fn fileChanged(p: *Pardes, id: u8, bytes: []const u8) void {
+ const pane = p.panes[id] orelse return;
+ if (pane.file == null) return;
+ const f = &pane.file.?;
+ // the disk arrived at what we already hold: an undo step that changes
+ // nothing is worse than no undo step
+ if (std.mem.eql(u8, f.content, bytes)) return;
+ const new = p.gpa.dupe(u8, bytes) catch return;
+ p.pushUndo(pane);
+ file_pane.setContent(p, f, new);
+ // keep the cursor where it was, clamped onto the new text: restoreSnap
+ // reads only the cursor and selection out of the snapshot it is handed,
+ // so handing it the pane's own is exactly "clamp what you have".
+ file_pane.restoreSnap(pane, f, .{
+ .content = undefined,
+ .cur_row = pane.cur_row,
+ .cur_col = pane.cur_col,
+ .vsel = pane.vsel,
+ });
+ }
+
fn handleMouse(p: *Pardes, m: Mouse) void {
const mcol = @min(m.col, p.screen_w -| 1);
const mrow = @min(m.row, p.screen_h -| 1);
@@ -5506,6 +5558,13 @@ pub const Pardes = struct {
pane.cur_row = @intCast(src.scroll);
pane.cols = @max(1, src.cols);
pane.rows = @max(1, src.rows);
+ // a restored file pane is watched like an opened one. Note
+ // its content is the DUMP's, which may already differ from
+ // disk; the shell stamps the watch from what it has, so the
+ // first external write reconciles the pane to disk and the
+ // dump's version lands one `u` away — which is the same
+ // bargain as any other external update.
+ if (out == null) p.emit(.{ .watch = .{ .pane = @intCast(i), .on = true } });
p.restoreTail(pane, src.tag);
},
.image => {