summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/builtins.zig5
-rw-r--r--src/exec.zig98
-rw-r--r--src/look.zig93
-rw-r--r--src/mouse.zig4
-rw-r--r--src/pardes.zig8
5 files changed, 163 insertions, 45 deletions
diff --git a/src/builtins.zig b/src/builtins.zig
index cca7bfb5..1a2f132f 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -516,7 +516,10 @@ pub const Look = struct {
// name on the message row would only be noise over that
pub const quiet = true;
pub fn run(c: Ctx) void {
- look.lookAt(c.p, c.id, c.arg orelse return);
+ // Bare (a key bound to it, a tag's Look with nothing held): the
+ // selection, else what Enter on the cursor takes, the one expansion.
+ const arg = c.arg orelse (exec.currentOperand(c.p, c.pane) orelse return);
+ look.lookAt(c.p, c.id, arg);
}
};
diff --git a/src/exec.zig b/src/exec.zig
index f3819a83..8ddb9abd 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -108,6 +108,23 @@ pub const PointerOperand = struct {
};
pub fn pointerOperand(p: *Pardes, pane: *Pane, clicked: Pane.Sel) PointerOperand {
+ return operandAt(p, pane, clicked, 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;
+}
+
+/// What a look or an exec at `clicked` takes: the ONE expansion every
+/// entry point uses (a B2/B3 click, Enter and Tab in normal mode, a bare
+/// 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 {
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;
@@ -126,48 +143,49 @@ pub fn pointerOperand(p: *Pardes, pane: *Pane, clicked: Pane.Sel) PointerOperand
else
clicked.c0;
var result: PointerOperand = .{ .row = row, .col = col };
-
- const kept = pane.sel[sel_slot];
- const inside_kept = if (pane.pointerSelection(kept)) |rows| inside: {
- if (clicked.r0 < pane.tag_rows) break :inside false;
- if (pane.file != null and clicked.c0 < panes.File.gutterWidth(pane)) break :inside false;
- const source_col: usize = @intCast(@max(0, col));
- for (rows) |span| {
- const target = if (span.raw_terminal)
- (if (raw) span.row else pane.surfRow(span.row))
- else
- (if (raw) pane.gridRow(span.row) else span.row);
- var lo = span.lo;
- var hi = span.hi;
- if (span.raw_terminal and !raw) {
- lo -|= span.prompt_bytes;
- hi -|= span.prompt_bytes;
- } else if (!span.raw_terminal and raw) {
- const prefix = panes.terminal.promptPrefixBytes(pane, row, raw_line);
- lo += prefix;
- hi += prefix;
+ if (borrow) {
+ const kept = pane.sel[sel_slot];
+ const inside_kept = if (pane.pointerSelection(kept)) |rows| inside: {
+ if (clicked.r0 < pane.tag_rows) break :inside false;
+ if (pane.file != null and clicked.c0 < panes.File.gutterWidth(pane)) break :inside false;
+ const source_col: usize = @intCast(@max(0, col));
+ for (rows) |span| {
+ const target = if (span.raw_terminal)
+ (if (raw) span.row else pane.surfRow(span.row))
+ else
+ (if (raw) pane.gridRow(span.row) else span.row);
+ var lo = span.lo;
+ var hi = span.hi;
+ if (span.raw_terminal and !raw) {
+ lo -|= span.prompt_bytes;
+ hi -|= span.prompt_bytes;
+ } else if (!span.raw_terminal and raw) {
+ const prefix = panes.terminal.promptPrefixBytes(pane, row, raw_line);
+ lo += prefix;
+ hi += prefix;
+ }
+ if (target == row and source_col >= lo and source_col < hi)
+ break :inside true;
}
- if (target == row and source_col >= lo and source_col < hi)
- break :inside true;
+ break :inside false;
+ } else edit.spanHas(clicked.r0, clicked.c0, kept.r0, kept.c0, kept.r1, kept.c1);
+ if (kept.state == .done and inside_kept) {
+ result.text = edit.selectionText(p, pane, kept) catch null;
+ result.preview = kept;
+ return result;
+ }
+ if (pane.body.vsel.active and pane.body.vsel.explicit and
+ edit.spanHas(row, col, pane.body.vsel.row, pane.body.vsel.col, pane.body.cur_row, pane.body.cur_col))
+ {
+ result.text = edit.currentSelText(p, pane);
+ return result;
+ }
+ if (pane.body.msel.active and row >= @min(pane.body.msel.r0, pane.body.msel.r1) and
+ row <= @max(pane.body.msel.r0, pane.body.msel.r1))
+ {
+ result.text = edit.currentSelText(p, pane);
+ return result;
}
- break :inside false;
- } else edit.spanHas(clicked.r0, clicked.c0, kept.r0, kept.c0, kept.r1, kept.c1);
- if (kept.state == .done and inside_kept) {
- result.text = edit.selectionText(p, pane, kept) catch null;
- result.preview = kept;
- return result;
- }
- if (pane.body.vsel.active and pane.body.vsel.explicit and
- edit.spanHas(row, col, pane.body.vsel.row, pane.body.vsel.col, pane.body.cur_row, pane.body.cur_col))
- {
- result.text = edit.currentSelText(p, pane);
- return result;
- }
- if (pane.body.msel.active and row >= @min(pane.body.msel.r0, pane.body.msel.r1) and
- row <= @max(pane.body.msel.r0, pane.body.msel.r1))
- {
- result.text = edit.currentSelText(p, pane);
- return result;
}
// In a diff, a file or hunk header, and a hunk line pointed at on its
// `+`/`-`/` ` prefix, expand to the whole line (the hover shows it) and
diff --git a/src/look.zig b/src/look.zig
index 115dabe1..a358b31a 100644
--- a/src/look.zig
+++ b/src/look.zig
@@ -2426,6 +2426,99 @@ test "a click, a 9P whole-line look and an empty event write-back take one expan
}
}
+test "every entry point to Look takes one expansion: a diff line and a file:12:5: line land in one place by each" {
+ if (!platform_has_fs) return;
+ const numbered = "l1\nl2\nl3\nl4\nl5\nl6\nl7\nl8\nl9\nl10\nl11\nline 12 here\nl13\n";
+ const diff_text =
+ \\diff --git a/src/x.zig b/src/x.zig
+ \\--- a/src/x.zig
+ \\+++ b/src/x.zig
+ \\@@ -11,2 +11,3 @@
+ \\ l11
+ \\-old
+ \\+line 12 here
+ \\
+ ;
+ const log_text = "src/x.zig:12:5: error: bad\n";
+ var fx: DiffFixture = undefined;
+ try fx.init(true, &.{.{ "src/x.zig", numbered }}, diff_text);
+ defer fx.tmp.cleanup();
+ const th = @import("ninep/testing.zig");
+ // `column` -1: wherever a click puts it, every other way the same.
+ const Case = struct { text: []const u8, name: []const u8, row: u16, col: u16, sweep: [2]u16, line: i32, column: i32 };
+ const cases = [_]Case{
+ // `+line 12 here` pointed at on its `+`: the hunk's new line 12.
+ .{ .text = diff_text, .name = "change.diff", .row = 6, .col = 0, .sweep = .{ 0, 12 }, .line = 11, .column = -1 },
+ // A compiler's place, its trailing `:` dropped: line 12, column 5.
+ .{ .text = log_text, .name = "build.log", .row = 0, .col = 2, .sweep = .{ 0, 14 }, .line = 11, .column = 4 },
+ };
+ for (cases) |case| {
+ const p = try Pardes.init(std.testing.allocator, .{ .cols = 100, .rows = 30 });
+ defer p.deinit();
+ const pane = try fx.open(p, case.text);
+ p.gpa.free(pane.file.?.path);
+ pane.file.?.path = try std.fs.path.join(p.gpa, &.{ fx.dir, case.name });
+ pane.tag_expanded = false;
+ var frame = std.heap.ArenaAllocator.init(std.testing.allocator);
+ defer frame.deinit();
+ const Entry = enum { click, sweep, enter, bare_look, look_file, event_write_back };
+ var column = case.column;
+ for (std.enums.values(Entry)) |entry| {
+ // From the pane, its cursor on the place, nothing selected,
+ // the target (once open) moved off it.
+ for (p.panes[1..]) |slot| if (slot) |other| {
+ other.body.cur_row = 0;
+ other.body.cur_col = 0;
+ };
+ p.active = 0;
+ pane.body.mode = .normal;
+ pane.body.vsel.active = false;
+ pane.body.msel.active = false;
+ pane.body.cur_row = case.row;
+ pane.body.cur_col = case.col;
+ pane.body.cur_pinned = true;
+ pane.file.?.scroll = 0;
+ p.sync();
+ _ = frame.reset(.retain_capacity);
+ _ = try p.render(frame.allocator());
+ const rect = p.rects[0];
+ const x0 = rect.x + config.GUTTER + panes.File.gutterWidth(pane);
+ const y = p.bodyTop(pane, rect) + case.row;
+ switch (entry) {
+ .click => {
+ p.update(.{ .mouse = .{ .button = .right, .kind = .press, .col = x0 + case.col, .row = y } });
+ p.update(.{ .mouse = .{ .button = .right, .kind = .release, .col = x0 + case.col, .row = y } });
+ },
+ .sweep => {
+ p.update(.{ .mouse = .{ .button = .right, .kind = .press, .col = x0 + case.sweep[0], .row = y } });
+ p.update(.{ .mouse = .{ .button = .right, .kind = .drag, .col = x0 + case.sweep[1], .row = y } });
+ p.update(.{ .mouse = .{ .button = .right, .kind = .release, .col = x0 + case.sweep[1], .row = y } });
+ },
+ .enter => p.update(.{ .key = .{ .cp = Key.enter } }),
+ .bare_look => _ = p.executeBuiltinLine(0, "Look"),
+ .look_file => {
+ const line = modal.lineSlice(case.text, case.row);
+ // A diff line written whole; a place, as a script writes it.
+ const written = if (std.mem.eql(u8, case.name, "change.diff")) line else line[case.sweep[0] .. case.sweep[1] + 1];
+ _ = th.wr(p, @intFromEnum(pardes.ctlfs.TopFile.look), written);
+ },
+ .event_write_back => {
+ const body = pane.file.?.content;
+ const text = pardes.ctlfs.events.expandAt(p, pane, body, modal.lineStartOffset(body, case.row) + case.col, true);
+ lookAt(p, 0, p.scratch.allocator().dupe(u8, text) catch unreachable);
+ },
+ }
+ const landed = p.panes[p.active].?;
+ const f = landed.file orelse return error.NothingOpened;
+ if (column < 0) column = landed.body.cur_col;
+ if (!std.mem.endsWith(u8, f.path, "/src/x.zig") or landed.body.cur_row != case.line or landed.body.cur_col != column) {
+ std.debug.print("{s} by {s}: at {s} {d}:{d}\n", .{ case.name, @tagName(entry), f.path, landed.body.cur_row, landed.body.cur_col });
+ return error.EntryPointsDiffer;
+ }
+ }
+ }
+}
+
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 21961cf8..7a2d0805 100644
--- a/src/mouse.zig
+++ b/src/mouse.zig
@@ -1044,7 +1044,9 @@ fn gestureRange(p: *Pardes, id: usize, text: []const u8, on_tag: bool, operand:
return .{ .q0 = q0, .q1 = @intCast(@min(q0 + text.len, f.content.len)) };
}
-fn dispatchPointerBuiltin(
+/// A look or exec of what an operand took (exec.operandAt), from a
+/// click or a key: an event reader is told, else it runs.
+pub fn dispatchPointerBuiltin(
p: *Pardes,
id: usize,
cmd: Builtin,
diff --git a/src/pardes.zig b/src/pardes.zig
index d0bb9229..1cc1c5b8 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -6326,9 +6326,11 @@ pub const Pardes = struct {
return;
}
}
- const sel = look.expandedSel(p, pane, look.cursorWordSel(p, pane)) orelse return;
- const word = edit.selectionText(p, pane, sel) catch return;
- exec.runBuiltinFrom(p, cmd, p.active, "", word, true);
+ // 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);
+ mouse.dispatchPointerBuiltin(p, p.active, cmd, operand.text, .{ .on_tag = false, .operand = operand });
return;
}
if (t.mode == .normal and t.what == .body and pane.hasPdf()) return normal.handlePdfNormal(p, pane, key);