diff options
Diffstat (limited to 'src/pardes.zig')
| -rw-r--r-- | src/pardes.zig | 59 |
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 => { |
