diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/exec.zig | 12 | ||||
| -rw-r--r-- | src/look.zig | 62 | ||||
| -rw-r--r-- | src/mouse.zig | 18 | ||||
| -rw-r--r-- | src/pardes.zig | 16 |
4 files changed, 96 insertions, 12 deletions
diff --git a/src/exec.zig b/src/exec.zig index 30a1a894..9d1b81d5 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -108,14 +108,14 @@ pub const PointerOperand = struct { }; pub fn pointerOperand(p: *Pardes, pane: *Pane, clicked: Pane.Sel) PointerOperand { - return operandAt(p, pane, clicked, true); + return operandAt(p, pane, clicked, true, true); } /// The body's explicit selection, else what a look at its cursor takes. pub fn currentOperand(p: *Pardes, pane: *Pane) ?[]const u8 { if (pane.file == null and !pane.isTerminal()) return null; if ((pane.body.vsel.active and pane.body.vsel.explicit) or pane.body.msel.active) return edit.currentSelText(p, pane); - return operandAt(p, pane, look.cursorWordSel(p, pane), false).text; + return operandAt(p, pane, look.cursorWordSel(p, pane), false, true).text; } /// What a look or an exec at `clicked` takes: the ONE expansion every @@ -123,8 +123,10 @@ pub fn currentOperand(p: *Pardes, pane: *Pane) ?[]const u8 { /// Look), so a diff line, a hunk line's code word and a file word expand /// alike however they are reached (look.zig's "every entry point" test). /// `borrow`: a gesture inside a kept or modal selection takes it; the -/// keyboard has said which selection it means already. -pub fn operandAt(p: *Pardes, pane: *Pane, clicked: Pane.Sel, borrow: bool) PointerOperand { +/// keyboard has said which selection it means already. `for_look`: only a +/// look takes a diff line's address; an exec there takes the word, as +/// anywhere else. +pub fn operandAt(p: *Pardes, pane: *Pane, clicked: Pane.Sel, borrow: bool, for_look: bool) PointerOperand { const visible = clicked.r0 - @as(i32, pane.tag_rows); const wrapped = pane.wrapAt(visible); const raw = pane.isTerminal() and pane.body.mode == .tty and clicked.r0 >= pane.tag_rows; @@ -193,7 +195,7 @@ pub fn operandAt(p: *Pardes, pane: *Pane, clicked: Pane.Sel, borrow: bool) Point // look from there. A hunk line's code is words, looked at as ever: // the code's, the prefix never one of them. var word_from: usize = 0; - if (clicked.r0 >= pane.tag_rows and clicked.r0 == clicked.r1 and clicked.c0 == clicked.c1 and row >= 0) diff: { + if (for_look and clicked.r0 >= pane.tag_rows and clicked.r0 == clicked.r1 and clicked.c0 == clicked.c1 and row >= 0) diff: { var address_buf: [4096 + 32]u8 = undefined; const at: usize = @intCast(row); const click = look.diffClick(p, pane, at, @intCast(@max(0, col)), &address_buf) orelse break :diff; diff --git a/src/look.zig b/src/look.zig index 0df1f307..a2aa5be0 100644 --- a/src/look.zig +++ b/src/look.zig @@ -2524,6 +2524,68 @@ test "every entry point to Look takes one expansion: a diff line and a file:12:5 } } +test "a tag word looked at by a click and by Enter is one record to an event reader; an exec on a diff line takes its word, never the address" { + if (!platform_has_fs) return; + const diff_text = + \\diff --git a/src/x.zig b/src/x.zig + \\--- a/src/x.zig + \\+++ b/src/x.zig + \\@@ -1,1 +1,1 @@ + \\-old + \\+line one + \\ + ; + var fx: DiffFixture = undefined; + try fx.init(true, &.{.{ "src/x.zig", "line one\n" }}, diff_text); + defer fx.tmp.cleanup(); + const th = @import("ninep/testing.zig"); + const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 }); + defer p.deinit(); + const pane = try fx.open(p, diff_text); + pane.tag_expanded = true; + var frame = std.heap.ArenaAllocator.init(std.testing.allocator); + defer frame.deinit(); + p.sync(); + _ = try p.render(frame.allocator()); + + // An exec on the `+` of a hunk line takes the word there; a look, the + // address the line names. + pane.body.cur_row = 5; + pane.body.cur_col = 0; + try std.testing.expectEqualStrings("+line", exec.operandAt(p, pane, cursorWordSel(p, pane), false, false).text.?); + try std.testing.expectEqualStrings("src/x.zig:1", exec.operandAt(p, pane, cursorWordSel(p, pane), false, true).text.?); + + // An event reader on the pane. + const event = pardes.ctlfs.Node.of(pane.serial, .event); + const opened = th.call(p, .{ .tag = 1, .op = .open, .node = event }); + defer _ = th.call(p, .{ .tag = 9, .op = .release, .node = event, .handle = opened.reply.handle }); + const tag = try tagline.tagText(p, p.scratch.allocator(), pane); + const at: u16 = @intCast(std.mem.lastIndexOf(u8, tag, "Del").?); + // A B3 click on the tag's Del, where the tag as shown has it... + const shown = (try tagline.shownTag(p, p.scratch.allocator(), pane)).text; + const shown_at = std.mem.lastIndexOf(u8, shown, "Del").?; + const shown_row: u16 = @intCast(std.mem.count(u8, shown[0..shown_at], "\n")); + const line_start = if (std.mem.lastIndexOfScalar(u8, shown[0..shown_at], '\n')) |nl| nl + 1 else 0; + const x = p.rects[0].x + TAG_TEXT_INSET + @as(u16, @intCast(panes.File.displayWidth(shown[line_start..shown_at]))) + 1; + const y = p.rects[0].y + shown_row; + p.update(.{ .mouse = .{ .button = .right, .kind = .press, .col = x, .row = y } }); + p.update(.{ .mouse = .{ .button = .right, .kind = .release, .col = x, .row = y } }); + var clicked_buf: [128]u8 = undefined; + const clicked = th.rd(p, event, 0, 4096).bytes; + @memcpy(clicked_buf[0..clicked.len], clicked); + // ...and Enter with the tag's cursor on it. + tagline.enterTag(p, pane); + pane.tag.mode = .normal; + pane.tag.cur_row = 0; + pane.tag.cur_col = at + 1; + p.update(.{ .key = .{ .cp = Key.enter } }); + const keyed = th.rd(p, event, 0, 4096).bytes; + // The same record but for its origin, mouse or keyboard. + try std.testing.expect(clicked.len > 2); + try std.testing.expectEqualStrings(clicked_buf[1..clicked.len], keyed[1..]); + try std.testing.expect(p.panes[0] != null); +} + test "Look outside a diff pane expands and looks as ever" { const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 }); defer p.deinit(); diff --git a/src/mouse.zig b/src/mouse.zig index 7a2d0805..8020fda8 100644 --- a/src/mouse.zig +++ b/src/mouse.zig @@ -965,6 +965,21 @@ fn reportGesture( operand: exec.PointerOperand, chord_arg: ?[]const u8, ) bool { + if ((p.panes[id] orelse return false).fs.readers == 0) return false; + return reportRange(p, id, cmd, text, on_tag, gestureRange(p, id, text, on_tag, operand), chord_arg); +} + +/// An event reader told of a look or an exec of `text` at `range` of the +/// tag or body, by a click or a key alike: false when the pane has none. +pub fn reportRange( + p: *Pardes, + id: usize, + cmd: Builtin, + text: []const u8, + on_tag: bool, + range: ctlfs.pane.State.Range, + chord_arg: ?[]const u8, +) bool { const scripted = p.panes[id] orelse return false; if (scripted.fs.readers == 0) return false; const is_look = cmd == config.look_cmd; @@ -973,7 +988,6 @@ fn reportGesture( else (if (on_tag) .tag_exec else .body_exec); const named = exec.namesBuiltin(text); - const range = gestureRange(p, id, text, on_tag, operand); // No flag 2: the record already carries the word a click took, its // range and text, and in acme flag 2 promises a second record with them // (editors/acme/exec.c:154-183, look.c:42-68), which none follows here. @@ -1293,7 +1307,7 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void { return; } }; - var operand = exec.pointerOperand(p, pane, clk); + var operand = exec.operandAt(p, pane, clk, true, s.button == config.look_button); if (operand.expanded) |expanded| pane.sel[b] = expanded else if (operand.text == null) diff --git a/src/pardes.zig b/src/pardes.zig index 674128a1..425a0993 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -6296,12 +6296,13 @@ pub const Pardes = struct { const range = t.primaryRange(shown, 0); const lo = @min(range.anchor, range.head); const hi = @max(range.anchor, range.head); - const txt = if ((t.vsel.active and t.vsel.explicit) or t.msel.active) - shown[lo..hi] + const span: [2]usize = if ((t.vsel.active and t.vsel.explicit) or t.msel.active) + .{ lo, hi } else word: { const b = config.wordBounds(shown, modal.selectionCursor(shown, range)); - break :word shown[b.lo..b.hi]; + break :word .{ b.lo, b.hi }; }; + const txt = shown[span[0]..span[1]]; // A column tag's command acts on that column, empty or not. p.exec_column = if (header != null) p.header_column else null; defer p.exec_column = null; @@ -6310,7 +6311,12 @@ pub const Pardes = struct { // Out of the tag or header FIRST: Del frees the pane, Kill // and Restore the whole session. if (header != null) tagline.exitHeader(p) else pane.focus = .body; - if (txt.len > 0) exec.runBuiltin(p, cmd, p.active, "", txt); + if (txt.len == 0) return; + // A pane's tag word: an event reader hears it as a click on + // it, at its place in the tag (flatSurface is the tag's text). + if (header == null and t.what == .tag and + mouse.reportRange(p, p.active, cmd, txt, true, .{ .q0 = @intCast(span[0]), .q1 = @intCast(span[1]) }, null)) return; + exec.runBuiltin(p, cmd, p.active, "", txt); return; } edit.pinPaneCursor(p, &pane.body); @@ -6330,7 +6336,7 @@ pub const Pardes = struct { // The cursor's word as a click there takes it: one expansion // (exec.operandAt), a diff line's address and all, and an // event reader told as a click tells it. - const operand = exec.operandAt(p, pane, look.cursorWordSel(p, pane), false); + const operand = exec.operandAt(p, pane, look.cursorWordSel(p, pane), false, cmd == config.look_cmd); mouse.dispatchPointerBuiltin(p, p.active, cmd, operand.text, .{ .on_tag = false, .operand = operand }); return; } |
