summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-06 13:10:53 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commit04cd5a2b41efd6f39c3ba15c32e60843e2c6485d (patch)
tree55e82e6907041314ebd32bb29ea6844a9cd48c45 /src
parentde4def3548a6729b0dfd2120495a61beef8c8c2c (diff)
downloadpardes-04cd5a2b41efd6f39c3ba15c32e60843e2c6485d.tar.gz
pardes-04cd5a2b41efd6f39c3ba15c32e60843e2c6485d.zip
layout: every pane open/close resizes exactly ONE sibling — never the whole column. Buggy paths fixed: Alt-n inserted a weight-1 shell (whole column reflowed) — now splitBelow the active pane; stackDocLeft (file/image/tutor open with no doc yet) gave the doc a fair share (every pane shrank) — now it halves only the old top pane's slot; ttyForDir (execute from a file pane) appended a weight-1 shell to the rightmost column — now splitBelow its bottom pane; Alt-c moved a pane out without absorbVWeight (all old siblings grew) — now the sibling above absorbs first. Underneath: splitBelow/absorbVWeight/stackDocLeft snap the whole column's weights to on-screen row counts, because computeGeom's per-pane round(avail*w/vsum) let fractional weights jiggle bystanders by a row; evictLonePristineTty recomputes geometry first (a just-stacked doc has no rect). New snap tests layout-open + layout-close cover Alt-n, doc stacking, ttyForDir, Alt-c, tag Del, pty EOF, and column-width handoff. Six goldens regenerated (fileedit, images, look-file, syntax, tutor, windowops): they had frozen the fair-share/append reflow this change removes; every diff is only the doc/split pane sizing, with untouched panes' boundaries restored bit-identical (e.g. bottom pane tag back at its boot row 17 instead of pushed to 22).
Diffstat (limited to 'src')
-rw-r--r--src/pardes.zig59
1 files changed, 40 insertions, 19 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index c4893b25..13b159aa 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -1145,12 +1145,14 @@ pub const Pardes = struct {
nt.greet = true;
const f = p.layoutFindTerm(p.active).?;
p.layoutInsert(f.col, f.idx + 1, free);
+ p.splitBelow(p.active, nt);
p.active = free;
return;
}
if (key.alt and !key.ctrl and key.cp == 'c') {
const f = p.layoutFindTerm(p.active).?;
if (p.ncol < MAX_COLS and p.col_n[f.col] > 1) {
+ p.absorbVWeight(p.active);
p.layoutRemove(p.active);
p.layoutAppendColumn(p.active);
}
@@ -2433,29 +2435,47 @@ pub const Pardes = struct {
p.ncol += 1;
}
- /// Hand a dying pane's vertical weight to ONE sibling (the pane above, or
- /// below for the topmost) so the rest of the column keeps its sizes — the
- /// deletion mirror of splitBelow. Call while `id` is still in the layout.
+ /// Snap every pane in column `c` to its on-screen row count so later
+ /// weight edits move ONLY the panes they name: computeGeom rounds
+ /// round(avail*w/vsum) per pane, and with fractional weights a split or
+ /// absorb elsewhere in the column can jiggle a bystander by a row.
+ fn snapColWeights(p: *Pardes, c: usize) void {
+ for (0..p.col_n[c]) |k| {
+ const pid = p.col_terms[c][k];
+ if (p.panes[pid]) |pp| pp.vweight = @floatFromInt(@max(1, p.rects[pid].h));
+ }
+ }
+
+ /// Hand a dying pane's rows to ONE sibling (the pane above, or below for
+ /// the topmost) so the rest of the column keeps its sizes bit-identical —
+ /// the deletion mirror of splitBelow. Call while `id` is still in the
+ /// layout, with rects current.
fn absorbVWeight(p: *Pardes, id: usize) void {
const f = p.layoutFindTerm(id) orelse return;
if (p.col_n[f.col] <= 1) return;
- const gone = p.panes[id] orelse return;
+ p.snapColWeights(f.col);
const sib = if (f.idx > 0) p.col_terms[f.col][f.idx - 1] else p.col_terms[f.col][f.idx + 1];
- if (p.panes[sib]) |s| s.vweight += gone.vweight;
+ if (p.panes[sib]) |s| s.vweight += @as(f32, @floatFromInt(@max(1, p.rects[id].h)));
}
/// Open a window BELOW `src` (acme-style) without rebalancing the column:
/// shrink ONLY src to its content height (cursor row kept visible) and hand
- /// the freed rows to `nw` — together they fill src's old slot.
+ /// the freed rows to `nw` — together they fill src's old slot and the other
+ /// panes keep their sizes bit-identical (weights snap to row counts).
fn splitBelow(p: *Pardes, src_id: usize, nw: *Pane) void {
const src = p.panes[src_id] orelse return;
const src_h = p.rects[src_id].h;
const body: u16 = if (src_h > BOX_H) src_h - BOX_H else 1;
const cur: u16 = if (src.file != null) body / 2 else @as(u16, @intCast(src.vt.screens.active.cursor.y)) + 1;
const keep = std.math.clamp(cur, 1, @max(1, body -| 1));
- const w = src.vweight;
- src.vweight = w * @as(f32, @floatFromInt(BOX_H + keep)) / @as(f32, @floatFromInt(src_h));
- nw.vweight = w - src.vweight;
+ if (p.layoutFindTerm(src_id)) |f| for (0..p.col_n[f.col]) |k| {
+ const pid = p.col_terms[f.col][k];
+ if (p.panes[pid]) |pp| if (pp != nw) {
+ pp.vweight = @floatFromInt(@max(1, p.rects[pid].h));
+ };
+ };
+ src.vweight = @floatFromInt(BOX_H + keep);
+ nw.vweight = @floatFromInt(@max(1, src_h -| (BOX_H + keep)));
}
/// a shell nobody has touched: no typing, cursor on the first prompt line,
@@ -2482,6 +2502,7 @@ pub const Pardes = struct {
if (n_tty != 1) return;
const tt = p.panes[tty_id] orelse return;
if (!isPristineTty(tt)) return;
+ p.computeGeom(); // a just-stacked doc has no rect yet; absorb snaps to rows
p.absorbVWeight(tty_id);
p.layoutRemove(tty_id);
p.deinitPane(tt);
@@ -2501,6 +2522,7 @@ pub const Pardes = struct {
const nt = p.newShell(free, dir) catch return null;
nt.greet = false;
const rc = if (p.ncol > 0) p.ncol - 1 else 0;
+ if (p.col_n[rc] > 0) p.splitBelow(p.col_terms[rc][p.col_n[rc] - 1], nt);
p.layoutInsert(rc, p.col_n[rc], free);
return free;
}
@@ -2558,18 +2580,17 @@ pub const Pardes = struct {
}
/// stack a fresh doc pane at the top of the LEFT column (acme convention:
- /// files left, terminals right) with a fair share, then evict a leftover
- /// pristine shell
+ /// files left, terminals right), halving ONLY the old top pane's slot so
+ /// the rest of the column keeps its sizes; then evict a leftover pristine
+ /// shell
fn stackDocLeft(p: *Pardes, free: usize, nt: *Pane) void {
const lc = 0;
- const ln = p.col_n[lc];
- if (ln > 0) {
- var vsum: f32 = 0;
- for (0..ln) |i| if (p.panes[p.col_terms[lc][i]]) |pp| {
- vsum += pp.vweight;
- };
- nt.vweight = vsum / @as(f32, @floatFromInt(ln));
- }
+ if (p.col_n[lc] > 0) if (p.panes[p.col_terms[lc][0]]) |top| {
+ p.snapColWeights(lc);
+ const h = p.rects[p.col_terms[lc][0]].h;
+ nt.vweight = @floatFromInt(@max(1, h / 2));
+ top.vweight = @floatFromInt(@max(1, h -| h / 2));
+ };
p.layoutInsert(lc, 0, free);
p.active = free;
p.evictLonePristineTty(lc, free);