From 081e1b9ab9d3bb8afaae8ea040c7767cb6e4f863 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 20:50:11 -0300 Subject: Undo after Get file or Edit's e puts the old name back with the old text and its clean state, so a Save or Putall after it writes nothing over the loaded file; a Get file that fails, or is asked about unsaved edits, renames nothing; Get file in a directory pane is refused with how to open it ctl.getFile is the one load-under-a-name path: it reads first, then makes one undo step (whose snapshot now keeps the name and its saved state), renames and loads, and leaves the pane clean. Co-Authored-By: Claude Opus 5.5 --- src/File.zig | 86 ++++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 63 insertions(+), 23 deletions(-) (limited to 'src/File.zig') diff --git a/src/File.zig b/src/File.zig index 522d4bd6..0f1579b2 100644 --- a/src/File.zig +++ b/src/File.zig @@ -32,8 +32,58 @@ pub const Snapshot = struct { cur_row: i32, cur_col: i32, vsel: Text.CharSel, + /// The name the text had then (owned), and whether and as what it was + /// saved under it: an undo across a load into the pane under another + /// name (Get file, Edit's e) puts the name back with the text, and the + /// dirty state that went with them, as acme's undo does. + path: ?[]u8 = null, + clean: bool = false, + saved_hash: ?u64 = null, + saved_len: ?usize = null, + + fn deinit(s: Snapshot, gpa: std.mem.Allocator) void { + gpa.free(s.content); + if (s.path) |path| gpa.free(path); + } }; +/// The pane's text, place and name now, for an undo or redo step. +fn snapshotOf(gpa: std.mem.Allocator, pane: *const Pane, f: *const State) ?Snapshot { + const content = gpa.dupe(u8, f.content) catch return null; + const path = gpa.dupe(u8, f.path) catch { + gpa.free(content); + return null; + }; + return .{ + .content = content, + .cur_row = pane.body.cur_row, + .cur_col = pane.body.cur_col, + .vsel = pane.body.vsel, + .path = path, + .clean = f.revision == f.saved_revision, + .saved_hash = f.saved_hash, + .saved_len = f.saved_len, + }; +} + +/// After a step's text went in: if it was under another name, that name +/// back, and the step's saved state with it (Get file, Edit's e). +fn restoreName(p: *Pardes, pane: *Pane, f: *State, snap: Snapshot) void { + defer if (snap.path) |path| p.gpa.free(path); + const path = snap.path orelse return; + if (std.mem.eql(u8, path, f.path)) return; + const id = for (p.panes, 0..) |slot, i| { + if (slot == pane) break i; + } else return; + pardes.ctlfs.pane.nameBuffer(p, id, path, false) catch return; + f.saved_hash = snap.saved_hash; + f.saved_len = snap.saved_len; + f.saved_revision = if (snap.clean) f.revision else f.revision -% 1; + f.disk_newer = null; + f.disk_newer_warned = false; + if (filesystem.localPath(path) != null) p.emit(.{ .watch = .{ .pane = @intCast(id), .on = true } }); +} + pub const History = struct { undo: [limits.undo_max]Snapshot = undefined, undo_len: usize = 0, @@ -1016,8 +1066,8 @@ pub fn deinit(p: *Pardes, pane: *Pane, file: *State) void { for (file.node_history.items) |step| p.gpa.free(step.ranges); file.node_history.deinit(p.gpa); if (ntwins > 0) return; - for (file.history.undo[0..file.history.undo_len]) |snap| p.gpa.free(snap.content); - for (file.history.redo[0..file.history.redo_len]) |snap| p.gpa.free(snap.content); + for (file.history.undo[0..file.history.undo_len]) |snap| snap.deinit(p.gpa); + for (file.history.redo[0..file.history.redo_len]) |snap| snap.deinit(p.gpa); p.gpa.destroy(file.history); } @@ -1340,7 +1390,7 @@ pub fn restoreSnap(pane: *Pane, f: *State, snap: Snapshot) void { fn pushHistory(gpa: std.mem.Allocator, slots: []Snapshot, len: *usize, snap: Snapshot) void { if (len.* == slots.len) { - gpa.free(slots[0].content); + slots[0].deinit(gpa); std.mem.copyForwards(Snapshot, slots[0 .. slots.len - 1], slots[1..]); len.* -= 1; } @@ -1351,15 +1401,13 @@ fn pushHistory(gpa: std.mem.Allocator, slots: []Snapshot, len: *usize, snap: Sna pub fn pushUndo(p: *Pardes, pane: *Pane) void { const f = if (pane.file) |*file| file else return; const history = f.history; - if (history.undo_len > 0 and std.mem.eql(u8, history.undo[history.undo_len - 1].content, f.content)) return; - const snap: Snapshot = .{ - .content = p.gpa.dupe(u8, f.content) catch return, - .cur_row = pane.body.cur_row, - .cur_col = pane.body.cur_col, - .vsel = pane.body.vsel, - }; + if (history.undo_len > 0) { + const last = history.undo[history.undo_len - 1]; + if (std.mem.eql(u8, last.content, f.content) and std.mem.eql(u8, last.path orelse f.path, f.path)) return; + } + const snap = snapshotOf(p.gpa, pane, f) orelse return; pushHistory(p.gpa, &history.undo, &history.undo_len, snap); - for (history.redo[0..history.redo_len]) |item| p.gpa.free(item.content); + for (history.redo[0..history.redo_len]) |item| item.deinit(p.gpa); history.redo_len = 0; } @@ -1367,16 +1415,12 @@ pub fn undo(p: *Pardes, pane: *Pane) void { const f = if (pane.file) |*file| file else return; const history = f.history; if (history.undo_len == 0) return; - const current: Snapshot = .{ - .content = p.gpa.dupe(u8, f.content) catch return, - .cur_row = pane.body.cur_row, - .cur_col = pane.body.cur_col, - .vsel = pane.body.vsel, - }; + const current = snapshotOf(p.gpa, pane, f) orelse return; pushHistory(p.gpa, &history.redo, &history.redo_len, current); history.undo_len -= 1; const previous = history.undo[history.undo_len]; setContent(p, f, previous.content); + restoreName(p, pane, f, previous); restoreSnap(pane, f, previous); } @@ -1384,16 +1428,12 @@ pub fn redo(p: *Pardes, pane: *Pane) void { const f = if (pane.file) |*file| file else return; const history = f.history; if (history.redo_len == 0) return; - const current: Snapshot = .{ - .content = p.gpa.dupe(u8, f.content) catch return, - .cur_row = pane.body.cur_row, - .cur_col = pane.body.cur_col, - .vsel = pane.body.vsel, - }; + const current = snapshotOf(p.gpa, pane, f) orelse return; pushHistory(p.gpa, &history.undo, &history.undo_len, current); history.redo_len -= 1; const next = history.redo[history.redo_len]; setContent(p, f, next.content); + restoreName(p, pane, f, next); restoreSnap(pane, f, next); } -- cgit v1.3