summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/pardes.zig90
-rw-r--r--src/tutor.txt16
-rw-r--r--test/snapshots/chordcut.golden279
-rw-r--r--test/snapshots/chordcut.snap101
4 files changed, 484 insertions, 2 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index f647ed00..9397f445 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -659,7 +659,9 @@ const Drag = union(enum) {
border_v: struct { left_col: usize, cur_x: u16 },
border_h: struct { col: usize, top_idx: usize, cur_y: u16 },
move: struct { id: usize, cur_x: u16, cur_y: u16 },
- select: struct { id: usize, button: Mouse.Button },
+ /// chorded: a 1-2/1-3 cut/paste chord fired during this left drag —
+ /// the drag's own release is then inert
+ select: struct { id: usize, button: Mouse.Button, chorded: bool = false },
};
const Rect = struct { x: u16, y: u16, w: u16, h: u16 };
@@ -3549,6 +3551,13 @@ pub const Pardes = struct {
}
return;
}
+ // acme 3-1: a left tap during a right (look) drag cancels
+ // the look; the rest of the gesture is inert
+ if (p.drag == .select and p.drag.select.button == .right) {
+ if (p.panes[p.drag.select.id]) |t| t.sel[@intFromEnum(Mouse.Button.right)].state = .none;
+ p.drag = .none;
+ return;
+ }
if (mrow < TOPBAR_H) return; // topbar: left click deliberately inert
// resize handles first: a pane's own trailing edge (v: the
// left column's last col; h: the upper pane's last row)
@@ -3596,6 +3605,19 @@ pub const Pardes = struct {
},
.middle, .right => switch (m.kind) {
.press => {
+ // acme 1-2 / 1-3: middle (Cut) or right (Paste) tapped
+ // while the left button holds a selection drag
+ if (p.drag == .select and p.drag.select.button == .left) {
+ p.chordCutPaste(m.button == .middle);
+ return;
+ }
+ // acme 2-3 / 3-2: tapping the OTHER of middle/right during
+ // an execute or look drag cancels it (textselect23's mask)
+ if (p.drag == .select) {
+ if (p.panes[p.drag.select.id]) |t| t.sel[@intFromEnum(p.drag.select.button)].state = .none;
+ p.drag = .none;
+ return;
+ }
if (mrow < TOPBAR_H) {
// global tagbar: MIDDLE runs the command under the click
// (a stray left click must never Kill)
@@ -3713,6 +3735,11 @@ pub const Pardes = struct {
},
.select => |s| {
const pane = p.panes[s.id] orelse return;
+ if (s.chorded) {
+ // a cut/paste chord consumed this drag; the release is inert
+ pane.sel[@intFromEnum(s.button)].state = .none;
+ return;
+ }
const b = @intFromEnum(s.button);
pane.sel[b].state = .done;
if (s.button == .left) {
@@ -3749,6 +3776,67 @@ pub const Pardes = struct {
}
}
+ /// acme 1-2 (Cut) / 1-3 (Paste): middle or right tapped while the left
+ /// button holds a selection drag. The first tap converts the mouse
+ /// selection into the modal one (vsel anchored at the drag start, cursor
+ /// at its end; a plain click is a bare cursor). Cut yanks + deletes the
+ /// selection; Paste replaces it with the yank register, or splices the
+ /// register in literally at a bare cursor — so 1-2 then 1-3 in one hold
+ /// nets out to Snarf (copy: the text returns, the register keeps it).
+ fn chordCutPaste(p: *Pardes, cut: bool) void {
+ const s = &p.drag.select;
+ const pane = p.panes[s.id] orelse return;
+ if (!s.chorded) {
+ s.chorded = true;
+ const sl = pane.sel[0];
+ pane.sel[0].state = .none;
+ if (pane.mode == .tty) return; // the pty owns the cursor there
+ const pfx: i32 = if (pane.file != null) PREFIX_W else 0;
+ const hs: i32 = if (pane.file != null) pane.hscroll else 0;
+ const row0 = @max(0, sl.r0 - @as(i32, BOX_H)) + pane.scroll();
+ const row1 = @max(0, sl.r1 - @as(i32, BOX_H)) + pane.scroll();
+ const col0 = @max(0, sl.c0 - pfx) + hs;
+ const col1 = @max(0, sl.c1 - pfx) + hs;
+ pane.cur_row = row1;
+ pane.cur_col = col1;
+ pane.cur_off = 0;
+ pane.cur_pinned = true;
+ pane.msel.active = false;
+ pane.pending = 0;
+ if (!pane.isTerminal()) pane.mode = .normal;
+ pane.vsel = .{ .active = row0 != row1 or col0 != col1, .row = row0, .col = col0, .explicit = false };
+ }
+ if (pane.mode == .tty) return;
+ if (cut) {
+ if (pane.vsel.active) p.normalDelete(pane, true);
+ } else if (pane.vsel.active) {
+ p.normalReplaceYank(pane);
+ } else if (pane.file) |*f| {
+ // bare cursor: splice the register in literally (no linewise
+ // magic) so a paste right after a cut restores the text exactly
+ const y = p.yank orelse return;
+ if (y.len == 0) return;
+ const at = modal.Cursor{ .row = @intCast(@max(0, pane.cur_row)), .col = @intCast(@max(0, pane.cur_col)) };
+ p.pushUndo(pane);
+ const new = modal.insertAt(p.gpa, f.content, at, y) catch return;
+ p.setFileContent(f, new);
+ pane.vsel = .{ .active = y.len > 1, .row = @intCast(at.row), .col = @intCast(at.col), .explicit = false };
+ const end = modal.advanceBy(at, y);
+ if (end.col > 0) {
+ pane.cur_row = @intCast(end.row);
+ pane.cur_col = @intCast(end.col - 1);
+ } else {
+ // the register ended in '\n': the cursor lands ON that newline
+ pane.cur_row = @intCast(end.row -| 1);
+ pane.cur_col = @intCast(modal.lineSlice(f.content, end.row -| 1).len);
+ }
+ pane.cur_off = 0;
+ pane.cur_pinned = true;
+ pane.sticky_col = -1;
+ pane.ensureCursorVisible();
+ } else p.normalPaste(pane, true); // terminal: splice run text at the cursor
+ }
+
/// tty mode: encode the key the way a terminal would and send it to the pty.
fn forwardKey(p: *Pardes, id: usize, key: Key) void {
var cbuf: [1]u8 = undefined;
diff --git a/src/tutor.txt b/src/tutor.txt
index 815cd4f7..f61ef8cc 100644
--- a/src/tutor.txt
+++ b/src/tutor.txt
@@ -66,12 +66,24 @@
opens/focuses a terminal there and ls's it; a file opens
a file pane (scrolled to a ":line" suffix if present).
- SELECT-THEN-ACT: there is no held-button chord. Instead:
+ SELECT-THEN-ACT:
- MIDDLE-drag over text selects AND runs it on release (one gesture);
a no-drag middle click auto-expands to the word under the cursor.
- or select with the keyboard (`v` chars / `x` lines), then press
Tab to execute or Enter to look — the acme chords on the keyboard.
+ CHORDS (acme's held-button combos):
+ 1-2 hold LEFT on a selection, tap MIDDLE: Cut it into the yank
+ register (the snarf buffer).
+ 1-3 hold LEFT, tap RIGHT: Paste the register over the selection,
+ or splice it in at a bare click point. So 1-2 then 1-3 in the
+ SAME hold = copy (Snarf): the text comes back, the register
+ keeps it. After a chord the left release is inert.
+ 2-1 hold a MIDDLE drag over a command, tap LEFT: the kept left
+ selection rides along as the command's ARGUMENT.
+ 2-3 / 3-1 / 3-2 tapping the other button during a MIDDLE (execute)
+ or RIGHT (look) drag CANCELS the gesture.
+
THE TAG: each pane has a one-line tag: its MODE (NOR/INS/TTY) + directory
or file path + builtins. File panes show "Save Del" by default; Save
writes the current file to disk, Del closes the window. Clicking a tag
@@ -393,6 +405,8 @@
MOUSE (acme): L select/focus+pin M execute R look(open)
select-then-act: middle-drag, or v/x then Tab
+ chords: 1-2 cut 1-3 paste (both in one hold = snarf)
+ 2-1 = middle-exec with the left selection as argument
file tag = mode + path + "Save Del"; other tags show Del
top bar = Kill Newcol Tutor Debug Colors NextColor
TTY: a terminal IS a pane; Ctrl-b toggles the live shell by default,
diff --git a/test/snapshots/chordcut.golden b/test/snapshots/chordcut.golden
new file mode 100644
index 00000000..b7c0122f
--- /dev/null
+++ b/test/snapshots/chordcut.golden
@@ -0,0 +1,279 @@
+== snap opened grid=60x30 cursor=7,2
+|Kill Newcol Tutor Debug Colors NextColor Dump
+| NOR /tmp/pardes-snap/chordcu NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+| 2 line 2 f.txt g.txt
+| 3 line 3
+| 4 line 4
+| 5 line 5
+|╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+| f.txt
+|
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap cut grid=60x30 cursor=7,3
+|Kill Newcol Tutor Debug Colors NextColor Dump
+| NOR /tmp/pardes-snap/chordcu NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+| 2 2 f.txt g.txt
+| 3 line 3
+| 4 line 4
+| 5 line 5
+| 6
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+| f.txt
+|
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap copied grid=60x30 cursor=10,4
+|Kill Newcol Tutor Debug Colors NextColor Dump
+| NOR /tmp/pardes-snap/chordcu NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+| 2 2 f.txt g.txt
+| 3 line 3
+| 4 line 4
+| 5 line 5
+| 6
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+| f.txt
+|
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap pointpaste grid=60x30 cursor=16,5
+|Kill Newcol Tutor Debug Colors NextColor Dump
+| NOR /tmp/pardes-snap/chordcu NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+| 2 2 f.txt g.txt
+| 3 line 3
+| 4 line 4line
+| 5 line 5
+| 6
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+| f.txt
+|
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap replaced grid=60x30 cursor=11,6
+|Kill Newcol Tutor Debug Colors NextColor Dump
+| NOR /tmp/pardes-snap/chordcu NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+| 2 2 f.txt g.txt
+| 3 line 3
+| 4 line 4line
+| 5 lline 5
+| 6
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+| f.txt
+|
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap cancelexec grid=60x30 cursor=25,3
+|Kill Newcol Tutor Debug Colors NextColor Dump
+| NOR /tmp/pardes-snap/chordcu NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+| 2 2echo B''OO g.txt f.txt g.txt
+| 3 line 3
+| 4 line 4line
+| 5 lline 5
+| 6
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+| f.txt
+|
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap execafter grid=60x30 cursor=25,3
+|Kill Newcol Tutor Debug Colors NextColor Dump
+| NOR /tmp/pardes-snap/chordcu NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+| 2 2echo B''OO g.txt f.txt g.txt
+| 3 line 3
+| 4 line 4line
+| 5 lline 5
+| 6
+| NOR /tmp/pardes-snap/chordcu
+| f.txt g.txt
+|
+|
+|
+| f.txt
+| BOO
+|
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap cancellook grid=60x30 cursor=25,3
+|Kill Newcol Tutor Debug Colors NextColor Dump
+| NOR /tmp/pardes-snap/chordcu NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+| 2 2echo B''OO g.txt f.txt g.txt
+| 3 line 3
+| 4 line 4line
+| 5 lline 5
+| 6
+| NOR /tmp/pardes-snap/chordcu
+| f.txt g.txt
+|
+|
+|
+| f.txt
+| BOO
+|
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap lookafter grid=60x30 cursor=7,5
+|Kill Newcol Tutor Debug Colors NextColor Dump
+| NOR /tmp/pardes-snap/chordcu NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+|╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ f.txt g.txt
+| NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+| 2 line 2
+| 3
+| NOR /tmp/pardes-snap/chordcu
+| f.txt g.txt
+|
+|
+|
+| f.txt
+| BOO
+|
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
diff --git a/test/snapshots/chordcut.snap b/test/snapshots/chordcut.snap
new file mode 100644
index 00000000..fc033e39
--- /dev/null
+++ b/test/snapshots/chordcut.snap
@@ -0,0 +1,101 @@
+# acme 1-2/1-3 chords in a file pane: with the left button holding a drag
+# selection, tapping middle Cuts it into the snarf (yank) register; tapping
+# right Pastes the register (replacing the selection, or splicing at a bare
+# click point); cut then paste in ONE hold nets out to copy/Snarf. The left
+# release after a chord is inert (the cursor stays where the op left it).
+# Also the cancel chords: tapping right during a middle (execute) drag or
+# left during a right (look) drag cancels the gesture entirely.
+lines f.txt 5
+lines g.txt 2
+start 30 60 -n 3
+wait 8000 f.txt
+stable 700 20000
+# open f.txt: click a body spot, type the name, look it
+press left 5 8
+release left 5 8
+stable 400 5000
+key i
+text f.txt
+key esc
+settle 700
+stable 400 5000
+press right 6 8
+release right 6 8
+wait 10000 line 3
+stable 700 15000
+snap opened
+# 1-2 Cut: left-drag over "line" on line 2, tap middle while holding
+press left 8 4
+drag left 11 4
+press middle 11 4
+release middle 11 4
+release left 11 4
+stable 400 5000
+snap cut
+# 1-2 then 1-3 in one hold = copy: line 3 keeps its text, register loaded
+press left 8 5
+drag left 11 5
+press middle 11 5
+release middle 11 5
+press right 11 5
+release right 11 5
+release left 11 5
+stable 400 5000
+snap copied
+# 1-3 at a bare click point: hold left past the end of line 4, tap right —
+# the register splices in at the click
+press left 14 6
+press right 14 6
+release right 14 6
+release left 14 6
+stable 400 5000
+snap pointpaste
+# 1-3 over a fresh selection replaces it with the register
+press left 9 7
+drag left 11 7
+press right 11 7
+release right 11 7
+release left 11 7
+stable 400 5000
+snap replaced
+# arm the cancel tests: append `echo B''OO g.txt` to line 2
+press left 10 4
+release left 10 4
+stable 400 5000
+key i
+text echo B''OO g.txt
+key esc
+settle 700
+stable 400 5000
+# 2-3 cancel (acme textselect2): tap right during the middle execute-drag
+# over `echo B''OO` — the command never runs (no BOO anywhere)
+press middle 10 4
+drag middle 19 4
+press right 19 4
+release right 19 4
+release middle 19 4
+stable 700 8000
+snap cancelexec
+# the same drag released clean proves the word was live: BOO appears
+press middle 10 4
+drag middle 19 4
+release middle 19 4
+wait 10000 BOO
+stable 700 10000
+snap execafter
+# 3-1 cancel (acme textselect3): tap left during the right look-drag over
+# g.txt — no pane opens
+press right 21 4
+drag right 25 4
+press left 25 4
+release left 25 4
+release right 25 4
+stable 700 8000
+snap cancellook
+# the same look released clean opens g.txt
+press right 21 4
+drag right 25 4
+release right 25 4
+wait 10000 line 2
+stable 700 10000
+snap lookafter