From 0fe78621cc0db8b835ebcb7c51bbd2d20d6dbcc2 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Tue, 29 Sep 2026 00:41:23 -0300 Subject: A focus write makes its pane's column the active one, and layout's active is it Writing a serial to /focus gave that pane the keyboard but left the active column where it was, so pane/new under Placement acme went elsewhere, and /layout's active named the keyboard's column rather than where a new pane would go. A focus write now sets the active column as the user's click there would, under either Placement, and /layout's active names the active column, which pane/new and a look place into. Co-Authored-By: Claude Opus 5.5 --- docs/fs.md | 6 ++++-- src/ninep/cols.zig | 25 +++++++++++++++++++++++-- src/ninep/ctl.zig | 5 ++++- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/docs/fs.md b/docs/fs.md index 55660bc0..a5d3c4ca 100644 --- a/docs/fs.md +++ b/docs/fs.md @@ -102,11 +102,13 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC. write follow to that open to wait for more /screen rendered screen JSON; frozen per open handle /listeners the session's dial addresses -/focus the serial of the pane with the keyboard; write a serial to give it the keyboard +/focus the serial of the pane with the keyboard; write a serial to give it the keyboard, + which makes its column the active one as a click there would /ctl the settings, one a line as a write takes them; write a setting or a session builtin /commands every builtin: word, `arg` if it takes one, `root` or `pane` (the ctl that takes it), a setting's values /layout one line per column, left to right: serial index x width current|notcurrent - empty|full pane-serials...; then active (- when no column has the keyboard) + empty|full pane-serials...; then active : the active column, where + pane/new and a look place a pane next (- when there is none) /tag the workspace tag; > replaces it, >> appends, one line /col//tag the tag of the column with serial n, the same way /pane/new open it to make a pane; the read answers that pane's serial. A session holds diff --git a/src/ninep/cols.zig b/src/ninep/cols.zig index 682c2a02..79943b1b 100644 --- a/src/ninep/cols.zig +++ b/src/ninep/cols.zig @@ -27,7 +27,8 @@ pub fn activeColumn(p: *const Pardes) ?usize { /// it, which stays while it lives), its index left to right, x and width in /// cells, `current` or `notcurrent` (rio's words, for the column with the /// keyboard), `empty` or `full`, and its panes' serials, top to bottom; -/// then `active `. +/// then `active `: the active column, where pane/new and a look +/// place a pane next (acme's activecol), not always the keyboard's. pub fn writeLayout(p: *Pardes, w: *std.Io.Writer) !void { const active = activeColumn(p); for (0..p.ncol) |c| { @@ -38,7 +39,7 @@ pub fn writeLayout(p: *Pardes, w: *std.Io.Writer) !void { for (p.col_panes[c][0..p.col_n[c]]) |id| if (p.panes[id]) |pane| try w.print(" {d}", .{pane.serial}); try w.writeByte('\n'); } - if (active) |c| try w.print("active {d}\n", .{layout.columnSerial(p, c)}) else try w.writeAll("active -\n"); + if (p.activeColumn() orelse active) |c| try w.print("active {d}\n", .{layout.columnSerial(p, c)}) else try w.writeAll("active -\n"); } pub fn layoutLen(p: *Pardes) u64 { @@ -179,3 +180,23 @@ test "a column's tag and the workspace's read, replace and append as a pane tag _ = th.wr(p, top, "Newcol Exit\n"); try testing.expectEqualStrings("Newcol Exit", tagline.topbar(p)); } + +test "a focus write makes its pane's column the active one: layout says so and pane/new goes there" { + for ([_][]const u8{ "Placement acme", "Placement pardes" }) |placement| { + const p = try th.withFile(testing.allocator, "x\n"); + defer p.deinit(); + try testing.expect(p.executeBuiltinLine(p.active, placement)); + const left = p.panes[0].?.serial; + p.newColumn(p.active); + p.newScratchFor(p.active, .fs); // fills the new, right column + p.sync(); + try testing.expectEqual(@as(usize, 1), layout.findPane(p, p.active).?.col); + var digits: [16]u8 = undefined; + _ = th.wr(p, @intFromEnum(tree.TopFile.focus), try std.fmt.bufPrint(&digits, "{d}\n", .{left})); + var want: [32]u8 = undefined; + const text = th.rd(p, @intFromEnum(tree.TopFile.layout), 0, 4096).bytes; + try testing.expect(std.mem.endsWith(u8, text, try std.fmt.bufPrint(&want, "active {d}\n", .{layout.columnSerial(p, 0)}))); + const made = th.call(p, .{ .tag = 1, .op = .open, .node = @intFromEnum(tree.TopFile.new) }).reply.handle; + try testing.expectEqual(@as(usize, 0), layout.findPane(p, p.paneBySerial(made).?).?.col); + } +} diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 325fc8c8..98120555 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -486,9 +486,12 @@ 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. + // The keyboard goes to the pane itself, off any header that had it, and + // its column becomes the active one, as the user's click there would + // make it: pane/new and a look place there next, whatever Placement. tagline.exitHeader(p); p.active = id; + if (pardes.layout.findPane(p, id)) |at| p.setActiveColumn(at.col); return .{ .tag = req.tag, .written = @intCast(req.data.len) }; } -- cgit v1.3