summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Output.zig35
-rw-r--r--src/ninep/ctl.zig25
-rw-r--r--src/pardes.zig1
3 files changed, 61 insertions, 0 deletions
diff --git a/src/Output.zig b/src/Output.zig
index f2ab1bda..b8dd5e34 100644
--- a/src/Output.zig
+++ b/src/Output.zig
@@ -609,6 +609,41 @@ pub fn openUnsaved(p: *Pardes, id: usize, content: []u8) void {
p.active = id;
}
+/// Pane `gone` closed: its row goes from +Unsaved, which lists only panes
+/// still there to lose.
+pub fn dropUnsaved(p: *Pardes, gone: *const Pane) void {
+ const gf = gone.file orelse return;
+ if (gf.output != null and !(gf.output.?.from == .cmd and gf.output.?.from.cmd == .New)) return;
+ for (p.panes) |slot| {
+ const rp = slot orelse continue;
+ if (rp == gone) continue;
+ const rf = if (rp.file) |*f| f else continue;
+ const o = rf.output orelse continue;
+ if (o.from != .unsaved) continue;
+ var scratch_name: [limits.host_path_cap + 24]u8 = undefined;
+ const as_scratch = std.fmt.bufPrint(&scratch_name, "{s} (pane {d}): Modified", .{ gf.path, gone.serial }) catch "";
+ var out: std.ArrayList(u8) = .empty;
+ var lines = std.mem.splitScalar(u8, rf.content, '\n');
+ var dropped = false;
+ var first = true;
+ while (lines.next()) |line| {
+ const is_it = std.mem.startsWith(u8, line, gf.path) and std.mem.eql(u8, line[gf.path.len..], ": Modified");
+ if (is_it or (as_scratch.len > 0 and std.mem.eql(u8, line, as_scratch))) {
+ dropped = true;
+ continue;
+ }
+ if (!first) out.append(p.gpa, '\n') catch return out.deinit(p.gpa);
+ out.appendSlice(p.gpa, line) catch return out.deinit(p.gpa);
+ first = false;
+ }
+ if (!dropped) return out.deinit(p.gpa);
+ const content = out.toOwnedSlice(p.gpa) catch return out.deinit(p.gpa);
+ File.setContent(p, rf, content);
+ resetBody(p, rp);
+ return;
+ }
+}
+
/// Recent: the one +Recent, filled again when it is there (as Find and
/// Grep's +Search is), else opened below the asking pane.
pub fn openRecent(p: *Pardes, id: usize) !void {
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 0d3f084d..3d7ee91a 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -2176,6 +2176,31 @@ test "Grep, Symbols and Diagnostics share their directory's one +Search" {
try testing.expectEqual(@as(usize, 1), searches);
}
+test "+Unsaved drops a pane once it has closed" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const a = try th.newPane(p);
+ _ = wr(p, Node.of(a, .name), "/tmp/pardes-unsaved-a.txt\n");
+ _ = wr(p, Node.of(a, .body), "edited\n");
+ const b = try th.newPane(p);
+ _ = wr(p, Node.of(b, .name), "/tmp/pardes-unsaved-b.txt\n");
+ _ = wr(p, Node.of(b, .body), "edited\n");
+ const root_ctl = @intFromEnum(tree.TopFile.ctl);
+ try testing.expectEqual(Status.err, wr(p, root_ctl, "Exit\n").reply.status);
+ var listed: ?*pardes.Pane = null;
+ for (p.panes) |slot| if (slot) |q| if (q.file) |f| if (f.output) |o| if (o.from == .unsaved) {
+ listed = q;
+ };
+ const list = listed.?;
+ try testing.expect(std.mem.indexOf(u8, list.file.?.content, "/tmp/pardes-unsaved-a.txt: Modified") != null);
+ // a closes (discarded): its row goes; b's stays.
+ _ = wr(p, Node.of(a, .ctl), "Del\n");
+ _ = wr(p, Node.of(a, .ctl), "Del\n");
+ p.sync();
+ try testing.expect(p.paneBySerial(a) == null);
+ try testing.expect(std.mem.indexOf(u8, list.file.?.content, "/tmp/pardes-unsaved-a.txt: Modified") == null);
+}
+
test "every EINVAL a write gets says why, in its err record too; DEL is a control character in a line" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();
diff --git a/src/pardes.zig b/src/pardes.zig
index c405c3d9..2649014a 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -4898,6 +4898,7 @@ pub const Pardes = struct {
// Closed, a file is remembered with its dot: Recent and a jump back
// to it reopen it there.
p.noteClosed(pane);
+ panes.Output.dropUnsaved(p, pane);
const watched = (if (pane.file) |f| f.output == null else false) or pane.hasPdf();
for (p.panes, 0..) |slot, id| if (slot == pane) {
if (watched) p.emit(.{ .watch = .{ .pane = @intCast(id), .on = false } });