diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 00:01:57 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 23f430573efe4f6ef55b0180aa0d742ff4eca1d9 (patch) | |
| tree | da2157ba73d6abff4b1674541433294fb6c2daa9 /src/ninep/pane.zig | |
| parent | 9584b0682b45f17aaa42157540947904d867777a (diff) | |
| download | pardes-23f430573efe4f6ef55b0180aa0d742ff4eca1d9.tar.gz pardes-23f430573efe4f6ef55b0180aa0d742ff4eca1d9.zip | |
Make a pane's tag a Text edited with the body's own keys
The tag was a fixed buffer with its own one-line editor, a cursor measured
across the path, and a hijack of the body's mode while it was typed into.
It is now the pane's second Text: its own characters after the computed
prefix (path, dirty marker, PDF page), which is never stored in it, so a
rename or a dirty marker never moves its cursor. Normal and insert mode are
the body's, undo included; `:` moves the keyboard between body and tag, the
tag remembers its cursor and starts on Save, and executing a word from it
hands the keyboard back. The mouse still sees the prefix: a sweep selects
across it and a click on the path drafts a new name.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/pane.zig')
| -rw-r--r-- | src/ninep/pane.zig | 44 |
1 files changed, 29 insertions, 15 deletions
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig index 7e9cf7e7..282ae473 100644 --- a/src/ninep/pane.zig +++ b/src/ninep/pane.zig @@ -15,6 +15,7 @@ const addressing = @import("addr.zig"); const ctl = @import("ctl.zig"); const pty = @import("pty.zig"); const screen = @import("screen.zig"); +const limits = @import("../memory.zig").limits; const Pardes = pardes.Pardes; const Pane = pardes.Pane; @@ -384,7 +385,7 @@ pub fn write(p: *Pardes, req: Req, id: usize, pane: *Pane, file: PaneFile) Reply return switch (file) { .name => writeName(p, req, id, pane), .body => writeBody(p, req, id, pane), - .tag => writeTag(p, req, pane), + .tag => writeTag(req, pane), .ctl => ctl.writePane(p, req, pane), .addr, .dot, .limit => writeRange(req, pane, file), .data, .xdata => writeData(p, req, pane), @@ -417,15 +418,15 @@ fn writeBody(p: *Pardes, req: Req, id: usize, pane: *Pane) Reply { return .{ .tag = req.tag, .written = @intCast(take) }; } -fn writeTag(p: *Pardes, req: Req, pane: *Pane) Reply { +fn writeTag(req: Req, pane: *Pane) Reply { if (req.data.len == 0) return .{ .tag = req.tag, .written = 0 }; - p.seedTail(pane); - const room = pane.tag_tail.len - pane.tag_tail_len; + // The tag's own text grows by what is written, newlines and all: a tag + // is a text like any other (acme's tag file appends the same way). + const had = Pardes.curTail(pane).len; + const room = limits.max_tag_tail -| had; if (room == 0) return Reply.fail(req.tag, E.NOSPC); const take = wholeUtf8(req.data[0..@min(req.data.len, room)]); - @memcpy(pane.tag_tail[pane.tag_tail_len..][0..take], req.data[0..take]); - pane.tag_tail_len += take; - pane.tag_init = true; + pane.appendTag(req.data[0..take]) catch return Reply.fail(req.tag, E.NOMEM); return .{ .tag = req.tag, .written = @intCast(take) }; } @@ -553,8 +554,8 @@ pub fn nameBuffer(p: *Pardes, id: usize, full: []const u8, preserve_tag: bool) ! f.output = null; pane.clearCwd(); if (!preserve_tag) { - pane.tag_init = false; - pane.tag_tail_len = 0; + if (pane.tag.own) |own| p.gpa.free(own); + pane.tag.own = null; } f.saved_revision = f.revision -% 1; f.watch_after_save = fs.localPath(full) != null; @@ -606,8 +607,13 @@ pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status { setDot(pane, .{}); }, .tag => { - pane.tag_tail_len = 0; - pane.tag_init = true; + const empty = p.gpa.alloc(u8, 0) catch return .err; + if (pane.tag.own) |own| p.gpa.free(own); + pane.tag.own = empty; + pane.tag.cur_row = 0; + pane.tag.cur_col = 0; + pane.tag.vsel.active = false; + pane.tag.nsel = 0; }, .addr => pf.addr = .{}, .limit => pf.limit = null, @@ -740,12 +746,20 @@ test "tag reads the whole tag and writes append to the editable tail" { const before = rd(p, node, 0, 4096).bytes.len; const w = wr(p, node, " Mine"); try testing.expectEqual(@as(u32, 5), w.reply.written); - try testing.expect(std.mem.endsWith(u8, pane.tag_tail[0..pane.tag_tail_len], " Mine")); + try testing.expect(std.mem.endsWith(u8, Pardes.curTail(pane), " Mine")); const after = rd(p, node, 0, 4096); try testing.expectEqual(before + 5, after.bytes.len); try testing.expect(std.mem.endsWith(u8, after.bytes, " Mine")); - pane.tag_tail_len = pane.tag_tail.len; + // A tag is a text like the body: a newline written to it stays one, and + // the tag reads back taller by it. + try testing.expectEqual(@as(u32, 10), wr(p, node, "\nNext Line").reply.written); + try testing.expect(std.mem.endsWith(u8, Pardes.curTail(pane), " Mine\nNext Line")); + try testing.expect(std.mem.endsWith(u8, rd(p, node, 0, 4096).bytes, " Mine\nNext Line")); + + if (pane.tag.own) |own| p.gpa.free(own); + pane.tag.own = try p.gpa.alloc(u8, limits.max_tag_tail); + @memset(pane.tag.own.?, 'x'); try testing.expectEqual(E.NOSPC, wr(p, node, "x").errno()); } @@ -873,10 +887,10 @@ test "truncating the tag clears its editable tail" { const pane = p.panes[0].?; const tag = Node.of(pane.serial, .tag); _ = wr(p, tag, " Mine"); - try testing.expect(pane.tag_tail_len > 0); + try testing.expect(Pardes.curTail(pane).len > 0); const cleared = call(p, .{ .tag = 1, .op = .setattr, .node = tag, .truncate = true }); try testing.expectEqual(Status.ok, cleared.reply.status); - try testing.expectEqual(@as(usize, 0), pane.tag_tail_len); + try testing.expectEqualStrings("", pane.tag.own.?); try testing.expect(std.mem.indexOf(u8, rd(p, tag, 0, 4096).bytes, " Mine") == null); } |
