summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-29 05:20:13 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:16 -0300
commit5002c612298fe645791218059a69d2bc80a42d2d (patch)
tree1c3ea64ea9b18f8c4f3f67508520f41fd758119e
parentc27e38eb3e97fd505255ee4b42d32ddc77c58ced (diff)
downloadpardes-5002c612298fe645791218059a69d2bc80a42d2d.tar.gz
pardes-5002c612298fe645791218059a69d2bc80a42d2d.zip
Newcol at the column cap says so, and a desktop session holds 16 columns
Newcol with every column slot taken, or a column too narrow to split, did nothing and said nothing. It now fails (a ctl write with it) with no space for a column: 16 max (ENOSPC through 9ns), or that the column is too narrow. MAX_COLS is 16 on a desktop, 6 on the board; each column costs about 8 KB of core state (the Pardes struct grows from 1.47 MB to 1.56 MB), most of it its tag's text. fs.md and the skill give the cap. Co-Authored-By: Claude Opus 5.5 <[email protected]>
-rw-r--r--.agents/skills/pardes-9p/SKILL.md2
-rw-r--r--docs/fs.md3
-rw-r--r--src/ninep/ctl.zig22
-rw-r--r--src/pardes.zig13
4 files changed, 36 insertions, 4 deletions
diff --git a/.agents/skills/pardes-9p/SKILL.md b/.agents/skills/pardes-9p/SKILL.md
index c9b1be23..26f3ff19 100644
--- a/.agents/skills/pardes-9p/SKILL.md
+++ b/.agents/skills/pardes-9p/SKILL.md
@@ -87,7 +87,7 @@ $m/ctl the settings, one a line as a write takes them; write a setting or
a pane's builtins (Del, Save f, Collapse, which folds that pane, and the
column word Delcol, which closes that pane's column) go to $m/pane/<n>/ctl
$m/commands every builtin: `Word`, `Word arg`, then `root` or `pane` (which ctl takes it)
-$m/layout one line per column: serial index x width current|notcurrent empty|full pane-serials...; active <serial>
+$m/layout one line per column (16 columns at most: Newcol past that fails, no space for a column): serial index x width current|notcurrent empty|full pane-serials...; active <serial>
$m/tag the workspace tag (> replaces, >> appends, one line); $m/col/<serial>/tag a column's (serials stay, as panes' do)
$m/tagexec a word as a click in the workspace tag (pane/<n>/tagexec: in that pane's tag); every exec
file reads back the serials its last write touched
diff --git a/docs/fs.md b/docs/fs.md
index ce33ac7c..09738816 100644
--- a/docs/fs.md
+++ b/docs/fs.md
@@ -112,7 +112,8 @@ Existing Plan9port/v9fs clients need a userspace bridge for QUIC.
/commands every builtin: word, `arg` if it takes one, `root`, `pane` or `both` (the ctl that takes it:
Edit is both, at the active pane from the root; a pane's word such as Undo or Msg
is refused at the root), a setting's values, then ` -- ` and what it does
-/layout one line per column, left to right: serial index x width current|notcurrent
+/layout one line per column (16 at most; the board 6; Newcol past that fails,
+ `no space for a column: 16 max`, ENOSPC), left to right: serial index x width current|notcurrent
(the column with the keyboard now) empty|full pane-serials...; then active
<serial>: acme's activecol, which the keyboard leaving for another column's
tag does not move, so the two can differ -- the active column, where
diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig
index 307921b9..3282c743 100644
--- a/src/ninep/ctl.zig
+++ b/src/ninep/ctl.zig
@@ -1721,3 +1721,25 @@ test "each word's /commands description is its own doc comment's first sentence,
try testing.expect(std.mem.indexOf(u8, source[block_start..at], head) != null);
}
}
+
+test "Newcol at the column cap fails the write and says the cap" {
+ const p = try withFile(testing.allocator, "x\n");
+ defer p.deinit();
+ p.update(.{ .resize = .{ .cols = 4000, .rows = 30 } });
+ const root_ctl = @intFromEnum(tree.TopFile.ctl);
+ while (p.ncol < pardes.MAX_COLS) {
+ // From the widest column's tag, so no column gets too narrow first.
+ p.sync();
+ var widest: usize = 0;
+ for (0..p.ncol) |c| if (p.col_w[c] > p.col_w[widest]) {
+ widest = c;
+ };
+ pardes.tagline.enterHeader(p, widest);
+ try testing.expectEqual(Status.ok, wr(p, root_ctl, "Newcol\n").reply.status);
+ }
+ const refused = wr(p, root_ctl, "Newcol\n");
+ try testing.expectEqual(Status.err, refused.reply.status);
+ var want: [64]u8 = undefined;
+ try testing.expect(std.mem.indexOf(u8, refused.reply.ename, try std.fmt.bufPrint(&want, "no space for a column: {d} max", .{pardes.MAX_COLS})) != null);
+ try testing.expect(th.logHas(p, "no space for a column"));
+}
diff --git a/src/pardes.zig b/src/pardes.zig
index b488ec1c..9f192004 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -267,7 +267,9 @@ pub const MAX_TAG_ROWS = 8;
pub const MAX_TAG_LAYERS = MAX_PANES + MAX_COLS + 1 + MAX_PANES * Pane.Notices.max;
pub const NOTICE_LAYER_BASE = MAX_PANES + MAX_COLS + 1;
pub const PDF_PAGE_GAP_PX = panes.Pdf.page_gap_px;
-pub const MAX_COLS = 6;
+/// Columns a session holds: 16 on a desktop (each costs about 8 KB of state,
+/// a column tag being most of it), 6 on the board.
+pub const MAX_COLS = if (platform == .esp32p4) 6 else 16;
const column_weight_unit = layout.column_weight_unit;
const max_column_weight = layout.max_column_weight;
pub const MAX_JUMPS = 64;
@@ -4849,7 +4851,14 @@ pub const Pardes = struct {
/// on its tag, for the next pane made to fill.
pub fn newColumn(p: *Pardes, from_id: usize) void {
const col = p.keyboardColumn(from_id) orelse return;
- const c = layout.insertColumn(p, col, false) orelse return;
+ // Said, not silently nothing: "no space" is 9ns's word for ENOSPC.
+ const c = layout.insertColumn(p, col, false) orelse {
+ var said: [96]u8 = undefined;
+ return p.reportFailure(from_id, if (p.ncol >= MAX_COLS)
+ std.fmt.bufPrint(&said, "Newcol: no space for a column: {d} max", .{MAX_COLS}) catch "Newcol: no space for a column"
+ else
+ "Newcol: no space for a column: this one is too narrow to split");
+ };
tagline.enterHeader(p, c);
}