summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/File.zig81
-rw-r--r--src/ninep/pane.zig3
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;