diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/File.zig | 81 | ||||
| -rw-r--r-- | src/ninep/pane.zig | 3 |
2 files changed, 58 insertions, 26 deletions
diff --git a/src/File.zig b/src/File.zig index e5805134..0d62e09b 100644 --- a/src/File.zig +++ b/src/File.zig @@ -32,50 +32,66 @@ 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, + /// The pane's name then, as History.gen counts its renames, and whether + /// and as what the text 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. No allocation a step: a name is kept once, at its rename. + gen: u32 = 0, 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, + .content = gpa.dupe(u8, f.content) catch return null, .cur_row = pane.body.cur_row, .cur_col = pane.body.cur_col, .vsel = pane.body.vsel, - .path = path, + .gen = f.history.gen, .clean = f.revision == f.saved_revision, .saved_hash = f.saved_hash, .saved_len = f.saved_len, }; } +/// A rename (ninep/pane.zig nameBuffer): the name it ends, kept for an undo +/// back across it; `old` is the History's from here (owned), freed if it +/// cannot be kept. +pub fn noteRenamed(gpa: std.mem.Allocator, history: *History, old: []u8) void { + history.names.append(gpa, .{ .gen = history.gen, .path = old }) catch gpa.free(old); + history.gen +%= 1; +} + /// 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 history = f.history; + if (snap.gen == history.gen) return; + // The latest name kept for that generation. + var at = history.names.items.len; + const path = while (at > 0) { + at -= 1; + if (history.names.items[at].gen == snap.gen) break history.names.items[at].path; + } else return; + if (std.mem.eql(u8, path, f.path)) { + history.gen = snap.gen; + 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; + // nameBuffer frees nothing of `path`: it keeps the name it ends. + const back = p.gpa.dupe(u8, path) catch return; + defer p.gpa.free(back); + pardes.ctlfs.pane.nameBuffer(p, id, back, false) catch return; + history.gen = snap.gen; f.saved_hash = snap.saved_hash; f.saved_len = snap.saved_len; f.saved_revision = if (snap.clean) f.revision else f.revision -% 1; @@ -90,12 +106,27 @@ pub const History = struct { redo: [limits.undo_max]Snapshot = undefined, redo_len: usize = 0, + /// The names the file had, each with the rename generation it ended + /// (Snapshot.gen), and the generation now. + names: std.ArrayList(struct { gen: u32, path: []u8 }) = .empty, + gen: u32 = 0, + pub fn create(gpa: std.mem.Allocator) !*History { const history = try gpa.create(History); history.undo_len = 0; history.redo_len = 0; + history.names = .empty; + history.gen = 0; return history; } + + pub fn destroy(history: *History, gpa: std.mem.Allocator) void { + for (history.undo[0..history.undo_len]) |snap| snap.deinit(gpa); + for (history.redo[0..history.redo_len]) |snap| snap.deinit(gpa); + for (history.names.items) |name| gpa.free(name.path); + history.names.deinit(gpa); + gpa.destroy(history); + } }; /// A file pane's backing: owned content, its derived caches, and undo history. @@ -1066,9 +1097,7 @@ 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| snap.deinit(p.gpa); - for (file.history.redo[0..file.history.redo_len]) |snap| snap.deinit(p.gpa); - p.gpa.destroy(file.history); + file.history.destroy(p.gpa); } fn reportEdit(p: *Pardes, f: *State, new: []const u8) void { @@ -1292,9 +1321,7 @@ pub fn relink(p: *Pardes, of: usize, id: usize, dot: ?[2]u32) void { } p.gpa.free(f.content); if (f.line_starts.len > 0) p.gpa.free(f.line_starts); - for (f.history.undo[0..f.history.undo_len]) |snap| snap.deinit(p.gpa); - for (f.history.redo[0..f.history.redo_len]) |snap| snap.deinit(p.gpa); - p.gpa.destroy(f.history); + f.history.destroy(p.gpa); f.content = sf.content; f.line_starts = sf.line_starts; f.history = sf.history; @@ -1334,12 +1361,14 @@ pub fn syncTwins(p: *Pardes) void { while (g < p.twin_groups.items.len) { const group = &p.twin_groups.items[g]; var members: [pardes.MAX_PANES]*State = undefined; + var member_panes: [pardes.MAX_PANES]*Pane = undefined; var n: usize = 0; for (p.panes) |slot| { const pane = slot orelse continue; const f = if (pane.file) |*file| file else continue; if (f.twin == group.id) { members[n] = f; + member_panes[n] = pane; n += 1; } } @@ -1363,7 +1392,7 @@ pub fn syncTwins(p: *Pardes) void { group.saved_len = f.saved_len; group.disk_newer = f.disk_newer; group.disk_gone = f.disk_gone; - for (members[0..n]) |other| { + for (members[0..n], member_panes[0..n]) |other, other_pane| { if (other == f) continue; other.saved_revision = f.saved_revision; other.saved_hash = f.saved_hash; @@ -1374,6 +1403,8 @@ pub fn syncTwins(p: *Pardes) void { const path = p.gpa.dupe(u8, f.path) catch continue; p.gpa.free(other.path); other.path = path; + // Renamed, each twin says so, as the one renamed did. + pardes.ctlfs.events.noteLog(p, .rename, other_pane); } } break; @@ -1444,7 +1475,7 @@ pub fn pushUndo(p: *Pardes, pane: *Pane) void { const history = f.history; 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; + if (std.mem.eql(u8, last.content, f.content) and last.gen == history.gen) return; } const snap = snapshotOf(p.gpa, pane, f) orelse return; pushHistory(p.gpa, &history.undo, &history.undo_len, snap); diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index 981c2b0e..3b70d62f 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -982,7 +982,8 @@ pub fn nameBuffer(p: *Pardes, id: usize, full: []const u8, preserve_tag: bool) ! if (std.mem.indexOfAny(u8, full, "\x00\n") != null) return error.InvalidName; if (std.mem.eql(u8, f.path, full)) return; const copy = try p.gpa.dupe(u8, full); - p.gpa.free(f.path); + // The name it ends is kept for an undo back across this rename. + panes.File.noteRenamed(p.gpa, f.history, f.path); f.path = copy; if (panes.Output.fileTraits(f.output).saves) { f.output = null; |
