summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 10:18:59 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commitd9c6786e19567878e3c89ae212296ce941484096 (patch)
tree23b973692fc258bd7910cadf135d06776015da6f /src
parent5b27be228f5961dee8727bec7480f375dfb588ed (diff)
downloadpardes-d9c6786e19567878e3c89ae212296ce941484096.tar.gz
pardes-d9c6786e19567878e3c89ae212296ce941484096.zip
Truncating data or xdata deletes only the addressed range, not the whole body
A shell's echo NEW > data opens data with OTRUNC, and truncate() spliced the whole body away before the write replaced the addressed range: a five-byte replacement erased a buffer. data and xdata hold what addr names, so truncating one now deletes that range and nothing else; > on data replaces the range and : > data deletes it, and only truncating body clears the buffer. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/fs-help.txt2
-rw-r--r--src/ninep/pane.zig32
2 files changed, 30 insertions, 4 deletions
diff --git a/src/fs-help.txt b/src/fs-help.txt
index b6812318..3f7357d4 100644
--- a/src/fs-help.txt
+++ b/src/fs-help.txt
@@ -38,7 +38,7 @@ Pitfalls, one each:
rmdir closes a pane even when it is dirty; index and pane/<n>/dirty show the flag.
addr, dot and limit read the same pair of offsets they take, so cp between them works.
dirty, mark and scroll read "0" or "1" and take "0" or "1"; truncating limit lifts it.
- Writing body appends; open it with truncation to replace; writes are whole UTF-8 sequences.
+ body: >> appends, > replaces it all. data: > replaces (and : > deletes) just the addr range.
tag reads path++text; writes append to the text (newlines too); truncating clears it, and dot or addr.
A terminal's body is a history snapshot frozen per open; pty/data is the live stream.
A failing command is reported in the editor and in log, not as a write error; a bad line fails the write.
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index 1e549107..822e0704 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -568,16 +568,27 @@ pub fn appendErrors(p: *Pardes, id: usize, text: []const u8) ?usize {
}
/// Truncation empties whatever the file holds: the body, the tag's editable
-/// tail (acme's `cleartag`), a range. Every other setattr just answers fresh
-/// attributes.
+/// tail (acme's `cleartag`), a range. `data` and `xdata` hold what `addr`
+/// addresses, so truncating one deletes that range and no more, and a
+/// shell's `echo new > data` replaces it. Every other setattr just answers
+/// fresh attributes.
pub fn truncate(p: *Pardes, pane: *Pane, file: PaneFile) tree.Status {
const pf = &pane.fs;
switch (file) {
- .body, .data, .xdata => if (fileOf(pane) != null) {
+ .body => if (fileOf(pane) != null) {
_ = spliceBody(p, pane, 0, bodyOf(pane).len, "") orelse return .err;
pf.addr = .{};
setDot(pane, .{});
},
+ .data, .xdata => if (fileOf(pane) != null) {
+ clampAddr(pf, bodyOf(pane).len);
+ const q0 = pf.addr.q0;
+ const q1 = @max(q0, pf.addr.q1);
+ const before = dotOf(pane);
+ _ = spliceBody(p, pane, q0, q1, "") orelse return .err;
+ setDot(pane, .{ .q0 = shiftOne(before.q0, q0, q1 - q0, 0), .q1 = shiftOne(before.q1, q0, q1 - q0, 0) });
+ pf.addr = .{ .q0 = q0, .q1 = q0 };
+ },
.tag => {
const empty = p.gpa.alloc(u8, 0) catch return .err;
// An edit like a keyboard one: `u` in the tag brings it back.
@@ -1036,6 +1047,21 @@ test "errors append to one +Errors buffer per directory" {
try testing.expectEqual(@as(usize, 1), found);
}
+test "truncating data deletes only the addressed range, so > data replaces it" {
+ const p = try withFile(testing.allocator, "hello world\nsecond line\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ for ([_]PaneFile{ .data, .xdata }) |file| {
+ _ = call(p, .{ .tag = 1, .op = .setattr, .node = Node.of(serial, .body), .truncate = true });
+ _ = wr(p, Node.of(serial, .body), "hello world\nsecond line\n");
+ _ = wr(p, Node.of(serial, .addr), "#0,#5");
+ try testing.expectEqual(Status.ok, call(p, .{ .tag = 2, .op = .setattr, .node = Node.of(serial, file), .truncate = true }).reply.status);
+ try testing.expectEqualStrings(" world\nsecond line\n", p.panes[0].?.file.?.content);
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, file), "HOWDY").reply.status);
+ try testing.expectEqualStrings("HOWDY world\nsecond line\n", p.panes[0].?.file.?.content);
+ }
+}
+
test "setattr truncation empties the body and answers fresh attributes" {
const gpa = testing.allocator;
const p = try withFile(gpa, "one\ntwo\n");