From 87bb23d39df92db0b383dd300a9ebd1ad9cb2b7a Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 10:13:45 -0300 Subject: Focus gives the keyboard only, reads empty while a header has it, and sits in the root listing's natural place A write to /focus leaves a folded pane folded, as rio keeps current apart from unhide; while a column or workspace tag has the keyboard no pane does, so /focus reads empty and every pane's ctl says notcurrent (an empty read also no longer answers stale staged bytes). focus, ctl and commands now list after status, and the discovery test checks the root as a set rather than by position. Co-Authored-By: Claude Opus 5.5 --- src/ninep/ctl.zig | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) (limited to 'src/ninep/ctl.zig') diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index e47dec16..4afc82dc 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -12,6 +12,7 @@ const panes = @import("../panes.zig"); const config = @import("../config.zig"); const fs = @import("../fs.zig"); const tagline = @import("../tagline.zig"); +const layout = @import("../layout.zig"); const builtins = @import("../builtins.zig"); const exec_line = @import("../exec.zig"); const tree = @import("tree.zig"); @@ -162,7 +163,9 @@ pub fn paneText(p: *Pardes, pane: *Pane, buf: []u8) []const u8 { }) catch return w.buffered(); const font = p.settings.font.effective_name.get(); writeQuoted(&w, if (font.len == 0) "default" else font) catch return w.buffered(); - w.print(" {d:>11} {s} ", .{ config.tab_width, if (p.panes[p.active] == pane) "current" else "notcurrent" }) catch {}; + // 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 {}; return w.buffered(); } @@ -333,10 +336,15 @@ pub fn readCommands(p: *Pardes, req: Req) Reply { // ---- /focus ---- /// The pane with the keyboard, which a write of a serial moves, the way -/// writing `current` to rio's wctl does (rio(4)). +/// writing `current` to rio's wctl does (rio(4)). While a column's or the +/// workspace's tag has it, no pane does, and this reads empty. A write +/// gives the keyboard and nothing else: a folded pane stays folded, as +/// rio keeps `current` apart from `unhide`. pub fn readFocus(p: *Pardes, req: Req) Reply { + const out = p.fs.stage(p.gpa); + if (p.header_focus) return tree.stagedReply(p, req); 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); + out.print(p.gpa, "{d}\n", .{pane.serial}) catch return Reply.fail(req.tag, E.NOMEM); return tree.stagedReply(p, req); } @@ -506,8 +514,11 @@ test "focus reads the pane with the keyboard, and a serial written to it moves t 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. + // A header has the keyboard: no pane does, and a write takes it back. tagline.enterHeader(p, null); + try testing.expectEqualStrings("", rd(p, focus, 0, 64).bytes); + try testing.expectEqual(@as(u64, 0), call(p, .{ .tag = 1, .op = .getattr, .node = focus }).reply.attr.size); + try testing.expect(std.mem.endsWith(u8, rd(p, Node.of(second, .ctl), 0, 4096).bytes, " notcurrent ")); 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); @@ -518,6 +529,14 @@ test "focus reads the pane with the keyboard, and a serial written to it moves t 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); + + // Focus gives the keyboard only: a folded pane stays folded. + const folded = p.paneBySerial(second).?; + layout.toggleCollapse(p, folded); + try testing.expect(p.panes[folded].?.collapsed); + try testing.expectEqual(Status.ok, wr(p, focus, try std.fmt.bufPrint(&line, "{d}", .{second})).reply.status); + try testing.expectEqual(folded, p.active); + try testing.expect(p.panes[folded].?.collapsed); } test "the pane ctl takes acme's verbs and the pane's builtins, and refuses the rest in Plan 9's words" { -- cgit v1.3