summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ninep/events.zig38
-rw-r--r--src/ninep/pane.zig28
-rw-r--r--src/ninep/tree.zig2
-rw-r--r--src/pardes.zig5
4 files changed, 55 insertions, 18 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig
index 906e7a9a..c6505f52 100644
--- a/src/ninep/events.zig
+++ b/src/ninep/events.zig
@@ -153,10 +153,17 @@ fn bootFirst(p: *Pardes) void {
/// Records `<kind> <serial> <name>`.
pub fn noteLog(p: *Pardes, kind: LogKind, pane: *Pane) void {
+ // Nothing about a pane comes before its `new`: one not announced yet
+ // is announced first, and a rename of it is no record, as its `new`
+ // carries the name it has by then.
+ if (pane.fs.unannounced and kind != .new) {
+ if (kind == .rename) return;
+ announce(p);
+ }
var buf: [4 * 4096 + 64]u8 = undefined;
var name_buf: [4 * 4096]u8 = undefined;
// As /index shows it: a newline in the name is `\n`.
- const name = shown(pane_files.nameOf(pane), &name_buf);
+ const name = shown(pane_files.nameOf(p, pane), &name_buf);
pushLog(p, std.fmt.bufPrint(&buf, "{s} {d} {s}\n", .{ @tagName(kind), pane.serial, name }) catch return);
}
@@ -1011,7 +1018,7 @@ test "the log records whether or not anyone reads, and an open that follows wait
// Nobody holds /log open, and the pane is recorded anyway.
const early = try th.newPane(p);
- const early_line = try std.fmt.bufPrint(&expected, "new {d} {s}\n", .{ early, pane_files.nameOf(p.panes[p.paneBySerial(early).?].?) });
+ const early_line = try std.fmt.bufPrint(&expected, "new {d} {s}\n", .{ early, pane_files.nameOf(p, p.panes[p.paneBySerial(early).?].?) });
const early_owned = try gpa.dupe(u8, early_line);
defer gpa.free(early_owned);
@@ -1048,7 +1055,7 @@ test "the log records whether or not anyone reads, and an open that follows wait
const serial = try th.newPane(p);
const id = p.paneBySerial(serial).?;
try testing.expectEqualStrings(
- try std.fmt.bufPrint(&expected, "new {d} {s}\n", .{ serial, pane_files.nameOf(p.panes[id].?) }),
+ try std.fmt.bufPrint(&expected, "new {d} {s}\n", .{ serial, pane_files.nameOf(p, p.panes[id].?) }),
rdf.next(p, log, fh, frozen).bytes,
);
try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .name), "/tmp/logged.txt\n").reply.status);
@@ -1190,6 +1197,31 @@ test "a Restore gives columns fresh serials, past every serial the old session h
try testing.expect(pardes.layout.columnSerial(restored, 0) != first);
}
+test "a pane named before it is announced has its new under that name, and no rename before it" {
+ const p = try withFile(testing.allocator, "one\n");
+ defer p.deinit();
+ const pane = p.panes[0].?;
+ // As a pane Config makes and names in one update.
+ pane.fs.unannounced = true;
+ try pane_files.nameBuffer(p, 0, "/tmp/named-first.txt", false);
+ announce(p);
+ const log = try freezeLog(p);
+ defer p.gpa.free(log.bytes);
+ var want: [64]u8 = undefined;
+ try testing.expect(std.mem.endsWith(u8, log.bytes, try std.fmt.bufPrint(&want, "new {d} /tmp/named-first.txt\n", .{pane.serial})));
+ try testing.expect(std.mem.indexOf(u8, log.bytes, try std.fmt.bufPrint(&want, "rename {d} ", .{pane.serial})) == null);
+ // A shell given no directory is named by the session's from the first,
+ // and saying it is there is no rename.
+ const free = p.freeSlot().?;
+ const shell = try p.newShell(free, "");
+ try testing.expectEqualStrings(p.sessionDir(), pane_files.nameOf(p, shell));
+ announce(p);
+ p.setCwd(free, p.sessionDir());
+ const after = try freezeLog(p);
+ defer p.gpa.free(after.bytes);
+ try testing.expect(std.mem.indexOf(u8, after.bytes, try std.fmt.bufPrint(&want, "rename {d} ", .{shell.serial})) == null);
+}
+
test "opens of the log share the open records, and a closed one frees its record" {
const gpa = testing.allocator;
const p = try withFile(gpa, "x\n");
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index eb317655..30530c3b 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -134,12 +134,14 @@ pub fn dirtyOf(pane: *const Pane) bool {
}
/// The pane's name: its file, its document, or a terminal's directory.
-pub fn nameOf(pane: *const Pane) []const u8 {
+pub fn nameOf(p: *Pardes, pane: *const Pane) []const u8 {
if (pane.file) |*f| return f.path;
if (pane.pdfPath()) |path| return path;
if (pane.image) |state| return state.path;
const cwd = pane.cwdSlice();
- return if (cwd.len > 0) cwd else "/";
+ // A shell given no directory was started in the session's, and is
+ // named by it until it says where it is (a `new` then, not a rename).
+ return if (cwd.len > 0) cwd else p.sessionDir();
}
pub fn kindOf(pane: *const Pane) []const u8 {
@@ -333,7 +335,7 @@ pub fn fileSize(p: *Pardes, id: usize, f: PaneFile) u64 {
.body => if (pane.file == null and pane.isTerminal()) panes.terminal.screenTextLen(pane) else if (pdfText(p, pane)) |t| t.len else bodyOf(pane).len,
.data, .xdata => bodyOf(pane).len,
.tag => tagOf(p, pane).len,
- .name => nameOf(pane).len + 1,
+ .name => nameOf(p, pane).len + 1,
.ctl => ctl: {
var buf: [ctl.pane_text_cap]u8 = undefined;
break :ctl ctl.paneText(p, pane, &buf).len;
@@ -380,7 +382,7 @@ pub fn indexLen(p: *Pardes) u64 {
// unreachable: a u32 serial is at most 10 digits
n += (std.fmt.bufPrint(&digits, "{d}", .{serial}) catch unreachable).len;
var name_buf: [4 * 4096]u8 = undefined;
- n += 1 + kindOf(pane).len + 3 + events.shown(nameOf(pane), &name_buf).len + 1;
+ n += 1 + kindOf(pane).len + 3 + events.shown(nameOf(p, pane), &name_buf).len + 1;
// unreachable: a column index is under 16 digits
n += 1 + (std.fmt.bufPrint(&digits, "{d}", .{columnOf(p, p.paneBySerial(serial).?)}) catch unreachable).len;
}
@@ -394,7 +396,7 @@ pub fn read(p: *Pardes, req: Req, id: usize, pane: *Pane, file: PaneFile) Reply
return switch (file) {
.name => name: {
const out = p.fs.stage(p.gpa);
- out.appendSlice(p.gpa, nameOf(pane)) catch {};
+ out.appendSlice(p.gpa, nameOf(p, pane)) catch {};
out.append(p.gpa, '\n') catch {};
break :name tree.stagedReply(p, req);
},
@@ -1016,7 +1018,7 @@ test "index lists serial kind dirty flag and name and stat reports its length" {
try testing.expectEqual(@as(u64, both.bytes.len), indexLen(p));
var expected: [4200]u8 = undefined;
const made = p.panes[p.paneBySerial(serial).?].?;
- try testing.expect(std.mem.endsWith(u8, both.bytes, try std.fmt.bufPrint(&expected, "\n{d} text 0 {s} {d}\n", .{ serial, nameOf(made), columnOf(p, p.paneBySerial(serial).?) })));
+ try testing.expect(std.mem.endsWith(u8, both.bytes, try std.fmt.bufPrint(&expected, "\n{d} text 0 {s} {d}\n", .{ serial, nameOf(p, made), columnOf(p, p.paneBySerial(serial).?) })));
}
test "a terminal is listed as term with its directory as name" {
@@ -1353,13 +1355,13 @@ test "a write of two lines to name is refused EINVAL, on a held open or not" {
const h = call(p, .{ .tag = 1, .op = .open, .node = node, .omode = 1 }).reply.handle;
try testing.expectEqual(E.INVAL, call(p, .{ .tag = 2, .op = .write, .node = node, .handle = h, .data = "/tmp/a\n/tmp/b\n" }).errno());
_ = call(p, .{ .tag = 3, .op = .release, .node = node, .handle = h, .opened = true });
- try testing.expectEqualStrings("/test.txt", nameOf(p.panes[p.paneBySerial(serial).?].?));
+ try testing.expectEqualStrings("/test.txt", nameOf(p, p.panes[p.paneBySerial(serial).?].?));
// Line by line, as bash writes it: the second line is refused.
const h2 = call(p, .{ .tag = 1, .op = .open, .node = node, .omode = 1 }).reply.handle;
try testing.expectEqual(Status.ok, call(p, .{ .tag = 2, .op = .write, .node = node, .handle = h2, .data = "/tmp/pardes-a\n" }).reply.status);
try testing.expectEqual(E.INVAL, call(p, .{ .tag = 2, .op = .write, .node = node, .handle = h2, .data = "/tmp/pardes-b\n" }).errno());
_ = call(p, .{ .tag = 3, .op = .release, .node = node, .handle = h2, .opened = true });
- try testing.expectEqualStrings("/tmp/pardes-a", nameOf(p.panes[p.paneBySerial(serial).?].?));
+ try testing.expectEqualStrings("/tmp/pardes-a", nameOf(p, p.panes[p.paneBySerial(serial).?].?));
}
test "a name cut across writes is one name, applied once at its newline or its close" {
@@ -1373,16 +1375,16 @@ test "a name cut across writes is one name, applied once at its newline or its c
p.fs.write_room = "/tmp/pardes-na".len;
_ = call(p, .{ .tag = 2, .op = .write, .node = node, .handle = h, .data = "/tmp/pardes-na" });
p.fs.write_room = 0;
- try testing.expectEqualStrings("/test.txt", nameOf(p.panes[p.paneBySerial(serial).?].?));
+ try testing.expectEqualStrings("/test.txt", nameOf(p, p.panes[p.paneBySerial(serial).?].?));
_ = call(p, .{ .tag = 2, .op = .write, .node = node, .handle = h, .data = "me.txt\n" });
- try testing.expectEqualStrings("/tmp/pardes-name.txt", nameOf(p.panes[p.paneBySerial(serial).?].?));
+ try testing.expectEqualStrings("/tmp/pardes-name.txt", nameOf(p, p.panes[p.paneBySerial(serial).?].?));
_ = call(p, .{ .tag = 3, .op = .release, .node = node, .handle = h, .opened = true });
// No newline, the whole write: applied at once, on its own open.
const h3 = call(p, .{ .tag = 1, .op = .open, .node = node, .omode = 1 }).reply.handle;
_ = call(p, .{ .tag = 2, .op = .write, .node = node, .handle = h3, .data = "/tmp/pardes-closed.txt" });
- try testing.expectEqualStrings("/tmp/pardes-closed.txt", nameOf(p.panes[p.paneBySerial(serial).?].?));
+ try testing.expectEqualStrings("/tmp/pardes-closed.txt", nameOf(p, p.panes[p.paneBySerial(serial).?].?));
_ = call(p, .{ .tag = 3, .op = .release, .node = node, .handle = h3, .opened = true });
- try testing.expectEqualStrings("/tmp/pardes-closed.txt", nameOf(p.panes[p.paneBySerial(serial).?].?));
+ try testing.expectEqualStrings("/tmp/pardes-closed.txt", nameOf(p, p.panes[p.paneBySerial(serial).?].?));
}
test "a replace through data, truncate then write, is one undo step" {
@@ -1469,7 +1471,7 @@ test "name reads the file name and writing it promotes a scratch without touchin
const name = Node.of(serial, .name);
const body = Node.of(serial, .body);
var scratch_name: [4200]u8 = undefined;
- const scratch_line = try std.fmt.bufPrint(&scratch_name, "{s}\n", .{nameOf(pane)});
+ const scratch_line = try std.fmt.bufPrint(&scratch_name, "{s}\n", .{nameOf(p, pane)});
try testing.expectEqualStrings(scratch_line, rd(p, name, 0, 4096).bytes);
try testing.expectEqual(@as(u64, scratch_line.len), call(p, .{ .tag = 1, .op = .getattr, .node = name }).reply.attr.size);
_ = wr(p, body, "scratch ");
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig
index 61ac7e87..10208666 100644
--- a/src/ninep/tree.zig
+++ b/src/ninep/tree.zig
@@ -1053,7 +1053,7 @@ fn readFile(p: *Pardes, req: Req, target: Target) Reply {
// A name as /log shows it: one line of UTF-8, a
// newline in it `\n` (events.shown).
var name_buf: [4 * 4096]u8 = undefined;
- const name = events.shown(pane.nameOf(pn), &name_buf);
+ const name = events.shown(pane.nameOf(p, pn), &name_buf);
out.print(p.gpa, "{d} {s} {d} {s} {d}\n", .{ serial, pane.kindOf(pn), @intFromBool(pane.dirtyOf(pn)), name, pane.columnOf(p, id) }) catch {};
}
break :index stagedReply(p, req);
diff --git a/src/pardes.zig b/src/pardes.zig
index a63568db..ef713e0c 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -5046,11 +5046,14 @@ pub const Pardes = struct {
// is this process's directory, a rename to undo. That answer, then,
// is not taken.
if (pane.isTerminal() and !pane.shell_spoke and pane.cwd == .owned and std.mem.eql(u8, cwd, p.sessionDir())) return;
+ // One with no directory yet is named by the session's (nameOf): its
+ // shell saying it is there is no new name.
+ const same_name = cur.len == 0 and std.mem.eql(u8, cwd, p.sessionDir());
pane.setOwnedCwd(cwd) catch |err| return p.reportError(id, "directory", err);
if (look.lookHoverPane(p) == id) look.cancelLookHover(p);
// A terminal is named by its directory, so /log hears it renamed; one
// not announced yet is announced under the new name.
- if (pane.isTerminal() and !pane.fs.unannounced) ctlfs.events.noteLog(p, .rename, pane);
+ if (pane.isTerminal() and !same_name) ctlfs.events.noteLog(p, .rename, pane);
}
pub fn shellBin(p: *const Pardes) []const u8 {