diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 13:09:12 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | 84fca2d6e5f498f2d4384712d49d727a25441d68 (patch) | |
| tree | 47a6d01ee65c5d631f59bee2f1efc5685ceb95f4 /src/ninep/events.zig | |
| parent | 047a2b50a7ac0162d7ef38f912d41c15a47479c7 (diff) | |
| download | pardes-84fca2d6e5f498f2d4384712d49d727a25441d68.tar.gz pardes-84fca2d6e5f498f2d4384712d49d727a25441d68.zip | |
A click that takes no text tells the event reader nothing, and click records are tested for their offsets
Dogfooding saw a body right-click reported as ML0 0 2 3 qty. On file panes
the offsets were right in every case tried here (a scratch made over 9P and
clicked while another pane is active, after a hover, scrolled, soft-wrapped,
tab-indented, inside parentheses); the test now holds them. The 0 0 form
is a terminal's body, which has no offsets to give (its body is a history
snapshot); since the event file now takes a record back whole, such a record
can still be written back and done, and the docs say so. What the test did
find: a click on whitespace sent an empty record, where acme's look3 and
execute return without a word on an empty expansion (look.c:37, exec.c:141).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/ninep/events.zig')
| -rw-r--r-- | src/ninep/events.zig | 56 |
1 files changed, 56 insertions, 0 deletions
diff --git a/src/ninep/events.zig b/src/ninep/events.zig index 3f36c65f..9ebb4bc1 100644 --- a/src/ninep/events.zig +++ b/src/ninep/events.zig @@ -921,3 +921,59 @@ test "opens of the log share the open records, and a closed one frees its record for (handles) |h| _ = call(p, .{ .tag = 5, .op = .release, .node = log, .handle = h }); for (p.fs.opens) |o| try testing.expect(o.node == 0); } + +test "a click in the body reports the offsets of the word it takes, so the record can be written back" { + const config = @import("../config.zig"); + const gpa = testing.allocator; + const text = "alpha qty beta\n qty\n\tqty\ndef f(qty): return qty.x\n" ++ "w" ** 100 ++ " qty\n"; + const p = try withFile(gpa, text); + defer p.deinit(); + // A scratch made over 9P, clicked while another pane has the keyboard. + const scratch = try th.newPane(p); + _ = wr(p, Node.of(scratch, .body), text); + for ([_]u32{ serialOf(p), scratch }) |serial| { + const id = p.paneBySerial(serial).?; + p.active = p.paneBySerial(if (serial == scratch) serialOf(p) else scratch).?; + const event = Node.of(serial, .event); + const h = call(p, .{ .tag = 1, .op = .open, .node = event }).reply.handle; + var frame = std.heap.ArenaAllocator.init(gpa); + defer frame.deinit(); + _ = try p.render(frame.allocator()); + const rect = p.rects[id]; + const x0 = rect.x + config.GUTTER + config.PREFIX_W; + const y0 = if (p.settings.tag_bottom) rect.y else rect.y + p.panes[id].?.tag_rows; + const tab: u16 = config.tab_width; + const Click = struct { x: u16, y: u16, look: []const u8, exec: []const u8, scroll: i32 = 0 }; + // Where the wrapped line's `qty` landed, as drawn. + const surface = try p.render(frame.allocator()); + var wrap_col: u16 = 0; + while (wrap_col < rect.w and !std.mem.eql(u8, surface.at(x0 + wrap_col, y0 + 5).grapheme(), "q")) wrap_col += 1; + for ([_]Click{ + .{ .x = x0 + 7, .y = y0, .look = "ML6 9 0 3 qty\n", .exec = "MX6 9 0 3 qty\n" }, + .{ .x = x0 + 5, .y = y0 + 1, .look = "ML19 22 0 3 qty\n", .exec = "MX19 22 0 3 qty\n" }, + .{ .x = x0 + tab + 1, .y = y0 + 2, .look = "ML24 27 0 3 qty\n", .exec = "MX24 27 0 3 qty\n" }, + .{ .x = x0 + 7, .y = y0 + 3, .look = "ML34 37 0 3 qty\n", .exec = "MX34 37 0 3 qty\n" }, + // Scrolled a line, the first row shows the second. + .{ .x = x0 + 5, .y = y0, .look = "ML19 22 0 3 qty\n", .exec = "MX19 22 0 3 qty\n", .scroll = 1 }, + // Wrapped: the word on the continuation row. + .{ .x = x0 + wrap_col, .y = y0 + 5, .look = "ML154 157 0 3 qty\n", .exec = "MX154 157 0 3 qty\n" }, + }) |c| for ([_]pardes.Mouse.Button{ config.look_button, config.exec_button }, [_][]const u8{ c.look, c.exec }) |button, want| { + // The pointer rests on the word first, as a hand's does, and + // the hover preview comes up under it. + p.panes[id].?.scrollBy(c.scroll); + _ = try p.render(frame.allocator()); + p.update(.{ .mouse = .{ .button = .none, .kind = .motion, .col = c.x, .row = c.y } }); + for (0..200) |_| p.update(.tick); + p.update(.{ .mouse = .{ .button = button, .kind = .press, .col = c.x, .row = c.y } }); + p.update(.{ .mouse = .{ .button = button, .kind = .release, .col = c.x, .row = c.y } }); + try testing.expectEqualStrings(want, call(p, .{ .tag = 2, .op = .read, .node = event, .handle = h, .size = 256 }).bytes); + p.panes[id].?.scrollBy(-c.scroll); + }; + // A click on nothing expands to nothing, and acme then says + // nothing (look.c:37, exec.c:141). + p.update(.{ .mouse = .{ .button = config.look_button, .kind = .press, .col = x0 + 60, .row = y0 + 1 } }); + p.update(.{ .mouse = .{ .button = config.look_button, .kind = .release, .col = x0 + 60, .row = y0 + 1 } }); + try testing.expectEqual(Status.again, call(p, .{ .tag = 2, .op = .read, .node = event, .handle = h, .size = 256 }).reply.status); + _ = call(p, .{ .tag = 3, .op = .release, .node = event, .handle = h }); + } +} |
