summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 16:43:44 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit319325b74f3c97cad9988faebc7c6afda26cad08 (patch)
tree8b12f6862c0906fbc39f1d5b9ac100688833978d /src
parent79fb1b77b8c9993fb36c250daf36970807f767c9 (diff)
downloadpardes-319325b74f3c97cad9988faebc7c6afda26cad08.tar.gz
pardes-319325b74f3c97cad9988faebc7c6afda26cad08.zip
A 9P write scrolls only the view: dot moves as acme's textinsert moves it, never to the end of the write
writeData, flushBatch and writeBody showed what they wrote with showOffset, which put the cursor at the write's end. The cursor is dot's head, so a data write or a body append moved dot there, and a `sel` write after it replaced the wrong text. showOffset now scrolls the view to the offset and leaves the cursor, pinned, where it was. Dot shifts only by the rule it already followed, acme's: an insert before dot shifts it, one at its start or inside it grows it, one at its end or after it leaves it. The test covers each case, then a body append followed by a sel write. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/ninep/pane.zig45
1 files changed, 43 insertions, 2 deletions
diff --git a/src/ninep/pane.zig b/src/ninep/pane.zig
index 7526075b..fef0f85f 100644
--- a/src/ninep/pane.zig
+++ b/src/ninep/pane.zig
@@ -231,14 +231,22 @@ pub fn setDot(pane: *Pane, r: State.Range) void {
pane.ensureCursorVisible();
}
+/// Scrolls `off` into view and nothing else: the cursor is dot's head, and
+/// a write shown is not a write that moves dot (acme's textinsert leaves
+/// dot where its rule puts it; only the view follows the text).
pub fn showOffset(pane: *Pane, off: usize) void {
const text = bodyOf(pane);
const c = positionAt(pane, text, @min(off, text.len));
+ // Where dot is now, kept: pinned, so the view moving under it does not
+ // move it to the view's top (pinCursor).
+ pane.pinCursor();
+ const row = pane.body.cur_row;
+ const col = pane.body.cur_col;
pane.body.cur_row = @intCast(c.row);
pane.body.cur_col = @intCast(c.col);
- pane.body.cur_pinned = true;
- pane.body.sticky_col = -1;
pane.ensureCursorVisible();
+ pane.body.cur_row = row;
+ pane.body.cur_col = col;
}
pub fn clampAddr(pf: *State, len: usize) void {
@@ -1350,6 +1358,39 @@ test "after a failed address, one that goes from it says there is none" {
try testing.expectEqual(Status.ok, wr(p, addr, ".").reply.status);
}
+test "a 9P write moves dot only as acme's textinsert does, and scrolls only the view" {
+ const Case = struct { at: u32, want: State.Range };
+ // dot is `cd` of `abcdef\n` (2..4); two bytes go in at `at`.
+ for ([_]Case{
+ .{ .at = 0, .want = .{ .q0 = 4, .q1 = 6 } }, // before: shifted
+ .{ .at = 2, .want = .{ .q0 = 2, .q1 = 6 } }, // at its start: grown
+ .{ .at = 3, .want = .{ .q0 = 2, .q1 = 6 } }, // inside: grown
+ .{ .at = 4, .want = .{ .q0 = 2, .q1 = 4 } }, // at its end: left
+ .{ .at = 6, .want = .{ .q0 = 2, .q1 = 4 } }, // after: left
+ }) |c| {
+ const p = try withFile(testing.allocator, "abcdef\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ const pane = p.panes[p.paneBySerial(serial).?].?;
+ setDot(pane, .{ .q0 = 2, .q1 = 4 });
+ var addr: [16]u8 = undefined;
+ _ = wr(p, Node.of(serial, .addr), try std.fmt.bufPrint(&addr, "#{d}", .{c.at}));
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .data), "XX").reply.status);
+ try testing.expectEqual(c.want, dotOf(pane));
+ }
+ // A body append leaves dot, and a sel write after it replaces dot, not
+ // the text appended.
+ const p = try withFile(testing.allocator, "abcdef\n");
+ defer p.deinit();
+ const serial = serialOf(p);
+ const pane = p.panes[p.paneBySerial(serial).?].?;
+ setDot(pane, .{ .q0 = 2, .q1 = 4 });
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .body), "more\n").reply.status);
+ try testing.expectEqual(State.Range{ .q0 = 2, .q1 = 4 }, dotOf(pane));
+ try testing.expectEqual(Status.ok, wr(p, Node.of(serial, .sel), "YY").reply.status);
+ try testing.expectEqualStrings("abYYef\nmore\n", fileOf(pane).?.content);
+}
+
test "a write of two lines to name is refused EINVAL, on a held open or not" {
const p = try withFile(testing.allocator, "x\n");
defer p.deinit();