diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 07:15:20 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | 03773fac517a2a26dc2f2813db9906d3bf7e91db (patch) | |
| tree | a048a062c7994cec1b676f04300ce0a5e0af9ffd /src/ninep/ctl.zig | |
| parent | d13ae04008e678ed992e6181487dbfd126a4154f (diff) | |
| download | pardes-03773fac517a2a26dc2f2813db9906d3bf7e91db.tar.gz pardes-03773fac517a2a26dc2f2813db9906d3bf7e91db.zip | |
`size` is refused only when the panes' own minima do not fit, so it is monotonic
The check read how the weights shared the rows after the resize, not
whether the rows could hold every pane's tag and two rows: a pane the
proportional squeeze took short made `size` refuse, growing included,
and refuse sizes it took a moment before. It now asks layout.minRows,
the minimum placement asks, per column; a size they fit is taken, and a
pane the resize took under its minimum gets its rows back. An out of
range size says `invalid size`, which 9ns reads as EINVAL, as the doc had.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/ctl.zig')
| -rw-r--r-- | src/ninep/ctl.zig | 55 |
1 files changed, 52 insertions, 3 deletions
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index 02201de0..665913fc 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -521,15 +521,20 @@ pub fn writeRoot(p: *Pardes, req: Req) Reply { if (!p.unattended) return refuse(p, req, "size: a frontend is attached and owns the size", line); if (apply) { const was: [2]u16 = .{ p.screen_w, p.screen_h }; + const before = p.rects; p.update(.{ .resize = .{ .cols = size[0], .rows = size[1] } }); p.sync(); // Every pane keeps its tag and two rows, as placement - // leaves them: a size too small for that is refused. - if (!pardes.layout.everyPaneRoomy(p)) { + // leaves them (layout.minRows, the one minimum): a size + // too small for the panes' minima is refused, and one + // they fit gives back rows the resize took under them. + if (!pardes.layout.minimaFit(p)) { p.update(.{ .resize = .{ .cols = was[0], .rows = was[1] } }); p.sync(); return refuse(p, req, "size: too small for the panes, each its tag and 2 rows", line); } + pardes.layout.restoreMinima(p, &before); + p.sync(); } continue; } @@ -548,7 +553,8 @@ pub fn writeRoot(p: *Pardes, req: Req) Reply { const size_min = [2]u16{ 20, 6 }; const size_max = [2]u16{ 4096, 4096 }; -const e_size = std.fmt.comptimePrint("size takes <cols> <rows>, at least {d}x{d}, at most {d}x{d}", .{ size_min[0], size_min[1], size_max[0], size_max[1] }); +// "invalid": the word 9ns reads as EINVAL (enameToErrno), as winsize's. +const e_size = std.fmt.comptimePrint("invalid size: takes <cols> <rows>, at least {d}x{d}, at most {d}x{d}", .{ size_min[0], size_min[1], size_max[0], size_max[1] }); /// `size <cols> <rows>`, within size_min and size_max. fn sizeOf(line: []const u8) ?[2]u16 { @@ -1703,6 +1709,49 @@ test "size sets the screen of a session no frontend is attached to, and only of try testing.expectEqual(@as(u16, 40), p.screen_h); } +test "size is monotonic: growing is never refused, and a size once taken is taken again" { + const p = try Pardes.init(testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 60 }); + defer p.deinit(); + while (p.nextEffect()) |_| {} + p.unattended = true; + const root_ctl = @intFromEnum(tree.TopFile.ctl); + const new = @intFromEnum(tree.TopFile.new); + // The dogfood's repro: Newcol, three pane/new, a Tty, then pane/new + // until a column has no room (ENOSPC). + try testing.expectEqual(Status.ok, wr(p, root_ctl, "Newcol\n").reply.status); + for (0..3) |_| try testing.expectEqual(Status.ok, call(p, .{ .tag = 1, .op = .open, .node = new }).reply.status); + try testing.expectEqual(Status.ok, wr(p, Node.of(p.panes[p.active].?.serial, .ctl), "Tty\n").reply.status); + p.sync(); + while (call(p, .{ .tag = 1, .op = .open, .node = new }).reply.status == .ok) p.sync(); + p.sync(); + var line: [32]u8 = undefined; + // Down a row at a time (59, 58, 57, 56 and on) until one is refused: + // once refused, every smaller one is too. + var taken: [60]bool = @splat(false); + var rows: u16 = 59; + var refused = false; + while (rows >= 20) : (rows -= 1) { + taken[rows] = wr(p, root_ctl, try std.fmt.bufPrint(&line, "size 160 {d}\n", .{rows})).reply.status == .ok; + if (refused) try testing.expect(!taken[rows]); + if (!taken[rows]) refused = true; + } + try testing.expect(refused); // the columns are full: some size is too small + // Back up: each size is taken again exactly when it was on the way down. + rows = 20; + while (rows <= 59) : (rows += 1) { + const ok = wr(p, root_ctl, try std.fmt.bufPrint(&line, "size 160 {d}\n", .{rows})).reply.status == .ok; + try testing.expectEqual(taken[rows], ok); + } + for ([_]u16{ 60, 61, 64, 70, 100 }) |grown| { + try testing.expectEqual(Status.ok, wr(p, root_ctl, try std.fmt.bufPrint(&line, "size 160 {d}\n", .{grown})).reply.status); + try testing.expectEqual(grown, p.screen_h); + } + // Out of range is EINVAL, in words 9ns reads as that. + const bad = wr(p, root_ctl, "size 5 2\n"); + try testing.expectEqual(E.INVAL, bad.errno()); + try testing.expectStringStartsWith(bad.reply.ename, "invalid size"); +} + test "Repl with no such language names a few whole, and where the rest are" { const p = try th.withTerm(testing.allocator); defer p.deinit(); |
