summaryrefslogtreecommitdiff
path: root/src/mouse.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 00:01:57 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commit23f430573efe4f6ef55b0180aa0d742ff4eca1d9 (patch)
treeda2157ba73d6abff4b1674541433294fb6c2daa9 /src/mouse.zig
parent9584b0682b45f17aaa42157540947904d867777a (diff)
downloadpardes-23f430573efe4f6ef55b0180aa0d742ff4eca1d9.tar.gz
pardes-23f430573efe4f6ef55b0180aa0d742ff4eca1d9.zip
Make a pane's tag a Text edited with the body's own keys
The tag was a fixed buffer with its own one-line editor, a cursor measured across the path, and a hijack of the body's mode while it was typed into. It is now the pane's second Text: its own characters after the computed prefix (path, dirty marker, PDF page), which is never stored in it, so a rename or a dirty marker never moves its cursor. Normal and insert mode are the body's, undo included; `:` moves the keyboard between body and tag, the tag remembers its cursor and starts on Save, and executing a word from it hands the keyboard back. The mouse still sees the prefix: a sweep selects across it and a click on the path drafts a new name. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/mouse.zig')
-rw-r--r--src/mouse.zig82
1 files changed, 58 insertions, 24 deletions
diff --git a/src/mouse.zig b/src/mouse.zig
index 5f77634b..ae047c58 100644
--- a/src/mouse.zig
+++ b/src/mouse.zig
@@ -39,7 +39,6 @@ pub const Drag = union(enum) {
/// `grab`: how far into the grip it was pressed, so the column's left
/// edge lands at `cur_x - grab`. A release still on the grip moves nothing.
column_move: struct { serial: u32, cur_x: u16, grab: u16 = 0 },
- tag: struct { id: usize },
/// chorded: a 1-2/1-3 cut/paste chord fired during this left drag —
/// the drag's own release is then inert
select: struct {
@@ -55,7 +54,7 @@ pub const Drag = union(enum) {
fn button(drag: Drag) ?Mouse.Button {
return switch (drag) {
.none => null,
- .border_v, .border_h, .move, .column_move, .tag => config.select_button,
+ .border_v, .border_h, .move, .column_move => config.select_button,
.select => |selection| selection.button,
};
}
@@ -228,7 +227,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
const previous_tag_hit = p.pointer_tag_hit;
p.pointer_tag_hit = if (m.tag_hit) |point| p.reprojectTagHit(point) else null;
if (m.tag_hit != null and p.pointer_tag_hit == null and m.kind == .press) return;
- if (m.tag_hit == null and (m.kind == .drag or m.kind == .release) and (p.drag == .tag or p.drag == .select or p.header_drag)) {
+ if (m.tag_hit == null and (m.kind == .drag or m.kind == .release) and (p.drag == .select or p.header_drag)) {
if (previous_tag_hit) |previous| {
var continued = previous;
continued.pixel_x = (@as(f32, @floatFromInt(raw_col)) + 0.5) * @as(f32, @floatFromInt(previous.metrics.body_w));
@@ -236,7 +235,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
p.pointer_tag_hit = continued;
}
}
- if ((m.kind == .drag or m.kind == .release) and (p.drag == .tag or p.drag == .select or p.header_drag)) {
+ if ((m.kind == .drag or m.kind == .release) and (p.drag == .select or p.header_drag)) {
if (previous_tag_hit) |previous| if (p.pointer_tag_hit) |*current| {
current.kind = previous.kind;
current.id = previous.id;
@@ -303,7 +302,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
(m.button == config.select_button or m.button == config.exec_button or m.button == config.look_button))
{
if (m.button == config.select_button) if (p.panes[p.active]) |pane| {
- if (pane.tag_edit) Pardes.exitTagEdit(pane);
+ pane.focus = .body;
pane.closePrompt();
};
var column: ?usize = null;
@@ -439,7 +438,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
const r = p.rects[id];
const pane = p.panes[id] orelse return;
if (id != p.active) if (p.panes[p.active]) |previous| {
- if (previous.tag_edit) Pardes.exitTagEdit(previous);
+ previous.focus = .body;
previous.closePrompt();
};
const tag_y = if (p.settings.tag_bottom) r.y + r.h -| BOX_H else r.y;
@@ -448,20 +447,29 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
// gutter scrollbar: a left click scrolls UP to that row
p.active = id;
pane.scrollBy(-(@as(i32, mrow) - @as(i32, body_y)));
- } else if (mrow >= tag_y and mrow < tag_y + BOX_H) {
- if (mcol < r.x + TAG_TEXT_INSET) return;
- p.active = id;
- pane.closePrompt();
- p.enterTagEdit(pane, if (tagColumn(p, .pane, id, p.pointer_tag_hit, false)) |value| @as(i32, value) else @as(i32, mcol) - @as(i32, r.x + TAG_TEXT_INSET));
- pane.tag_anchor = pane.tag_col;
- p.drag = .{ .tag = .{ .id = id } };
} else {
+ const on_tag = mrow >= tag_y and mrow < tag_y + BOX_H;
+ if (on_tag and mcol < r.x + TAG_TEXT_INSET) return;
p.active = id;
- if (pane.tag_edit) Pardes.exitTagEdit(pane);
pane.closePrompt();
const pointed = edit.pointerTextSelection(p, id, mcol, mrow, m.body_hit, p.pointer_tag_hit) orelse return;
pane.clearPointerSelection(sel_slot);
pane.sel[sel_slot] = pointed.sel;
+ // A clicked tag is typed straight into, from where it was
+ // clicked; `:` is the door to its normal mode.
+ pane.focus = if (on_tag) .tag else .body;
+ if (on_tag) {
+ // a count typed before the click was meant for the body
+ pane.body.normal.clear();
+ pane.tag.mode = .insert;
+ pane.tag.normal.clear();
+ pane.tag.vsel.active = false;
+ pane.tag.msel.active = false;
+ pane.tag.nsel = 0;
+ pane.tag.cur_pinned = true;
+ pane.tag.cur_row = 0;
+ pane.tag.cur_col = @intCast(p.tagOffsetAt(pane, @intCast(@max(0, pointed.sel.c0))) orelse 0);
+ }
p.drag = .{ .select = .{
.id = id,
.button = config.select_button,
@@ -472,7 +480,7 @@ pub fn handleMouse(p: *Pardes, m: Mouse) void {
config.select_button,
mcol,
mrow,
- false,
+ pointed.on_tag,
),
} };
}
@@ -617,13 +625,6 @@ pub fn dragUpdate(p: *Pardes, mcol: u16, mrow: u16, body_hit: ?Mouse.BodyHit) vo
panes.Pdf.pointerUpdate(&s.pdf, p, pane, mcol, mrow);
}
},
- .tag => |d| {
- const pane = p.panes[d.id] orelse return;
- const c = if (tagColumn(p, .pane, d.id, p.pointer_tag_hit, true)) |value| @as(i32, value) else @as(i32, mcol) - @as(i32, p.rects[d.id].x + TAG_TEXT_INSET);
- const text = p.tagText(p.scratch.allocator(), pane) catch return;
- pane.tag_col = @intCast(@min(text.len, panes.File.rawAtDisplay(text, @as(usize, @intCast(@max(0, c))) + pane.tag_scroll)));
- pane.tag_sel = pane.tag_col != pane.tag_anchor;
- },
.none => {},
}
}
@@ -816,6 +817,16 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void {
const sl = pane.sel[sel_slot];
const dragged = sl.c0 != sl.c1 or sl.r0 != sl.r1;
if (!dragged) pane.sel[sel_slot].state = .none;
+ if (sl.r0 < BOX_H) {
+ // On the tag the caret goes where the button came up. A
+ // click on the path, if it can be renamed, drafts a name.
+ const at = p.tagOffsetAt(pane, @intCast(@max(0, sl.c1)));
+ if (at) |col| {
+ pane.tag.cur_row = 0;
+ pane.tag.cur_col = @intCast(col);
+ } else if (!dragged) p.draftName(pane, @intCast(@max(0, sl.c1)));
+ return;
+ }
if (dragged) edit.capturePointerSelection(p, pane, sel_slot) catch {};
if (dragged and pane.body.mode == .tty) mirrorTtySelection(p, pane);
const body_vis = sl.r1 - @as(i32, BOX_H);
@@ -869,7 +880,7 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void {
}
const txt = operand.text;
const cmd = if (s.button == config.look_button) config.look_cmd else config.exec_cmd;
- if (clk.r0 < BOX_H and s.button == config.exec_button and pane.tag_name_len != null) {
+ if (clk.r0 < BOX_H and s.button == config.exec_button and pane.prompt == .name) {
const prefix_width = panes.File.displayWidth(p.tagPrefix(pane) catch return);
const on_name = @as(usize, @intCast(@max(0, clk.c0))) < prefix_width;
if (!p.commitTagName(pane)) return;
@@ -895,7 +906,6 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void {
});
}
},
- .tag => {}, // dragUpdate already left the tag cursor + selection set
.none => {},
}
}
@@ -913,6 +923,30 @@ fn chordCutPaste(p: *Pardes, cut: bool) void {
if (cut and pv.selection_text.len > 0) edit.setYank(p, pv.selection_text);
return;
};
+ if (@min(pane.sel[sel_slot].r0, pane.sel[sel_slot].r1) < BOX_H) {
+ // A sweep over the tag cuts from and pastes into the tag's own text;
+ // the part of it over the prefix is not the tag's to cut.
+ const t = &pane.tag;
+ if (!s.chorded) {
+ s.chorded = true;
+ const sl = pane.sel[sel_slot];
+ pane.sel[sel_slot].state = .none;
+ const from = p.tagOffsetAt(pane, @intCast(@max(0, @min(sl.c0, sl.c1)))) orelse 0;
+ const to = p.tagOffsetAt(pane, @intCast(@max(0, @max(sl.c0, sl.c1)))) orelse 0;
+ t.cur_row = 0;
+ t.cur_col = @intCast(to);
+ t.cur_pinned = true;
+ t.vsel = .{ .active = from != to, .row = 0, .col = @intCast(from), .explicit = false };
+ t.msel.active = false;
+ t.nsel = 0;
+ }
+ if (cut) {
+ if (t.vsel.active) edit.normalDelete(p, t, true);
+ } else if (t.vsel.active) {
+ edit.normalReplaceYank(p, t);
+ } else edit.pasteText(p, t, p.yank orelse return, true, 1);
+ return;
+ }
if (pane.body.mode == .tty) {
if (!s.chorded) {
s.chorded = true;