diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 19:23:32 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:17 -0300 |
| commit | 576cee8a444d07a050df433f043367ca57fe36c4 (patch) | |
| tree | 37c4ebee644f0c0de2aa4688e4fdd4786fdd8d6b /src | |
| parent | a15c05b3d15bc269b3aec195c5e813a0f0203d4f (diff) | |
| download | pardes-576cee8a444d07a050df433f043367ca57fe36c4.tar.gz pardes-576cee8a444d07a050df433f043367ca57fe36c4.zip | |
Newcol shares a column's rows out again when its narrower tags would leave a pane under its tag and two rows, or is refused
A seeded sweep of placements (New, pane/new, Edit =, Tty, look, Delcol, deletes, refusals included) over screens 6 to 40 rows high found no refused placement that leaves a pane short since lyltlqnz and lrpzwyom; the pane a row short came from Newcol, which halves a column's width so long tags wrap onto more rows, raising those panes' minimums with no rows moved. Newcol now shares each such column's rows out again, and where a column has not the rows for every minimum there is no new column (Newcol: no space for a column: the panes' tags would not fit), as a size too small is refused. The sweep, 300 seeds, is the test; a user's drag or a tag grown by editing may still take a pane under, as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/ninep/tree.zig | 49 | ||||
| -rw-r--r-- | src/pardes.zig | 9 |
2 files changed, 58 insertions, 0 deletions
diff --git a/src/ninep/tree.zig b/src/ninep/tree.zig index 30bf073d..c86a689c 100644 --- a/src/ninep/tree.zig +++ b/src/ninep/tree.zig @@ -1578,6 +1578,55 @@ test "editor paths resolve to the same nodes the wire serves" { try testing.expectEqual(before, p.next_serial); } +test "random placements, refused or not, leave no pane under its tag and two rows" { + var tmp = testing.tmpDir(.{}); + defer tmp.cleanup(); + try tmp.dir.writeFile(testing.io, .{ .sub_path = "f.txt", .data = "one\ntwo\n" }); + var dir_buf: [4096]u8 = undefined; + const dir = dir_buf[0..try tmp.dir.realPath(testing.io, &dir_buf)]; + var look_line: [4200]u8 = undefined; + const looked = try std.fmt.bufPrint(&look_line, "{s}/f.txt\n", .{dir}); + var seed: u64 = 0; + while (seed < 300) : (seed += 1) { + var prng = std.Random.DefaultPrng.init(seed); + const r = prng.random(); + const rows = r.intRangeAtMost(u16, 6, 40); + const p = try pardes.Pardes.init(testing.allocator, .{ .tty_only = true, .cols = r.intRangeAtMost(u16, 60, 240), .rows = rows }); + defer p.deinit(); + while (p.nextEffect()) |_| {} + _ = try p.setTestFile("x\n"); + while (p.nextEffect()) |_| {} + for (0..r.uintLessThan(usize, 3)) |_| _ = th.wr(p, Node.of(serialOf(p), .exec), "Newcol\n"); + if (r.boolean()) p.settings.placement = .pardes; + for (0..40) |step| { + var serials: [pardes.MAX_PANES]u32 = undefined; + var n: usize = 0; + for (p.panes) |slot| if (slot) |pn| { + serials[n] = pn.serial; + n += 1; + }; + if (n == 0) break; + const at = serials[r.uintLessThan(usize, n)]; + const action = r.uintLessThan(u8, 8); + switch (action) { + 0 => _ = th.wr(p, Node.of(at, .exec), "New\n"), + 1 => _ = call(p, .{ .tag = 1, .op = .open, .node = @intFromEnum(TopFile.new) }), + 2 => _ = th.wr(p, Node.of(at, .exec), "Newcol\n"), + 3 => _ = th.wr(p, Node.of(at, .exec), "Edit =\n"), + 4 => _ = th.wr(p, Node.of(at, .exec), "Tty\n"), + 5 => _ = th.wr(p, Node.of(at, .look), looked), + 6 => _ = th.wr(p, Node.of(at, .exec), "Delcol\n"), + else => _ = th.wr(p, Node.of(at, .ctl), "delete\n"), + } + for (0..p.ncol) |c| if (!layout.columnAtMinimums(p, c)) { + std.debug.print("seed {d} rows {d} step {d} action {d}: column {d} under its minimums\n", .{ seed, rows, step, action, c }); + for (p.col_panes[c][0..p.col_n[c]]) |id| std.debug.print(" pane {d} h {d} min {d}\n", .{ id, p.rects[id].h, layout.minRows(p, id) }); + return error.TestUnexpectedResult; + }; + } + } +} + test "at the pane cap, pane/new, look and New each say so, and look reads back empty" { const p = try th.withFile(testing.allocator, "x\n"); defer p.deinit(); diff --git a/src/pardes.zig b/src/pardes.zig index 19fe51d1..22d5f7a1 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -5082,6 +5082,15 @@ pub const Pardes = struct { else "Newcol: no space for a column: this one is too narrow to split"); }; + // A narrower column wraps long tags onto more rows, which can leave + // a pane under its tag and two rows: its column's rows are shared + // out again, and where they cannot hold every minimum there is no + // new column, as a size too small is refused. + for (0..p.ncol) |k| if (!layout.columnAtMinimums(p, k) and !layout.shareColumn(p, k)) { + layout.dropColumn(p, c); + layout.compute(p); + return p.reportFailure(from_id, "Newcol: no space for a column: the panes' tags would not fit"); + }; tagline.enterHeader(p, c); } |
