summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-08 09:56:53 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commitb91e5ebf8b529d0ced0bf18e83b17df0bd2374ce (patch)
tree58122968971bee42ef7144a0d3c1315c5158d636
parent32041350ab22935724b950794c5c1d71374dc76b (diff)
downloadpardes-b91e5ebf8b529d0ced0bf18e83b17df0bd2374ce.tar.gz
pardes-b91e5ebf8b529d0ced0bf18e83b17df0bd2374ce.zip
chord polish: the selection a 1-2/1-3 chord leaves behind is dismissed by the next left click instead of stretching to it (explicit v/x selections keep click-extend), and in a tty-mode shell 1-3 now pastes into the pty like a terminal emulator — the click is forwarded first (SGR or X10, only when the app listens for mouse) so mouse-aware programs paste at the click point, then the register arrives bracketed (mode 2004) or with \n->\r when unbracketed; 1-2 stays inert there. chordcut.snap pins both (a cat -v probe shows the exact wire bytes); tutor updated.
-rw-r--r--src/pardes.zig53
-rw-r--r--src/tutor.txt5
-rw-r--r--test/snapshots/chordcut.golden124
-rw-r--r--test/snapshots/chordcut.snap46
4 files changed, 226 insertions, 2 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index fc216b24..994b0785 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -3805,6 +3805,10 @@ pub const Pardes = struct {
if (!pane.isTerminal()) pane.mode = .normal;
pane.msel.active = false;
pane.pending = 0;
+ // a chord/motion selection is residue: a fresh click
+ // dismisses it rather than dragging the anchored span
+ // to the click (explicit v/x keeps vim's click-extend)
+ if (!pane.vsel.explicit) pane.vsel.active = false;
}
} else {
// acme execute (middle) / look (right): a no-drag click
@@ -3829,14 +3833,60 @@ pub const Pardes = struct {
/// 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).
+ /// In a tty-mode shell 1-3 instead pastes into the pty (forwarded click
+ /// + bracketed paste) and 1-2 is inert.
fn chordCutPaste(p: *Pardes, cut: bool) void {
const s = &p.drag.select;
const pane = p.panes[s.id] orelse return;
+ if (pane.mode == .tty) {
+ // tty: nothing can be cut — 1-2 stays inert; the pty owns the
+ // screen. 1-3 pastes like a terminal emulator: the click is
+ // forwarded first (only when the app listens for mouse) so
+ // mouse-aware programs put their cursor under it, then the
+ // register is typed — bracketed when the app set mode 2004
+ // (readline/vim/helix strip the markers), else with \n turned
+ // to \r like any unbracketed paste.
+ if (!s.chorded) {
+ s.chorded = true;
+ pane.sel[0].state = .none; // drop the sweep highlight
+ }
+ if (cut) return;
+ const y = p.yank orelse return;
+ if (y.len == 0) return;
+ const m = &pane.vt.modes;
+ if (m.get(.mouse_event_normal) or m.get(.mouse_event_button) or m.get(.mouse_event_any)) {
+ // dragUpdate keeps sel[0] tracking the held left button, so
+ // the click lands where the mouse is at this tap; 1-based,
+ // body-relative (the tag row is ours, not the app's)
+ const col: u16 = @intCast(std.math.clamp(pane.sel[0].c1 + 1, 1, 9999));
+ const row: u16 = @intCast(std.math.clamp(pane.sel[0].r1 - @as(i32, BOX_H) + 1, 1, 9999));
+ var mb: [32]u8 = undefined;
+ if (m.get(.mouse_format_sgr)) {
+ p.emitWrite(s.id, std.fmt.bufPrint(&mb, "\x1b[<0;{d};{d}M\x1b[<0;{d};{d}m", .{ col, row, col, row }) catch return);
+ } else {
+ // ponytail: legacy X10 bytes; add utf8/urxvt formats if an app ever wants them
+ const cb: u8 = @intCast(32 + @min(col, 222));
+ const rb: u8 = @intCast(32 + @min(row, 222));
+ p.emitWrite(s.id, &[_]u8{ 0x1b, '[', 'M', 32, cb, rb, 0x1b, '[', 'M', 35, cb, rb });
+ }
+ }
+ if (m.get(.bracketed_paste)) {
+ p.emitWrite(s.id, "\x1b[200~");
+ p.emitWrite(s.id, y);
+ p.emitWrite(s.id, "\x1b[201~");
+ } else {
+ const cp = p.scratch.allocator().dupe(u8, y) catch return;
+ for (cp) |*c| {
+ if (c.* == '\n') c.* = '\r';
+ }
+ p.emitWrite(s.id, cp);
+ }
+ 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();
@@ -3852,7 +3902,6 @@ pub const Pardes = struct {
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) {
diff --git a/src/tutor.txt b/src/tutor.txt
index 50b92558..7a8c7026 100644
--- a/src/tutor.txt
+++ b/src/tutor.txt
@@ -85,6 +85,11 @@
— a kept left selection or a v/x one, wherever it lives.
2-3 / 3-1 / 3-2 tapping the other button during a MIDDLE (execute)
or RIGHT (look) drag CANCELS the gesture.
+ In a TTY-mode shell 1-3 pastes into the program like a terminal:
+ the click is forwarded first (if it listens for mouse) so the
+ paste lands under it, then the register arrives bracketed.
+ The selection a chord leaves behind is dismissed by the next left
+ click (it doesn't stretch to the click).
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
diff --git a/test/snapshots/chordcut.golden b/test/snapshots/chordcut.golden
index b7c0122f..41194c76 100644
--- a/test/snapshots/chordcut.golden
+++ b/test/snapshots/chordcut.golden
@@ -277,3 +277,127 @@
|
|
|
+== style chordsel
+|0: 0-44 #969696,#2c2a3e, 45-59 d,#2c2a3e,
+|1: 0-1 d,#383c54, 2-29 #969696,#2c2a3e, 30-31 d,#383c54, 32-59 #969696,#2c2a3e,
+|2: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|3: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|4: 0-1 d,#6e4fd0, 2-29 #969696,#2c2a3e, 30-31 d,#525252, 32-59 d,d,
+|5: 0-1 d,#525252, 2-6 #969696,#2c2a3e, 7-10 #d8c8a8,#4a3a1e, 11-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|6: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|7: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|8: 0-1 d,#383c54, 2-29 #969696,#2c2a3e, 30-31 d,#525252, 32-59 d,d,
+|9: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|10: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|11: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|12: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|13: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|14: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|15: 0-1 d,#2a2a2a, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|16: 0-1 d,#383c54, 2-29 #969696,#2c2a3e, 30-31 d,#525252, 32-59 d,d,
+|17: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|18: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|19: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|20: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|21: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|22: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|23: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|24: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|25: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|26: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|27: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|28: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|29: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+== style clickdismiss
+|0: 0-44 #969696,#2c2a3e, 45-59 d,#2c2a3e,
+|1: 0-1 d,#383c54, 2-29 #969696,#2c2a3e, 30-31 d,#383c54, 32-59 #969696,#2c2a3e,
+|2: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|3: 0-1 d,#2a2a2a, 2-6 #5a5a5a,d, 7-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|4: 0-1 d,#6e4fd0, 2-29 #969696,#2c2a3e, 30-31 d,#525252, 32-59 d,d,
+|5: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|6: 0-1 d,#525252, 2-6 #969696,#2c2a3e, 7-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|7: 0-1 d,#525252, 2-6 #5a5a5a,d, 7-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|8: 0-1 d,#383c54, 2-29 #969696,#2c2a3e, 30-31 d,#525252, 32-59 d,d,
+|9: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|10: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|11: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|12: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|13: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|14: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|15: 0-1 d,#2a2a2a, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|16: 0-1 d,#383c54, 2-29 #969696,#2c2a3e, 30-31 d,#525252, 32-59 d,d,
+|17: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|18: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|19: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|20: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|21: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|22: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|23: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|24: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|25: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|26: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|27: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|28: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+|29: 0-1 d,#525252, 2-29 d,d, 30-31 d,#525252, 32-59 d,d,
+== snap ttyprobe grid=60x30 cursor=12,15
+|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
+| NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+| 2 line 2
+| 3
+| TTY /tmp/pardes-snap/chordcu
+| $ echo B''OO
+| BOO
+| $ stty -icanon -echo; printf
+| '\x1b[?2004h\x1b[?1000h\x1b
+| [?1006h'; cat -v
+| ^[[<0;8;3M^[[<0;8;3m^[[200~l
+| ine^[[201~
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+== snap ttyprompt grid=60x30 cursor=8,15
+|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
+| NOR /tmp/pardes-snap/chordcu
+| 1 line 1
+| 2 line 2
+| 3
+| TTY /tmp/pardes-snap/chordcu
+| $ stty -icanon -echo; printf
+| '\x1b[?2004h\x1b[?1000h\x1b
+| [?1006h'; cat -v
+| ^[[<0;8;3M^[[<0;8;3m^[[200~l
+| ine^[[201~
+| $
+| $ line
+| NOR /tmp/pardes-snap/chordcu
+|
+| f.txt g.txt
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
+|
diff --git a/test/snapshots/chordcut.snap b/test/snapshots/chordcut.snap
index fc033e39..8243098a 100644
--- a/test/snapshots/chordcut.snap
+++ b/test/snapshots/chordcut.snap
@@ -99,3 +99,49 @@ release right 25 4
wait 10000 line 2
stable 700 10000
snap lookafter
+# a fresh left click dismisses the chord's leftover selection instead of
+# dragging the anchored span to the click: copy `line` on f.txt line 1
+# (styled snap shows the selection), then click the next row (it's gone)
+press left 8 6
+drag left 11 6
+press middle 11 6
+release middle 11 6
+press right 11 6
+release right 11 6
+release left 11 6
+stable 400 5000
+snapstyle chordsel
+press left 8 7
+release left 8 7
+stable 400 5000
+snapstyle clickdismiss
+# tty paste chord: a mouse-listening app gets the click forwarded (SGR,
+# body-relative) then the register between bracketed-paste markers —
+# cat -v prints the received bytes visibly
+press left 5 11
+release left 5 11
+stable 400 5000
+key c-b
+stable 600 8000
+text stty -icanon -echo; printf '\x1b[?2004h\x1b[?1000h\x1b[?1006h'; cat -v
+key enter
+stable 700 8000
+press left 10 12
+press right 10 12
+release right 10 12
+release left 10 12
+stable 700 8000
+snap ttyprobe
+# a plain prompt (mouse modes off) gets no click bytes — just the paste;
+# readline's own 2004 strips the markers and inserts the register text
+key c-c
+stable 600 8000
+text stty sane; printf '\x1b[?1000l\x1b[?1006l'
+key enter
+stable 600 8000
+press left 10 12
+press right 10 12
+release right 10 12
+release left 10 12
+stable 700 8000
+snap ttyprompt