summaryrefslogtreecommitdiff
path: root/src/ninep/ctl.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/ninep/ctl.zig')
-rw-r--r--src/ninep/ctl.zig38
1 files changed, 33 insertions, 5 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index deb64565..a64521e7 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -163,7 +163,7 @@ fn writeQuoted(w: *std.Io.Writer, s: []const u8) !void {
try w.writeByte('\'');
}
-pub const pane_text_cap = 7 * 12 + 2 * 256 + 4 + "notcurrent ".len;
+pub const pane_text_cap = 7 * 12 + 2 * 256 + 4 + "notcurrent ".len + 48;
/// acme's winctlprint: serial, tag length, body length, a reserved zero, the
/// dirty flag, then the width in cells, the font and the tab width. Then
@@ -186,6 +186,10 @@ pub fn paneText(p: *Pardes, pane: *Pane, buf: []u8) []const u8 {
// While a column's or the workspace's tag has the keyboard, no pane does.
const current = p.panes[p.active] == pane and !p.header_focus;
w.print(" {d:>11} {s} ", .{ config.tab_width, if (current) "current" else "notcurrent" }) catch {};
+ // A terminal bound as a REPL ends with its id, `python-a`.
+ var idbuf: [48]u8 = undefined;
+ const id = exec_line.replId(&idbuf, pane);
+ if (id.len > 0) w.print("{s} ", .{id}) catch {};
return w.buffered();
}
@@ -196,7 +200,7 @@ const Builtin = builtins.registry.Builtin();
/// Refuses a control message, quoting it the way Plan 9's cmderror does
/// (kernel/misc/parse.c:82): `unknown control message "Bogus 3"`.
fn refuse(p: *Pardes, req: Req, why: []const u8, line: []const u8) Reply {
- const room = p.fs.ename.len - why.len - 3;
+ const room = p.fs.ename.len -| (why.len + 3);
const text = std.fmt.bufPrint(&p.fs.ename, "{s} \"{s}\"", .{ why, line[0..@min(line.len, room)] }) catch why;
return tree.failText(req.tag, E.INVAL, text);
}
@@ -298,7 +302,9 @@ fn writeSettings(p: *Pardes, w: *std.Io.Writer) !void {
// Bare, it names the default directory.
.dump_dir => if (s.dump_dir.len > 0) try w.print("{s} {s}\n", .{ word, s.dump_dir.get() }) else try w.print("{s}\n", .{word}),
.transition => |effect| try w.print("{s} {s}\n", .{ word, if (s.panel_transition == effect) "on" else "off" }),
- .scene => |effect| try w.print("{s} {s}\n", .{ word, if (@field(s.scene_effects, @tagName(effect))) "on" else "off" }),
+ .scene => |effect| if (s.post.level(effect) == 0) try w.print("{s} off\n", .{word}) else try w.print("{s} {d}\n", .{ word, s.post.level(effect) }),
+ .shader => for (s.post.list()) |entry| if (entry.scene == null) try w.print("{s} {s}\n", .{ word, entry.path.get() }),
+ .shader_animation => try w.print("{s} {s}\n", .{ word, @tagName(s.shader_animation) }),
}
}
try w.writeAll("LocationsConfig ");
@@ -395,7 +401,7 @@ pub fn writeFocus(p: *Pardes, req: Req) Reply {
return .{ .tag = req.tag, .written = @intCast(req.data.len) };
}
-const e_no_pane = "no such window";
+const e_no_pane = "no such pane";
pub fn readPane(p: *Pardes, req: Req, pane: *Pane) Reply {
var buf: [pane_text_cap]u8 = undefined;
@@ -662,7 +668,7 @@ test "the root ctl reads the settings as a write takes them, and takes the sessi
// its line; the lines before it have taken effect, as in acme.
const failed = wr(p, root_ctl, "Verbose on\nMount x\nVerbose off");
try testing.expectEqual(E.IO, failed.errno());
- try testing.expectEqualStrings("Mount name dial: MissingArgument \"Mount x\"", failed.reply.ename);
+ try testing.expectEqualStrings("Mount name dial: missing argument \"Mount x\"", failed.reply.ename);
try testing.expect(p.settings.verbose);
try testing.expect(!p.fs.no_prompt);
try testing.expectEqual(Status.ok, wr(p, root_ctl, "Verbose off").reply.status);
@@ -988,3 +994,25 @@ test "Joincol with no column to the right and Theme with no such theme say so" {
const pane = p.panes[p.active].?;
try testing.expect(std.mem.indexOf(u8, pane.msg[0..pane.msg_len], "no theme \"drak\"") != null);
}
+
+test "a look that fills a column's stillborn scratch is answered with the scratch already gone" {
+ const gpa = testing.allocator;
+ const p = try withFile(gpa, "x\n");
+ defer p.deinit();
+ var tmp = testing.tmpDir(.{});
+ defer tmp.cleanup();
+ try tmp.dir.writeFile(testing.io, .{ .sub_path = "f.txt", .data = "file\n" });
+ var dir: [4096]u8 = undefined;
+ const at = dir[0..try tmp.dir.realPath(testing.io, &dir)];
+ var line: [4200]u8 = undefined;
+ // Newcol makes a column of one empty +New; a file looked from it
+ // joins that column and takes the scratch's place.
+ try testing.expectEqual(tree.Status.ok, wr(p, @intFromEnum(tree.TopFile.ctl), "Newcol\n").reply.status);
+ const scratch = p.panes[p.active].?.serial;
+ try testing.expectEqual(tree.Status.ok, wr(p, Node.of(scratch, .look), try std.fmt.bufPrint(&line, "{s}/f.txt\n", .{at})).reply.status);
+ try testing.expect(p.paneBySerial(scratch) == null);
+ var want: [32]u8 = undefined;
+ const gone = try std.fmt.bufPrint(&want, "{d} ", .{scratch});
+ var rows = std.mem.splitScalar(u8, rd(p, @intFromEnum(tree.TopFile.index), 0, 4096).bytes, '\n');
+ while (rows.next()) |row| try testing.expect(!std.mem.startsWith(u8, row, gone));
+}