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.zig61
1 files changed, 56 insertions, 5 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 484b08c9..5cc238a7 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -10,6 +10,7 @@ const look = @import("../look.zig");
const panes = @import("../panes.zig");
const config = @import("../config.zig");
const fs = @import("../fs.zig");
+const tagline = @import("../tagline.zig");
const tree = @import("tree.zig");
const pane_files = @import("pane.zig");
@@ -154,10 +155,13 @@ fn writeQuoted(w: *std.Io.Writer, s: []const u8) !void {
try w.writeByte('\'');
}
-pub const pane_text_cap = 7 * 12 + 2 * 256 + 4;
+pub const pane_text_cap = 7 * 12 + 2 * 256 + 4 + "notcurrent ".len;
/// 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.
+/// dirty flag, then the width in cells, the font and the tab width. Then
+/// rio's word for whether the window has the keyboard, `current` or
+/// `notcurrent` (rio(4), wctl), which acme's line has no room for because
+/// acme has one window for input at a time only by the mouse.
pub fn paneText(p: *Pardes, pane: *Pane, buf: []u8) []const u8 {
var w = std.Io.Writer.fixed(buf);
w.print("{d:>11} {d:>11} {d:>11} {d:>11} {d:>11} {d:>11} ", .{
@@ -169,10 +173,32 @@ pub fn paneText(p: *Pardes, pane: *Pane, buf: []u8) []const u8 {
pane.cols,
}) catch return w.buffered();
writeQuoted(&w, fontName(p)) catch return w.buffered();
- w.print(" {d:>11} ", .{config.tab_width}) catch {};
+ w.print(" {d:>11} {s} ", .{ config.tab_width, if (p.panes[p.active] == pane) "current" else "notcurrent" }) catch {};
return w.buffered();
}
+// ---- /focus ----
+
+/// The pane with the keyboard, which a write of a serial moves, the way
+/// writing `current` to rio's wctl does (rio(4)).
+pub fn readFocus(p: *Pardes, req: Req) Reply {
+ const pane = p.panes[p.active] orelse return tree.stagedReply(p, req);
+ p.fs.stage(p.gpa).print(p.gpa, "{d}\n", .{pane.serial}) catch return Reply.fail(req.tag, E.NOMEM);
+ return tree.stagedReply(p, req);
+}
+
+pub fn writeFocus(p: *Pardes, req: Req) Reply {
+ const text = std.mem.trim(u8, req.data, " \t\r\n");
+ const serial = std.fmt.parseInt(u32, text, 10) catch return tree.failText(req.tag, E.INVAL, tree.e_bad_ctl);
+ const id = p.paneBySerial(serial) orelse return tree.failText(req.tag, E.NOENT, e_no_pane);
+ // The keyboard goes to the pane itself, off any header that had it.
+ tagline.exitHeader(p);
+ p.active = id;
+ return .{ .tag = req.tag, .written = @intCast(req.data.len) };
+}
+
+const e_no_pane = "no such window";
+
pub fn readPane(p: *Pardes, req: Req, pane: *Pane) Reply {
var buf: [pane_text_cap]u8 = undefined;
const out = p.fs.stage(p.gpa);
@@ -266,7 +292,7 @@ const root_status = @intFromEnum(tree.TopFile.status);
const root_look = @intFromEnum(tree.TopFile.look);
const root_exec = @intFromEnum(tree.TopFile.exec);
-test "pane ctl read is index's five fields plus width in cells, font and tab width" {
+test "pane ctl read is index's five fields plus width in cells, font, tab width and whether it is current" {
const gpa = testing.allocator;
const p = try withFile(gpa, "x\n");
defer p.deinit();
@@ -276,7 +302,7 @@ test "pane ctl read is index's five fields plus width in cells, font and tab wid
try testing.expectEqual(Status.ok, a.reply.status);
var want: std.ArrayList(u8) = .empty;
defer want.deinit(gpa);
- try want.print(gpa, "{d:>11} {d:>11} {d:>11} {d:>11} {d:>11} {d:>11} {s} {d:>11} ", .{
+ try want.print(gpa, "{d:>11} {d:>11} {d:>11} {d:>11} {d:>11} {d:>11} {s} {d:>11} current ", .{
pane.serial, pane_files.tagOf(p, pane).len, @as(usize, 2), 0, 0, pane.cols, "default", config.tab_width,
});
try testing.expectEqualStrings(want.items, a.bytes);
@@ -291,6 +317,31 @@ test "pane ctl read is index's five fields plus width in cells, font and tab wid
try testing.expectEqualStrings("'it''s'", w.buffered());
}
+test "focus reads the pane with the keyboard, and a serial written to it moves the keyboard there" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ const first = serialOf(p);
+ const second = try th.newPane(p);
+ const focus = @intFromEnum(tree.TopFile.focus);
+ var line: [16]u8 = undefined;
+ try testing.expectEqualStrings(try std.fmt.bufPrint(&line, "{d}\n", .{second}), rd(p, focus, 0, 64).bytes);
+ try testing.expectEqual(@as(u64, rd(p, focus, 0, 64).bytes.len), call(p, .{ .tag = 1, .op = .getattr, .node = focus }).reply.attr.size);
+ try testing.expect(std.mem.endsWith(u8, rd(p, Node.of(first, .ctl), 0, 4096).bytes, " notcurrent "));
+
+ // A header had the keyboard: the write takes it back to the pane.
+ tagline.enterHeader(p, null);
+ try testing.expectEqual(Status.ok, wr(p, focus, try std.fmt.bufPrint(&line, "{d}\n", .{first})).reply.status);
+ try testing.expect(!p.header_focus);
+ try testing.expectEqual(p.paneBySerial(first).?, p.active);
+ try testing.expect(std.mem.endsWith(u8, rd(p, Node.of(first, .ctl), 0, 4096).bytes, " current "));
+ try testing.expect(std.mem.endsWith(u8, rd(p, Node.of(second, .ctl), 0, 4096).bytes, " notcurrent "));
+
+ try testing.expectEqualStrings(e_no_pane, wr(p, focus, "99999\n").reply.ename);
+ try testing.expectEqual(E.NOENT, wr(p, focus, "99999").errno());
+ for ([_][]const u8{ "", "two", "-1", "1 2" }) |bad| try testing.expectEqual(E.INVAL, wr(p, focus, bad).errno());
+ try testing.expectEqual(p.paneBySerial(first).?, p.active);
+}
+
test "the pane ctl takes get, lock and unlock, and nothing that a file of its own now answers" {
const gpa = testing.allocator;
const p = try withFile(gpa, "one\ntwo\n");