diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-29 06:43:39 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:16 -0300 |
| commit | 96383331f09e8af3fa24f767b3c56863309723b8 (patch) | |
| tree | 12aa88b5e4832623de2c3a413b7b237f6ef34add /src | |
| parent | 35e865eba278a1ef48a6ce160767d652a9c54ae8 (diff) | |
| download | pardes-96383331f09e8af3fa24f767b3c56863309723b8.tar.gz pardes-96383331f09e8af3fa24f767b3c56863309723b8.zip | |
A terminal keeps its tag and two body rows through drags, squeezes and a smaller window
A terminal of one body row loses its prompt's mark and reads busy for
ever. Placement already refused to make one that short; now layout never
leaves one there either: a grip drag stops at the floor, and a column
squeezed by weights or by the window takes the rows from its other panes.
Text panes keep acme's way and can still be dragged to their tag, since
acme has no terminals to follow. The shrinktag, splitparent and cornerdrag
goldens are re-recorded for it, shrinktag's re-grab moved to where the
grip now is.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/layout.zig | 69 | ||||
| -rw-r--r-- | src/mouse.zig | 9 | ||||
| -rw-r--r-- | src/pardes.zig | 6 |
3 files changed, 69 insertions, 15 deletions
diff --git a/src/layout.zig b/src/layout.zig index 644054ed..cbf0fd96 100644 --- a/src/layout.zig +++ b/src/layout.zig @@ -274,7 +274,11 @@ pub fn applyRowSplit(p: *Pardes, cc: usize, k: usize, cur_y: u16) void { const rb = p.rects[p.col_panes[cc][k + 1]]; const combined: f32 = @floatFromInt(ra.h + rb.h); var nt: f32 = @floatFromInt(if (p.settings.tag_bottom) cur_y -| ra.y else (cur_y + 1) -| ra.y); - nt = std.math.clamp(nt, @as(f32, BOX_H), @max(@as(f32, BOX_H), combined - BOX_H)); + // A terminal keeps its tag and two body rows (floorRows); a text pane + // gives up all but its tag, as acme's do. + const lo: f32 = @floatFromInt(@max(BOX_H, floorRows(p, p.col_panes[cc][k]))); + const hi = combined - @as(f32, @floatFromInt(@max(BOX_H, floorRows(p, p.col_panes[cc][k + 1])))); + nt = std.math.clamp(nt, @min(lo, @max(@as(f32, BOX_H), hi)), @max(@as(f32, BOX_H), hi)); if (combined <= 0) return; // Dragging a folded tag open is an explicit resize, so switch this pair // back to visible-height weights without consuming other folded weights. @@ -794,16 +798,62 @@ pub fn compute(p: *Pardes) void { if (vsum <= 0) vsum = 1; var y: u16 = p.topBarHeight() + p.columnBarHeight(); + const top = y; const avail_h = p.screen_h -| y; - for (0..p.col_n[c]) |k| { + var hs: [MAX_PANES]u16 = undefined; + const n = p.col_n[c]; + for (0..n) |k| { const id = p.col_panes[c][k]; - const pane = p.panes[id] orelse continue; - const lastk = k + 1 == p.col_n[c]; + const pane = p.panes[id] orelse { + hs[k] = 0; + continue; + }; + const lastk = k + 1 == n; const fh = @as(f32, @floatFromInt(avail_h)) * pane.vweight / vsum; const room = p.screen_h -| y; - const ch: u16 = if (lastk) room else @min(room, @max(1, @as(u16, @intFromFloat(@round(fh))))); - p.rects[id] = .{ .x = x, .y = y, .w = cw, .h = ch }; - y +|= ch; + hs[k] = if (lastk) room else @min(room, @max(1, @as(u16, @intFromFloat(@round(fh))))); + y +|= hs[k]; + } + keepTerminalFloors(p, c, hs[0..n]); + y = top; + for (0..n) |k| { + const id = p.col_panes[c][k]; + if (p.panes[id] == null) continue; + p.rects[id] = .{ .x = x, .y = y, .w = cw, .h = hs[k] }; + y +|= hs[k]; + } + } +} + +/// The fewest rows a pane is laid out with: a terminal keeps its tag and +/// `min_body_rows` (a shorter one loses its prompt and reads busy for +/// ever), a text pane may shrink to its tag, as acme's windows do. +fn floorRows(p: *Pardes, id: usize) u16 { + const pane = p.panes[id] orelse return 0; + return if (pane.isTerminal()) @max(1, pane.tag_rows) + min_body_rows else 1; +} + +/// Raises each terminal in column `c` below its floor to it, a row at a +/// time from whichever pane has the most rows above its own floor. Where +/// the column has not the rows, the terminal keeps what it could get. +// ponytail: a row per step, O(panes x rows); a column holds few of both. +fn keepTerminalFloors(p: *Pardes, c: usize, hs: []u16) void { + for (hs, 0..) |*h, k| { + const floor = floorRows(p, p.col_panes[c][k]); + while (h.* < floor) { + var giver: ?usize = null; + var spare: u16 = 0; + for (hs, 0..) |other, j| { + if (j == k) continue; + const extra = other -| floorRows(p, p.col_panes[c][j]); + if (extra > spare) { + spare = extra; + giver = j; + } + } + const g = giver orelse break; + hs[g] -= 1; + h.* += 1; } } } @@ -911,9 +961,10 @@ test "collapse local transfers remain bounded through repeated tiny window resiz test "collapse expansion is limited to space available in one neighbor" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 80, .rows = 42 }); defer p.deinit(); + // Text panes: a terminal would keep its tag and two rows (floorRows). for (1..4) |id| { - _ = try p.newShell(id, ""); - insert(p, 0, id, id); + p.newScratchBelow(0); + try std.testing.expectEqual(id, p.col_panes[0][id]); } compute(p); toggleCollapse(p, 2); diff --git a/src/mouse.zig b/src/mouse.zig index cb5512ad..9eefa9a1 100644 --- a/src/mouse.zig +++ b/src/mouse.zig @@ -109,20 +109,23 @@ test "a pane's last row of text is no resize handle; the rule over the next pane try std.testing.expect(nudgeRightSeam(p, 2) == seam + 2); } -test "a pane's grip dragged up or down its own column moves its top, down to a tag alone above" { +test "a pane's grip dragged up or down its own column moves its top, down to the floor of the pane above" { if (platform == .web) return; const p = try cornerFixture(std.testing.allocator); defer p.deinit(); const upper = p.col_panes[1][0]; const lower = p.col_panes[1][1]; const grip_x = p.rects[lower].x; - // Up, past the pane above's first row: it keeps one row, its tag. + // Up, past the pane above's first row: a text pane keeps its tag, a + // terminal its tag and two rows (layout.floorRows). const top = p.rects[upper].y; + const above = p.panes[upper].?; + const floor: u16 = if (above.isTerminal()) above.tag_rows + layout.min_body_rows else 1; p.update(.{ .mouse = .{ .button = .left, .kind = .press, .col = grip_x, .row = p.rects[lower].y } }); p.update(.{ .mouse = .{ .button = .left, .kind = .drag, .col = grip_x, .row = top } }); p.update(.{ .mouse = .{ .button = .left, .kind = .release, .col = grip_x, .row = top } }); p.sync(); - try std.testing.expectEqual(@as(u16, 1), p.rects[upper].h); + try std.testing.expectEqual(floor, p.rects[upper].h); try std.testing.expectEqual(lower, p.col_panes[1][1]); // Down, inside itself: its top follows, the pane above taking the rows. const to = p.rects[lower].y + 6; diff --git a/src/pardes.zig b/src/pardes.zig index 8e9d3c7e..98b8819c 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -6598,9 +6598,9 @@ pub const Pardes = struct { pane.tag_top = @intCast(@min(top, lines - pane.tag_rows)); } else pane.tag_top = 0; const cols = @max(1, r.w -| config.GUTTER); - // The tag steals the top rows. The pty is what the pane shows, - // even one row dragged short: a taller pty would put its cursor - // below the pane (only winsize keeps two, ctlfs.pty). + // The tag steals the top rows. The pty is what the pane shows + // (layout keeps a terminal two body rows where the window has + // them): a taller pty would put its cursor below the pane. const rows = @max(1, r.h -| pane.tag_rows); // A terminal's pty follows its pane down to a row, whatever the // resize; only a folded one keeps the size it had, and one not |
