6a936579
feat: columns on — aspect hydration, rails, focus_dir, split chords
a73x 2026-08-24 18:06
Commit message
docscheck.budget
| Old | New | ||
|---|---|---|---|
| @@ -8,7 +8,7 @@ engine.zig 0 | |||
| 8 | handoff.zig 0 | 8 | handoff.zig 0 |
| 9 | interact.zig 0 | 9 | interact.zig 0 |
| 10 | keymap.zig 0 | 10 | keymap.zig 0 |
| 11 | layout.zig 528 | 11 | layout.zig 761 |
| 12 | main.zig 0 | 12 | main.zig 0 |
| 13 | muxa.zig 0 | 13 | muxa.zig 0 |
| 14 | mux_main.zig 0 | 14 | mux_main.zig 0 |
| @@ -27,7 +27,7 @@ shellint.zig 0 | |||
| 27 | sockpath.zig 0 | 27 | sockpath.zig 0 |
| 28 | spawn.zig 0 | 28 | spawn.zig 0 |
| 29 | testtmp.zig 0 | 29 | testtmp.zig 0 |
| 30 | wallview.zig 277 | 30 | wallview.zig 694 |
| 31 | wall.zig 0 | 31 | wall.zig 0 |
| 32 | wasm_core.zig 0 | 32 | wasm_core.zig 0 |
| 33 | webhub_main.zig 0 | 33 | webhub_main.zig 0 |
src/layout.zig
| Old | New | ||
|---|---|---|---|
| @@ -249,6 +249,17 @@ pub const Tree = struct { | |||
| 249 | try self.split(focus, tile, .stacked); | 249 | try self.split(focus, tile, .stacked); |
| 250 | } | 250 | } |
| 251 | 251 | ||
| 252 | /// Set the root container's orientation. `insert` wraps a bare root in | ||
| 253 | /// `.stacked` by default (matching the stripe era); hydration picks | ||
| 254 | /// `.beside` when the terminal is wide enough, and calls this to flip | ||
| 255 | /// the root the first `insert` created. | ||
| 256 | pub fn setRootOrient(self: *Tree, o: Orient) void { | ||
| 257 | if (self.root) |r| switch (r.*) { | ||
| 258 | .container => r.container.orient = o, | ||
| 259 | .leaf => {}, | ||
| 260 | }; | ||
| 261 | } | ||
| 262 | |||
| 252 | /// Delete the leaf; a container left with one child dissolves — the child | 263 | /// Delete the leaf; a container left with one child dissolves — the child |
| 253 | /// takes its place in the grandparent (or becomes root). | 264 | /// takes its place in the grandparent (or becomes root). |
| 254 | pub fn remove(self: *Tree, tile: u8) void { | 265 | pub fn remove(self: *Tree, tile: u8) void { |
| @@ -816,3 +827,23 @@ test "resize refuses before a nested container's slot goes sub-minimum" { | |||
| 816 | const r1 = f.rectOf(1).?; | 827 | const r1 = f.rectOf(1).?; |
| 817 | try std.testing.expect(r1.cols >= 2); | 828 | try std.testing.expect(r1.cols >= 2); |
| 818 | } | 829 | } |
| 830 | |||
| 831 | test "setRootOrient flips the root container's axis" { | ||
| 832 | // insert wraps a bare root in .stacked; hydration calls setRootOrient | ||
| 833 | // to pick .beside when the terminal is wide. Three tiles left→right | ||
| 834 | // is the proof the root's axis moved. | ||
| 835 | var t = Tree.init(std.testing.allocator); | ||
| 836 | defer t.deinit(); | ||
| 837 | try t.addFirst(0); | ||
| 838 | try t.insert(0, 1); | ||
| 839 | try t.insert(1, 2); | ||
| 840 | t.setRootOrient(.beside); | ||
| 841 | var f = try t.flatten(std.testing.allocator, 24, 82, .{ .rows = 2, .cols = 2 }, null); | ||
| 842 | defer f.deinit(std.testing.allocator); | ||
| 843 | // Three beside panes over 82 cols: 2 rails → 80 cols, 80/3 = 26r2, | ||
| 844 | // so 27/27/26 left→right. The left-to-right ordering is what a | ||
| 845 | // .beside root means, and what a .stacked root would not give. | ||
| 846 | try std.testing.expect(f.rectOf(0).?.left < f.rectOf(1).?.left); | ||
| 847 | try std.testing.expect(f.rectOf(1).?.left < f.rectOf(2).?.left); | ||
| 848 | try std.testing.expectEqual(@as(usize, 2), f.rails.len); | ||
| 849 | } | ||
src/wallview.zig
| Old | New | ||
|---|---|---|---|
| @@ -106,6 +106,25 @@ fn wallFloors(live: usize) layout.Floors { | |||
| 106 | }; | 106 | }; |
| 107 | } | 107 | } |
| 108 | 108 | ||
| 109 | /// The root container's orientation for N tiles at a given terminal size: | ||
| 110 | /// `.beside` when the terminal is wide enough that columns are the natural | ||
| 111 | /// cut, `.stacked` otherwise. The 2x corrects for cell shape — a terminal | ||
| 112 | /// twice as wide as it is tall has roughly square panes side-by-side. | ||
| 113 | fn rootOrient(size: proto.Size) layout.Orient { | ||
| 114 | return if (size.cols >= 2 * size.rows) .beside else .stacked; | ||
| 115 | } | ||
| 116 | |||
| 117 | /// `interact.Dir` and `layout.Dir` are the same enum tags in different | ||
| 118 | /// modules; this is the one place they meet, so the switch stays explicit. | ||
| 119 | fn dirOf(d: interact.PrefixFilter.Dir) layout.Dir { | ||
| 120 | return switch (d) { | ||
| 121 | .left => .left, | ||
| 122 | .down => .down, | ||
| 123 | .up => .up, | ||
| 124 | .right => .right, | ||
| 125 | }; | ||
| 126 | } | ||
| 127 | |||
| 109 | const State = enum { | 128 | const State = enum { |
| 110 | connecting, | 129 | connecting, |
| 111 | up, | 130 | up, |
| @@ -223,6 +242,12 @@ const Shared = struct { | |||
| 223 | /// mutates it under `paint_mu` — the same single-writer rule as `sel` | 242 | /// mutates it under `paint_mu` — the same single-writer rule as `sel` |
| 224 | /// and `label_rows` — and relayout flattens it to read rects. | 243 | /// and `label_rows` — and relayout flattens it to read rects. |
| 225 | tree: layout.Tree = layout.Tree.init(std.heap.page_allocator), | 244 | tree: layout.Tree = layout.Tree.init(std.heap.page_allocator), |
| 245 | /// The last flatten result, kept so `focus_dir` can read adjacency | ||
| 246 | /// without re-flattening under the keyboard's `paint_mu` hold. | ||
| 247 | /// Relayout replaces it (freeing the old one); the keyboard thread | ||
| 248 | /// is the sole reader, under the same ownership as `tree`. | ||
| 249 | last_flat: ?layout.Flat = null, | ||
| 250 | flat_alloc: std.mem.Allocator = std.heap.page_allocator, | ||
| 226 | /// Bumped when the terminal's contents are no longer anybody's paint — | 251 | /// Bumped when the terminal's contents are no longer anybody's paint — |
| 227 | /// a relayout re-cut the stripes, or the focus moved and the newly | 252 | /// a relayout re-cut the stripes, or the focus moved and the newly |
| 228 | /// focused tile owes a full repaint. Pumps compare it against what | 253 | /// focused tile owes a full repaint. Pumps compare it against what |
| @@ -312,6 +337,9 @@ const Tile = struct { | |||
| 312 | /// tile must attach at 0x0 — the entry tile and a chord-born tile may | 337 | /// tile must attach at 0x0 — the entry tile and a chord-born tile may |
| 313 | /// carry the rect. | 338 | /// carry the rect. |
| 314 | creates: bool = false, | 339 | creates: bool = false, |
| 340 | /// How a chord-born tile enters the tree when its session name arrives. | ||
| 341 | /// Set by the keyboard with `.ask`, read by the answer handler. | ||
| 342 | pending_place: Place = .beside_focus, | ||
| 315 | /// One wall-file complaint per tile, whatever went wrong and however | 343 | /// One wall-file complaint per tile, whatever went wrong and however |
| 316 | /// often. `client.warnWall`'s latch, one per tile because a tile is the | 344 | /// often. `client.warnWall`'s latch, one per tile because a tile is the |
| 317 | /// unit that has an attach to record. | 345 | /// unit that has an attach to record. |
| @@ -1543,6 +1571,23 @@ fn paintEmptyWallLocked(shared: *Shared) void { | |||
| 1543 | proto.writeAllFd(shared.out_fd, fbs.getWritten()) catch {}; | 1571 | proto.writeAllFd(shared.out_fd, fbs.getWritten()) catch {}; |
| 1544 | } | 1572 | } |
| 1545 | 1573 | ||
| 1574 | /// Paint the vertical rails between `.beside` siblings. One column of | ||
| 1575 | /// `\x1b[7m \x1b[0m` per rail — a reverse-video bar in the label-bar's | ||
| 1576 | /// style, so a rail reads as structure and not as session output. The wall | ||
| 1577 | /// owns rails; tiles never touch them (paint.zig's span-bounded clears). | ||
| 1578 | fn paintRailsLocked(shared: *Shared, flat: layout.Flat) void { | ||
| 1579 | if (!shared.is_tty) return; | ||
| 1580 | if (flat.rails.len == 0) return; | ||
| 1581 | var buf: [128]u8 = undefined; | ||
| 1582 | for (flat.rails) |rail| { | ||
| 1583 | var row: u16 = rail.top; | ||
| 1584 | while (row < rail.top + rail.rows) : (row += 1) { | ||
| 1585 | const out = std.fmt.bufPrint(&buf, "\x1b[{d};{d}H\x1b[7m \x1b[0m", .{ row + 1, rail.col + 1 }) catch continue; | ||
| 1586 | proto.writeAllFd(shared.out_fd, out) catch {}; | ||
| 1587 | } | ||
| 1588 | } | ||
| 1589 | } | ||
| 1590 | |||
| 1546 | /// One `paint_mu` hold: no window where a pump paints rows that just | 1591 | /// One `paint_mu` hold: no window where a pump paints rows that just |
| 1547 | /// changed owner. | 1592 | /// changed owner. |
| 1548 | fn relayout( | 1593 | fn relayout( |
| @@ -1571,7 +1616,9 @@ fn relayout( | |||
| 1571 | return; | 1616 | return; |
| 1572 | } | 1617 | } |
| 1573 | if (shared.tree.flatten(alloc, shared.size.rows, shared.size.cols, wallFloors(live), null)) |flat| { | 1618 | if (shared.tree.flatten(alloc, shared.size.rows, shared.size.cols, wallFloors(live), null)) |flat| { |
| 1574 | defer flat.deinit(alloc); | 1619 | // Replace the stored flat: free the old one first, then keep this. |
| 1620 | if (shared.last_flat) |*old| old.deinit(shared.flat_alloc); | ||
| 1621 | shared.last_flat = flat; | ||
| 1575 | for (tiles, present) |*t, p| { | 1622 | for (tiles, present) |*t, p| { |
| 1576 | if (!p) continue; | 1623 | if (!p) continue; |
| 1577 | if (flat.rectOf(@intCast(t.idx))) |r| { | 1624 | if (flat.rectOf(@intCast(t.idx))) |r| { |
| @@ -1585,6 +1632,7 @@ fn relayout( | |||
| 1585 | // now claims. | 1632 | // now claims. |
| 1586 | t.resize_pending.store(true, .release); | 1633 | t.resize_pending.store(true, .release); |
| 1587 | } | 1634 | } |
| 1635 | paintRailsLocked(shared, flat); | ||
| 1588 | } else |_| {} | 1636 | } else |_| {} |
| 1589 | 1637 | ||
| 1590 | // The generation bump is what puts the rects back: every surviving | 1638 | // The generation bump is what puts the rects back: every surviving |
| @@ -1744,6 +1792,10 @@ const FocusTo = union(enum) { | |||
| 1744 | stay, | 1792 | stay, |
| 1745 | }; | 1793 | }; |
| 1746 | 1794 | ||
| 1795 | /// How a chord-born tile enters the tree: `beside_focus` is the `c` chord's | ||
| 1796 | /// sibling-after-focus (insert), `right_of` / `below` are the split chords. | ||
| 1797 | const Place = enum { beside_focus, right_of, below }; | ||
| 1798 | |||
| 1747 | /// A sibling is focused if it has a tile and GETS one if not — otherwise a | 1799 | /// A sibling is focused if it has a tile and GETS one if not — otherwise a |
| 1748 | /// tile labelled S would paint T. | 1800 | /// tile labelled S would paint T. |
| 1749 | fn addSessionTile( | 1801 | fn addSessionTile( |
| @@ -1754,6 +1806,7 @@ fn addSessionTile( | |||
| 1754 | shared: *Shared, | 1806 | shared: *Shared, |
| 1755 | from: usize, | 1807 | from: usize, |
| 1756 | name: []const u8, | 1808 | name: []const u8, |
| 1809 | place: Place, | ||
| 1757 | ) FocusTo { | 1810 | ) FocusTo { |
| 1758 | const target = tiles[from].r.target; | 1811 | const target = tiles[from].r.target; |
| 1759 | const want = proto.resolveName(name); | 1812 | const want = proto.resolveName(name); |
| @@ -1779,7 +1832,11 @@ fn addSessionTile( | |||
| 1779 | // every rect and sets it again, so the two agree. | 1832 | // every rect and sets it again, so the two agree. |
| 1780 | shared.label_rows = if (new_live > 1) 1 else 0; | 1833 | shared.label_rows = if (new_live > 1) 1 else 0; |
| 1781 | const at = live.*; | 1834 | const at = live.*; |
| 1782 | shared.tree.insert(@intCast(from), @intCast(at)) catch return .full; | 1835 | switch (place) { |
| 1836 | .beside_focus => shared.tree.insert(@intCast(from), @intCast(at)) catch return .full, | ||
| 1837 | .right_of => shared.tree.splitRight(@intCast(from), @intCast(at)) catch return .full, | ||
| 1838 | .below => shared.tree.splitBelow(@intCast(from), @intCast(at)) catch return .full, | ||
| 1839 | } | ||
| 1783 | const flat = shared.tree.flatten( | 1840 | const flat = shared.tree.flatten( |
| 1784 | alloc, | 1841 | alloc, |
| 1785 | shared.size.rows, | 1842 | shared.size.rows, |
| @@ -2040,6 +2097,7 @@ fn hydrate( | |||
| 2040 | spawnPump(&tiles[at]); | 2097 | spawnPump(&tiles[at]); |
| 2041 | added += 1; | 2098 | added += 1; |
| 2042 | } | 2099 | } |
| 2100 | if (added > 0) shared.tree.setRootOrient(rootOrient(shared.size)); | ||
| 2043 | return added; | 2101 | return added; |
| 2044 | } | 2102 | } |
| 2045 | 2103 | ||
| @@ -2164,6 +2222,8 @@ pub fn run(alloc: std.mem.Allocator, resolved: []const Resolved, entry: Entry) ! | |||
| 2164 | // has one, and the geometry is re-cut when the first Ctrl-\ w hydrates | 2222 | // has one, and the geometry is re-cut when the first Ctrl-\ w hydrates |
| 2165 | // the rest — `relayout` already does exactly that for `x`. | 2223 | // the rest — `relayout` already does exactly that for `x`. |
| 2166 | var shared = Shared{ .out_fd = stdout_fd, .size = size, .is_tty = is_tty }; | 2224 | var shared = Shared{ .out_fd = stdout_fd, .size = size, .is_tty = is_tty }; |
| 2225 | shared.tree = layout.Tree.init(alloc); | ||
| 2226 | shared.flat_alloc = alloc; | ||
| 2167 | { | 2227 | { |
| 2168 | var i: usize = 0; | 2228 | var i: usize = 0; |
| 2169 | while (i < resolved.len) : (i += 1) { | 2229 | while (i < resolved.len) : (i += 1) { |
| @@ -2173,6 +2233,8 @@ pub fn run(alloc: std.mem.Allocator, resolved: []const Resolved, entry: Entry) ! | |||
| 2173 | shared.tree.insert(@intCast(i - 1), @intCast(i)) catch return 2; | 2233 | shared.tree.insert(@intCast(i - 1), @intCast(i)) catch return 2; |
| 2174 | } | 2234 | } |
| 2175 | } | 2235 | } |
| 2236 | if (resolved.len > 1) | ||
| 2237 | shared.tree.setRootOrient(rootOrient(size)); | ||
| 2176 | } | 2238 | } |
| 2177 | const init_flat = shared.tree.flatten( | 2239 | const init_flat = shared.tree.flatten( |
| 2178 | alloc, | 2240 | alloc, |
| @@ -2184,7 +2246,7 @@ pub fn run(alloc: std.mem.Allocator, resolved: []const Resolved, entry: Entry) ! | |||
| 2184 | std.debug.print("mux: terminal too small for {d} tiles\n", .{resolved.len}); | 2246 | std.debug.print("mux: terminal too small for {d} tiles\n", .{resolved.len}); |
| 2185 | return 2; | 2247 | return 2; |
| 2186 | }; | 2248 | }; |
| 2187 | defer init_flat.deinit(alloc); | 2249 | shared.last_flat = init_flat; |
| 2188 | 2250 | ||
| 2189 | // A daemon that dies mid-write must surface as a write error on that | 2251 | // A daemon that dies mid-write must surface as a write error on that |
| 2190 | // tile's thread, not a process-fatal SIGPIPE. | 2252 | // tile's thread, not a process-fatal SIGPIPE. |
| @@ -2366,6 +2428,7 @@ pub fn run(alloc: std.mem.Allocator, resolved: []const Resolved, entry: Entry) ! | |||
| 2366 | &shared, | 2428 | &shared, |
| 2367 | z, | 2429 | z, |
| 2368 | name.slice(), | 2430 | name.slice(), |
| 2431 | tiles[z].pending_place, | ||
| 2369 | )) { | 2432 | )) { |
| 2370 | .moved => |to| { | 2433 | .moved => |to| { |
| 2371 | last_focus = z; | 2434 | last_focus = z; |
| @@ -2509,6 +2572,7 @@ pub fn run(alloc: std.mem.Allocator, resolved: []const Resolved, entry: Entry) ! | |||
| 2509 | // answer arrives on the pump's transport and is posted | 2572 | // answer arrives on the pump's transport and is posted |
| 2510 | // back to the keyboard (`postAnswer`), which moves the | 2573 | // back to the keyboard (`postAnswer`), which moves the |
| 2511 | // focus or grows the wall. | 2574 | // focus or grows the wall. |
| 2575 | tiles[z].pending_place = .beside_focus; | ||
| 2512 | tiles[z].ask.store(@intFromEnum(client.SwitchIntent.new), .release); | 2576 | tiles[z].ask.store(@intFromEnum(client.SwitchIntent.new), .release); |
| 2513 | ring(&tiles[z]); | 2577 | ring(&tiles[z]); |
| 2514 | }, | 2578 | }, |
| @@ -2520,9 +2584,26 @@ pub fn run(alloc: std.mem.Allocator, resolved: []const Resolved, entry: Entry) ! | |||
| 2520 | tiles[z].ask.store(@intFromEnum(client.SwitchIntent.prev), .release); | 2584 | tiles[z].ask.store(@intFromEnum(client.SwitchIntent.prev), .release); |
| 2521 | ring(&tiles[z]); | 2585 | ring(&tiles[z]); |
| 2522 | }, | 2586 | }, |
| 2523 | .focus_dir, | 2587 | .focus_dir => |d| { |
| 2524 | .split_right, | 2588 | if (shared.last_flat) |flat| { |
| 2525 | .split_below, | 2589 | if (layout.neighbor(flat, @intCast(z), dirOf(d))) |nb| { |
| 2590 | if (nb < live and present[nb] and nb != z) { | ||
| 2591 | last_focus = z; | ||
| 2592 | setFocus(tiles[0..live], &shared, nb); | ||
| 2593 | } | ||
| 2594 | } | ||
| 2595 | } | ||
| 2596 | }, | ||
| 2597 | .split_right => { | ||
| 2598 | tiles[z].pending_place = .right_of; | ||
| 2599 | tiles[z].ask.store(@intFromEnum(client.SwitchIntent.new), .release); | ||
| 2600 | ring(&tiles[z]); | ||
| 2601 | }, | ||
| 2602 | .split_below => { | ||
| 2603 | tiles[z].pending_place = .below; | ||
| 2604 | tiles[z].ask.store(@intFromEnum(client.SwitchIntent.new), .release); | ||
| 2605 | ring(&tiles[z]); | ||
| 2606 | }, | ||
| 2526 | .fullscreen, | 2607 | .fullscreen, |
| 2527 | .resize, | 2608 | .resize, |
| 2528 | => {}, | 2609 | => {}, |
| @@ -2980,6 +3061,12 @@ test "viewRows: a one-tile wall keeps every row, a multi-tile wall loses the lab | |||
| 2980 | test "relayout sets resize_pending on every live tile" { | 3061 | test "relayout sets resize_pending on every live tile" { |
| 2981 | const alloc = std.testing.allocator; | 3062 | const alloc = std.testing.allocator; |
| 2982 | var shared = Shared{ .out_fd = -1, .size = .{ .cols = 80, .rows = 24 }, .is_tty = false }; | 3063 | var shared = Shared{ .out_fd = -1, .size = .{ .cols = 80, .rows = 24 }, .is_tty = false }; |
| 3064 | shared.tree = layout.Tree.init(alloc); | ||
| 3065 | shared.flat_alloc = alloc; | ||
| 3066 | defer shared.tree.deinit(); | ||
| 3067 | defer if (shared.last_flat) |*f| f.deinit(alloc); | ||
| 3068 | try shared.tree.addFirst(0); | ||
| 3069 | try shared.tree.insert(0, 1); | ||
| 2983 | const tiles = try alloc.alloc(Tile, 2); | 3070 | const tiles = try alloc.alloc(Tile, 2); |
| 2984 | defer alloc.free(tiles); | 3071 | defer alloc.free(tiles); |
| 2985 | const present = try alloc.alloc(bool, 2); | 3072 | const present = try alloc.alloc(bool, 2); |
| @@ -3045,6 +3132,11 @@ test "a pump's answer that grew the wall re-cuts it; a mere focus move does not" | |||
| 3045 | test "a one-tile wall draws no label bar and paints row 1" { | 3132 | test "a one-tile wall draws no label bar and paints row 1" { |
| 3046 | const alloc = std.testing.allocator; | 3133 | const alloc = std.testing.allocator; |
| 3047 | var shared = Shared{ .out_fd = -1, .size = .{ .cols = 80, .rows = 24 }, .is_tty = false }; | 3134 | var shared = Shared{ .out_fd = -1, .size = .{ .cols = 80, .rows = 24 }, .is_tty = false }; |
| 3135 | shared.tree = layout.Tree.init(alloc); | ||
| 3136 | shared.flat_alloc = alloc; | ||
| 3137 | defer shared.tree.deinit(); | ||
| 3138 | defer if (shared.last_flat) |*f| f.deinit(alloc); | ||
| 3139 | try shared.tree.addFirst(0); | ||
| 3048 | const tiles = try alloc.alloc(Tile, 1); | 3140 | const tiles = try alloc.alloc(Tile, 1); |
| 3049 | defer alloc.free(tiles); | 3141 | defer alloc.free(tiles); |
| 3050 | const present = try alloc.alloc(bool, 1); | 3142 | const present = try alloc.alloc(bool, 1); |
| @@ -3244,6 +3336,12 @@ test "a relayout drops every tile's highlight, because the stripes move under it | |||
| 3244 | // The generation is the whole mechanism, so it is what this pins. | 3336 | // The generation is the whole mechanism, so it is what this pins. |
| 3245 | const alloc = std.testing.allocator; | 3337 | const alloc = std.testing.allocator; |
| 3246 | var shared = Shared{ .out_fd = -1, .size = .{ .cols = 80, .rows = 24 }, .is_tty = false }; | 3338 | var shared = Shared{ .out_fd = -1, .size = .{ .cols = 80, .rows = 24 }, .is_tty = false }; |
| 3339 | shared.tree = layout.Tree.init(alloc); | ||
| 3340 | shared.flat_alloc = alloc; | ||
| 3341 | defer shared.tree.deinit(); | ||
| 3342 | defer if (shared.last_flat) |*f| f.deinit(alloc); | ||
| 3343 | try shared.tree.addFirst(0); | ||
| 3344 | try shared.tree.insert(0, 1); | ||
| 3247 | const tiles = try alloc.alloc(Tile, 2); | 3345 | const tiles = try alloc.alloc(Tile, 2); |
| 3248 | defer alloc.free(tiles); | 3346 | defer alloc.free(tiles); |
| 3249 | const present = try alloc.alloc(bool, 2); | 3347 | const present = try alloc.alloc(bool, 2); |
test/e2e.sh
| Old | New | ||
|---|---|---|---|
| @@ -4970,7 +4970,8 @@ wait_grid "$SOCK26" "cwb-pin" "CLI wall: session b's marker" b | |||
| 4970 | --sock "$SOCK26" --session b > "$OUT.cwinj" 2>&1 ) & | 4970 | --sock "$SOCK26" --session b > "$OUT.cwinj" 2>&1 ) & |
| 4971 | CWINJPID=$! | 4971 | CWINJPID=$! |
| 4972 | set +e | 4972 | set +e |
| 4973 | timeout 40 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.cwcap" --err "$OUT.cwcap.err" -- \ | 4973 | # tall: two tiles at 100x30 trip the aspect rule (100 >= 60) |
| 4974 | timeout 40 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.cwcap" --err "$OUT.cwcap.err" -- \ | ||
| 4974 | "$MUX" wall --sock "$SOCK26#a" "--sock $SOCK26#b" > "$OUT.cwpc" 2>&1 <<'EOF' | 4975 | "$MUX" wall --sock "$SOCK26#a" "--sock $SOCK26#b" > "$OUT.cwpc" 2>&1 <<'EOF' |
| 4975 | expect cwlive-pin 20000 | 4976 | expect cwlive-pin 20000 |
| 4976 | send \x1cd | 4977 | send \x1cd |
| @@ -4992,7 +4993,7 @@ grep -q "cwb-pin" "$OUT.cwcap" || { | |||
| 4992 | grep -q "cwlive-pin" "$OUT.cwcap" || { | 4993 | grep -q "cwlive-pin" "$OUT.cwcap" || { |
| 4993 | echo "e2e FAIL: CLI wall: the live delta never painted"; exit 1; } | 4994 | echo "e2e FAIL: CLI wall: the live delta never painted"; exit 1; } |
| 4994 | # Every tile claims its rect: a and b were created 80-wide by pipe, and | 4995 | # Every tile claims its rect: a and b were created 80-wide by pipe, and |
| 4995 | # this 100-wide wall resized both on attach. There is no 0x0 passivity to | 4996 | # this 40-wide wall resized both on attach. There is no 0x0 passivity to |
| 4996 | # assert — the wall writes the sessions it shows. | 4997 | # assert — the wall writes the sessions it shows. |
| 4997 | assert_stopped "$SOCK26" "$D23PID" "CLI wall" "$OUT.cwstop" | 4998 | assert_stopped "$SOCK26" "$D23PID" "CLI wall" "$OUT.cwstop" |
| 4998 | D23PID="" | 4999 | D23PID="" |
| @@ -5091,7 +5092,8 @@ D25PID=$! | |||
| 5091 | wait_sock "$SOCK28" "$OUT.nsw.d" "new-session daemon never bound" | 5092 | wait_sock "$SOCK28" "$OUT.nsw.d" "new-session daemon never bound" |
| 5092 | 5093 | ||
| 5093 | set +e | 5094 | set +e |
| 5094 | timeout 40 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.nsw" --err "$OUT.nsw.err" \ | 5095 | # tall: Ctrl-\ c adds a second tile at 80x24 (80 >= 48) |
| 5096 | timeout 40 "$PTYCLIENT" --cols 40 --rows 24 --out "$OUT.nsw" --err "$OUT.nsw.err" \ | ||
| 5095 | -- "$MUX" --sock "$SOCK28" > "$OUT.nsw.log" 2>&1 <<'EOF' | 5097 | -- "$MUX" --sock "$SOCK28" > "$OUT.nsw.log" 2>&1 <<'EOF' |
| 5096 | expect \x1b[?1049h 15000 | 5098 | expect \x1b[?1049h 15000 |
| 5097 | settle 400 15000 | 5099 | settle 400 15000 |
| @@ -5216,7 +5218,8 @@ D26PID=$! | |||
| 5216 | wait_sock "$SOCK29" "$OUT.ring.d" "session-ring daemon never bound" | 5218 | wait_sock "$SOCK29" "$OUT.ring.d" "session-ring daemon never bound" |
| 5217 | 5219 | ||
| 5218 | set +e | 5220 | set +e |
| 5219 | timeout 60 "$PTYCLIENT" --cols 80 --rows 24 --out "$OUT.ring" --err "$OUT.ring.err" \ | 5221 | # tall: Ctrl-\ c adds tiles at 80x24 (80 >= 48) |
| 5222 | timeout 60 "$PTYCLIENT" --cols 40 --rows 24 --out "$OUT.ring" --err "$OUT.ring.err" \ | ||
| 5220 | -- "$MUX" --sock "$SOCK29" > "$OUT.ring.log" 2>&1 <<'EOF' | 5223 | -- "$MUX" --sock "$SOCK29" > "$OUT.ring.log" 2>&1 <<'EOF' |
| 5221 | expect \x1b[?1049h 15000 | 5224 | expect \x1b[?1049h 15000 |
| 5222 | settle 400 15000 | 5225 | settle 400 15000 |
| @@ -5654,7 +5657,8 @@ wait_grid "$SOCK37" "zsb-pin" "focus skip: session b's marker" b | |||
| 5654 | ZSATT_BEFORE=$(attaches_now "$SOCK37") | 5657 | ZSATT_BEFORE=$(attaches_now "$SOCK37") |
| 5655 | watch_clients "$SOCK37" "$OUT.zswatch" | 5658 | watch_clients "$SOCK37" "$OUT.zswatch" |
| 5656 | set +e | 5659 | set +e |
| 5657 | timeout 90 "$PTYCLIENT" --cols 92 --rows 30 --out "$OUT.zscap" --err "$OUT.zscap.err" -- \ | 5660 | # tall: the aspect rule would cut columns at 92x30 (92 >= 60) |
| 5661 | timeout 90 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.zscap" --err "$OUT.zscap.err" -- \ | ||
| 5658 | "$MUX" wall "--sock $SOCK37#a" "--sock $SOCK37#b" > "$OUT.zspc" 2>&1 <<'EOF' | 5662 | "$MUX" wall "--sock $SOCK37#a" "--sock $SOCK37#b" > "$OUT.zspc" 2>&1 <<'EOF' |
| 5659 | expect zsb-pin 20000 | 5663 | expect zsb-pin 20000 |
| 5660 | settle 700 20000 | 5664 | settle 700 20000 |
| @@ -5713,13 +5717,12 @@ grep -q "zs-two" "$OUT.zsfa" && { | |||
| 5713 | assert_attach_delta "$ZSATT_BEFORE" "$ZSATT_AFTER" 2 "focus skip" | 5717 | assert_attach_delta "$ZSATT_BEFORE" "$ZSATT_AFTER" 2 "focus skip" |
| 5714 | assert_never_two_clients "$OUT.zswatch" a "focus skip" | 5718 | assert_never_two_clients "$OUT.zswatch" a "focus skip" |
| 5715 | assert_never_two_clients "$OUT.zswatch" b "focus skip" | 5719 | assert_never_two_clients "$OUT.zswatch" b "focus skip" |
| 5716 | # Both tiles claimed their rect: 92 cols each (this leg's own width) and 14 | 5720 | # Both tiles claimed their rect: 40 cols each (this leg's own width) and 14 |
| 5717 | # rows each (30 terminal rows cut into two stripes of 15, minus one label | 5721 | # rows each (30 terminal rows cut into two stripes of 15, minus one label |
| 5718 | # bar each — layoutStripes' arithmetic, held to the size the wall attached | 5722 | # bar each). A focus move resizes nothing. |
| 5719 | # at). A focus move resizes nothing. | ||
| 5720 | for _s in a b; do | 5723 | for _s in a b; do |
| 5721 | timeout 20 "$MUXA" status --sock "$SOCK37" --session "$_s" > "$OUT.zsst$_s" 2>&1 | 5724 | timeout 20 "$MUXA" status --sock "$SOCK37" --session "$_s" > "$OUT.zsst$_s" 2>&1 |
| 5722 | grep -q '"cols":92' "$OUT.zsst$_s" || { | 5725 | grep -q '"cols":40' "$OUT.zsst$_s" || { |
| 5723 | echo "e2e FAIL: focus skip: session $_s is not at the width its tile claimed:" | 5726 | echo "e2e FAIL: focus skip: session $_s is not at the width its tile claimed:" |
| 5724 | cat "$OUT.zsst$_s"; exit 1; } | 5727 | cat "$OUT.zsst$_s"; exit 1; } |
| 5725 | grep -q '"rows":14' "$OUT.zsst$_s" || { | 5728 | grep -q '"rows":14' "$OUT.zsst$_s" || { |
| @@ -5903,7 +5906,9 @@ pipe_detach | |||
| 5903 | wait_grid "$SOCK39" "zdlive-pin" "dead tile: the live session's marker" a | 5906 | wait_grid "$SOCK39" "zdlive-pin" "dead tile: the live session's marker" a |
| 5904 | 5907 | ||
| 5905 | set +e | 5908 | set +e |
| 5906 | timeout 90 "$PTYCLIENT" --cols 90 --rows 24 --out "$OUT.zdcap" --err "$OUT.zdcap.err" -- \ | 5909 | # tall: two tiles at 90x24 trip the aspect rule (90 >= 48); 70x36 stays |
| 5910 | # stacked (70 < 72) and fits the bar label the assertion greps for | ||
| 5911 | timeout 90 "$PTYCLIENT" --cols 70 --rows 36 --out "$OUT.zdcap" --err "$OUT.zdcap.err" -- \ | ||
| 5907 | "$MUX" wall "--sock $SOCK39#a" "--sock $SOCK39#ghost" > "$OUT.zdpc" 2>&1 <<'EOF' | 5912 | "$MUX" wall "--sock $SOCK39#a" "--sock $SOCK39#ghost" > "$OUT.zdpc" 2>&1 <<'EOF' |
| 5908 | expect zdlive-pin 20000 | 5913 | expect zdlive-pin 20000 |
| 5909 | settle 800 20000 | 5914 | settle 800 20000 |
| @@ -6124,7 +6129,8 @@ _whx_n=$(wc -l < "$WHXSTATE/mux/wall") | |||
| 6124 | # single expect plus `settle` gives; the tile it forgets is the FOCUSED | 6129 | # single expect plus `settle` gives; the tile it forgets is the FOCUSED |
| 6125 | # one, tile 0, whose line the file is then asserted NOT to hold. | 6130 | # one, tile 0, whose line the file is then asserted NOT to hold. |
| 6126 | set +e | 6131 | set +e |
| 6127 | XDG_STATE_HOME="$WHXSTATE" timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \ | 6132 | # tall: two tiles at 100x30 trip the aspect rule (100 >= 60) |
| 6133 | XDG_STATE_HOME="$WHXSTATE" timeout 60 "$PTYCLIENT" --cols 40 --rows 30 \ | ||
| 6128 | --out "$OUT.whxcap" --err "$OUT.whxcap.err" -- \ | 6134 | --out "$OUT.whxcap" --err "$OUT.whxcap.err" -- \ |
| 6129 | "$MUX" wall > "$OUT.whxpc" 2>&1 <<'EOF' | 6135 | "$MUX" wall > "$OUT.whxpc" 2>&1 <<'EOF' |
| 6130 | expect whxb-pin 20000 | 6136 | expect whxb-pin 20000 |
| @@ -6158,17 +6164,17 @@ _whx_bars=$(grep -o "whxb-pin" "$OUT.whxcap" | wc -l) | |||
| 6158 | cat "$OUT.whxpc"; exit 1; } | 6164 | cat "$OUT.whxpc"; exit 1; } |
| 6159 | # ...and the SESSION is untouched: it still answers, and it still holds | 6165 | # ...and the SESSION is untouched: it still answers, and it still holds |
| 6160 | # what it held. "Remove is detach" — the tile went, the session did not. | 6166 | # what it held. "Remove is detach" — the tile went, the session did not. |
| 6161 | # xa was 80-wide from its pipe attach; this 100-wide wall resized it on | 6167 | # xa was 80-wide from its pipe attach; this 40-wide wall resized it on |
| 6162 | # hydrate, and the detach left that size behind. | 6168 | # hydrate, and the detach left that size behind. |
| 6163 | XDG_STATE_HOME="$WHSTATE" "$MUXA" status --sock "$SOCK40" --session xa > "$OUT.whxst" 2>&1 | 6169 | XDG_STATE_HOME="$WHSTATE" "$MUXA" status --sock "$SOCK40" --session xa > "$OUT.whxst" 2>&1 |
| 6164 | grep -q '"cols":100' "$OUT.whxst" || { | 6170 | grep -q '"cols":40' "$OUT.whxst" || { |
| 6165 | echo "e2e FAIL: x forgets: session xa stopped answering — \\x1cx killed it:" | 6171 | echo "e2e FAIL: x forgets: session xa stopped answering — \\x1cx killed it:" |
| 6166 | cat "$OUT.whxst"; exit 1; } | 6172 | cat "$OUT.whxst"; exit 1; } |
| 6167 | wait_grid "$SOCK40" "whxa-pin" "x forgets: xa's grid outlived its tile" xa | 6173 | wait_grid "$SOCK40" "whxa-pin" "x forgets: xa's grid outlived its tile" xa |
| 6168 | 6174 | ||
| 6169 | # The last tile: an empty wall SAYS so rather than going blank. | 6175 | # The last tile: an empty wall SAYS so rather than going blank. |
| 6170 | set +e | 6176 | set +e |
| 6171 | XDG_STATE_HOME="$WHXSTATE" timeout 60 "$PTYCLIENT" --cols 100 --rows 30 \ | 6177 | XDG_STATE_HOME="$WHXSTATE" timeout 60 "$PTYCLIENT" --cols 40 --rows 30 \ |
| 6172 | --out "$OUT.whxcap2" --err "$OUT.whxcap2.err" -- \ | 6178 | --out "$OUT.whxcap2" --err "$OUT.whxcap2.err" -- \ |
| 6173 | "$MUX" wall > "$OUT.whxpc2" 2>&1 <<'EOF' | 6179 | "$MUX" wall > "$OUT.whxpc2" 2>&1 <<'EOF' |
| 6174 | expect whxb-pin 20000 | 6180 | expect whxb-pin 20000 |
| @@ -6269,7 +6275,9 @@ CVATT_BEFORE=$(attaches_now "$SOCK45") | |||
| 6269 | --sock "$SOCK45" --session 0 > "$OUT.cvinj" 2>&1 ) & | 6275 | --sock "$SOCK45" --session 0 > "$OUT.cvinj" 2>&1 ) & |
| 6270 | CVINJPID=$! | 6276 | CVINJPID=$! |
| 6271 | set +e | 6277 | set +e |
| 6272 | XDG_STATE_HOME="$CVSTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \ | 6278 | # tall: Ctrl-\ w folds in a saved tile, making two at 100x30 (100 >= 60); |
| 6279 | # 70x36 stays stacked (70 < 72) and fits the bar label the assertion greps | ||
| 6280 | XDG_STATE_HOME="$CVSTATE" timeout 90 "$PTYCLIENT" --cols 70 --rows 36 \ | ||
| 6273 | --out "$OUT.cvcap" --err "$OUT.cvcap.err" \ | 6281 | --out "$OUT.cvcap" --err "$OUT.cvcap.err" \ |
| 6274 | -- "$MUX" --sock "$SOCK45" > "$OUT.cvpc" 2>&1 <<'EOF' | 6282 | -- "$MUX" --sock "$SOCK45" > "$OUT.cvpc" 2>&1 <<'EOF' |
| 6275 | expect \x1b[?1049h 15000 | 6283 | expect \x1b[?1049h 15000 |
| @@ -6360,7 +6368,8 @@ grep -qx -- "--sock $SOCK46#two" "$RGSTATE/mux/wall" 2>/dev/null && { | |||
| 6360 | 6368 | ||
| 6361 | RGATT_BEFORE=$(attaches_now "$SOCK46") | 6369 | RGATT_BEFORE=$(attaches_now "$SOCK46") |
| 6362 | set +e | 6370 | set +e |
| 6363 | XDG_STATE_HOME="$RGSTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \ | 6371 | # tall: Ctrl-\ n grows the wall to two tiles at 100x30 (100 >= 60) |
| 6372 | XDG_STATE_HOME="$RGSTATE" timeout 90 "$PTYCLIENT" --cols 40 --rows 30 \ | ||
| 6364 | --out "$OUT.rgcap" --err "$OUT.rgcap.err" \ | 6373 | --out "$OUT.rgcap" --err "$OUT.rgcap.err" \ |
| 6365 | -- "$MUX" --sock "$SOCK46" > "$OUT.rgpc" 2>&1 <<'EOF' | 6374 | -- "$MUX" --sock "$SOCK46" > "$OUT.rgpc" 2>&1 <<'EOF' |
| 6366 | expect \x1b[?1049h 15000 | 6375 | expect \x1b[?1049h 15000 |
| @@ -6453,7 +6462,8 @@ pipe_send 'exit 7\n' | |||
| 6453 | pipe_waitexit "exit semantics: the only tile's shell exited 7, and mux" 7 | 6462 | pipe_waitexit "exit semantics: the only tile's shell exited 7, and mux" 7 |
| 6454 | 6463 | ||
| 6455 | set +e | 6464 | set +e |
| 6456 | XDG_STATE_HOME="$XESTATE" timeout 90 "$PTYCLIENT" --cols 100 --rows 30 \ | 6465 | # tall: Ctrl-\ c creates a second tile at 100x30 (100 >= 60) |
| 6466 | XDG_STATE_HOME="$XESTATE" timeout 90 "$PTYCLIENT" --cols 40 --rows 30 \ | ||
| 6457 | --out "$OUT.xecap" --err "$OUT.xecap.err" \ | 6467 | --out "$OUT.xecap" --err "$OUT.xecap.err" \ |
| 6458 | -- "$MUX" --sock "$SOCK47" > "$OUT.xepc" 2>&1 <<'EOF' | 6468 | -- "$MUX" --sock "$SOCK47" > "$OUT.xepc" 2>&1 <<'EOF' |
| 6459 | expect \x1b[?1049h 15000 | 6469 | expect \x1b[?1049h 15000 |
| @@ -6977,7 +6987,8 @@ wait_grid "$SOCK50" "wmb-pin" "wall cluster: session b's marker" b | |||
| 6977 | # other way. A negative that cannot fail is not a test, so each carries the | 6987 | # other way. A negative that cannot fail is not a test, so each carries the |
| 6978 | # marker that MUST land in the focused session. | 6988 | # marker that MUST land in the focused session. |
| 6979 | set +e | 6989 | set +e |
| 6980 | timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wmcap3" --err "$OUT.wmcap3.err" -- \ | 6990 | # tall: two tiles at 100x30 trip the aspect rule (100 >= 60) |
| 6991 | timeout 60 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.wmcap3" --err "$OUT.wmcap3.err" -- \ | ||
| 6981 | "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc3" 2>&1 <<'EOF' | 6992 | "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc3" 2>&1 <<'EOF' |
| 6982 | expect wmb-pin 20000 | 6993 | expect wmb-pin 20000 |
| 6983 | settle 700 20000 | 6994 | settle 700 20000 |
| @@ -7006,7 +7017,8 @@ grep -q "wm-three" "$OUT.wmfa" && { | |||
| 7006 | # The other direction: focus b explicitly, then click a's content and watch | 7017 | # The other direction: focus b explicitly, then click a's content and watch |
| 7007 | # the focus come back to a. | 7018 | # the focus come back to a. |
| 7008 | set +e | 7019 | set +e |
| 7009 | timeout 60 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.wmcap4" --err "$OUT.wmcap4.err" -- \ | 7020 | # tall: two tiles at 100x30 trip the aspect rule (100 >= 60) |
| 7021 | timeout 60 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.wmcap4" --err "$OUT.wmcap4.err" -- \ | ||
| 7010 | "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc4" 2>&1 <<'EOF' | 7022 | "$MUX" wall "--sock $SOCK50#a" "--sock $SOCK50#b" > "$OUT.wmpc4" 2>&1 <<'EOF' |
| 7011 | expect wmb-pin 20000 | 7023 | expect wmb-pin 20000 |
| 7012 | settle 700 20000 | 7024 | settle 700 20000 |
| @@ -7324,7 +7336,8 @@ pipe_detach | |||
| 7324 | wait_grid "$SOCK52" "mc-pin" "cursor: session c's marker" c | 7336 | wait_grid "$SOCK52" "mc-pin" "cursor: session c's marker" c |
| 7325 | 7337 | ||
| 7326 | set +e | 7338 | set +e |
| 7327 | timeout 40 "$PTYCLIENT" --cols 100 --rows 30 --out "$OUT.cucap" --err "$OUT.cucap.err" -- \ | 7339 | # tall: three tiles at 100x30 trip the aspect rule (100 >= 60) |
| 7340 | timeout 40 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.cucap" --err "$OUT.cucap.err" -- \ | ||
| 7328 | "$MUX" wall --sock "$SOCK52#a" "--sock $SOCK52#b" "--sock $SOCK52#c" > "$OUT.cupc" 2>&1 <<'EOF' | 7341 | "$MUX" wall --sock "$SOCK52#a" "--sock $SOCK52#b" "--sock $SOCK52#c" > "$OUT.cupc" 2>&1 <<'EOF' |
| 7329 | expect mc-pin 20000 | 7342 | expect mc-pin 20000 |
| 7330 | settle 500 15000 | 7343 | settle 500 15000 |
| @@ -7397,7 +7410,8 @@ pipe_detach | |||
| 7397 | wait_grid "$SOCK53" "sb2nbr" "scrollback-rect: session b's pin" b | 7410 | wait_grid "$SOCK53" "sb2nbr" "scrollback-rect: session b's pin" b |
| 7398 | 7411 | ||
| 7399 | set +e | 7412 | set +e |
| 7400 | timeout 90 "$PTYCLIENT" --cols 92 --rows 30 --out "$OUT.sb2cap" --err "$OUT.sb2cap.err" -- \ | 7413 | # tall: two tiles at 92x30 trip the aspect rule (92 >= 60) |
| 7414 | timeout 90 "$PTYCLIENT" --cols 40 --rows 30 --out "$OUT.sb2cap" --err "$OUT.sb2cap.err" -- \ | ||
| 7401 | "$MUX" wall "--sock $SOCK53#a" "--sock $SOCK53#b" > "$OUT.sb2pc" 2>&1 <<'EOF' | 7415 | "$MUX" wall "--sock $SOCK53#a" "--sock $SOCK53#b" > "$OUT.sb2pc" 2>&1 <<'EOF' |
| 7402 | expect sb2nbr 20000 | 7416 | expect sb2nbr 20000 |
| 7403 | settle 700 20000 | 7417 | settle 700 20000 |