summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 20:23:31 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:17 -0300
commit68dd567218505838814d73473460a0c0ff8e97b3 (patch)
treebabe029d3940c9dd6f7ef7b385086f2fabefe6aa /src
parentf8fe738416fe3a40a7136f2cb5528e8fd57f40df (diff)
parentf99806de546c837ab6f2d5b1e3e8accc95e63a7e (diff)
downloadpardes-68dd567218505838814d73473460a0c0ff8e97b3.tar.gz
pardes-68dd567218505838814d73473460a0c0ff8e97b3.zip
Release Pardes 0.12
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src')
-rw-r--r--src/CHANGELOG.md25
-rw-r--r--src/gui/gui.zig12
-rw-r--r--src/mouse.zig47
3 files changed, 73 insertions, 11 deletions
diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md
index 0d9c42f4..ae796a4d 100644
--- a/src/CHANGELOG.md
+++ b/src/CHANGELOG.md
@@ -1,5 +1,30 @@
# Changelog
+## 0.0.12
+
+- A selection deep in a very long line no longer slows every frame: a 5 MB
+ line with the selection at its end redraws in milliseconds, not seconds.
+- `Joincol` is refused when the joined column cannot give each pane its tag
+ and 2 rows. Save on a PDF or image pane is refused. A builtin made for
+ one kind of pane (Petscii, PdfTint, Togglettymode, …) is refused on any
+ other; `Mode` and `TreeContext` say why when they cannot act.
+- After `ThemeFile`, `/ctl`, DumpConfig and a Dump read `ThemeFile <path>`.
+ DumpConfig's unsupported settings are `# … unsupported` comments, so the
+ whole report writes back to `/ctl`. Every bad setting value names what
+ the setting takes.
+- A refused write to `dot` changes nothing; an edit that changes nothing
+ makes no undo step; the jump list never holds one place twice in a row;
+ `Jumplist` refills its one `+Jumps`; a look miss cuts a long path in its
+ middle.
+- A look past a PDF's last page is a miss; PdfSections on a PDF with no
+ outline says so; a Rename that finds nothing says so and fails; a theme
+ file error names its path; Recent keeps PDFs and images, and an open
+ file's row is where its dot is now.
+- Over a program that tracks the mouse, B2 and B3 are pardes's execute and
+ look again, sweeps and chords included; Shift-B2 and Shift-B3 go to the
+ program as its buttons 2 and 3. B1 and the wheel stay the program's, and
+ Shift keeps them pardes's.
+
## 0.0.11
- A look on a diff names a file as the repository does: git's `a/`/`b/`
diff --git a/src/gui/gui.zig b/src/gui/gui.zig
index 1e58d3ba..ea0e2862 100644
--- a/src/gui/gui.zig
+++ b/src/gui/gui.zig
@@ -5042,7 +5042,7 @@ test "a wheel's notch is whole lines, a touchpad's fraction is not" {
try std.testing.expectEqual(@as(?i32, null), notchOf(std.math.nan(f32), 1));
}
-test "a click over codex in a terminal reaches its pty with its modifiers; with Shift it is pardes's" {
+test "a click over codex in a terminal reaches its pty with its modifiers; Shift swaps B1 to pardes and B2 to the program" {
if (comptime !pardes.panes.terminal.enabled) return error.SkipZigTest;
const gpa = std.testing.allocator;
const core = try pardes.Pardes.init(gpa, .{ .cols = 60, .rows = 16, .tty_only = true });
@@ -5077,9 +5077,17 @@ test "a click over codex in a terminal reaches its pty with its modifiers; with
try std.testing.expectEqualStrings("\x1b[<32;7;4M", try take(core, &sent));
in.post(.{ .mouse = pointerMouse(.left, .release, next, .{}) });
try std.testing.expectEqualStrings("\x1b[<0;7;4m", try take(core, &sent));
+ // Shift-B1 is pardes's select; B2 is pardes's execute and Shift-B2
+ // the program's, reported without the Shift.
+ in.post(.{ .mouse = pointerMouse(.left, .press, mc, .{ .shift = true }) });
+ in.post(.{ .mouse = pointerMouse(.left, .release, mc, .{ .shift = true }) });
+ try std.testing.expectEqualStrings("", try take(core, &sent));
+ in.post(.{ .mouse = pointerMouse(.middle, .press, mc, .{}) });
+ in.post(.{ .mouse = pointerMouse(.middle, .release, mc, .{}) });
+ try std.testing.expectEqualStrings("", try take(core, &sent));
in.post(.{ .mouse = pointerMouse(.middle, .press, mc, .{ .shift = true }) });
in.post(.{ .mouse = pointerMouse(.middle, .release, mc, .{ .shift = true }) });
- try std.testing.expectEqualStrings("", try take(core, &sent));
+ try std.testing.expectEqualStrings("\x1b[<1;6;4M\x1b[<1;6;4m", try take(core, &sent));
}
test "a wheel over codex in a terminal reaches its pty as the mouse reports it asked for" {
diff --git a/src/mouse.zig b/src/mouse.zig
index ecb9bb57..41362249 100644
--- a/src/mouse.zig
+++ b/src/mouse.zig
@@ -335,7 +335,14 @@ fn programWheel(p: *Pardes, id: usize, m: Mouse, col: u16, row: u16) bool {
return true;
}
-pub const Grab = struct { id: usize, serial: u32, held: u3 = 0 };
+pub const Grab = struct {
+ id: usize,
+ serial: u32,
+ held: u3 = 0,
+ /// Taken by Shift-B2 or Shift-B3: the Shift chose the program, so its
+ /// reports leave it out.
+ unshift: bool = false,
+};
/// The 0-based cell of a terminal program's grid under screen cell
/// `col`,`row`, or null over chrome (tag, gutter, scrollbar, anything past
@@ -360,8 +367,10 @@ fn tracksMouse(pane: *const Pane) bool {
/// it: presses, releases, drags with a button down (1002) and, under 1003,
/// every move, once per cell. A press it took keeps its drag and release
/// wherever the pointer goes. Tags, gutters and the rest of pardes's
-/// chrome are never its, and Shift keeps pardes's own select, execute and
-/// look. True when the event was the program's.
+/// chrome are never its. B1 is the program's and Shift-B1 pardes's select;
+/// B2 and B3 are pardes's execute and look, and Shift-B2 and Shift-B3 the
+/// program's, reported without the Shift that chose it. True when the
+/// event was the program's.
fn programMouse(p: *Pardes, m: Mouse) bool {
const button: ?panes.terminal.MouseButton = switch (m.button) {
.left => .left,
@@ -372,9 +381,10 @@ fn programMouse(p: *Pardes, m: Mouse) bool {
};
const bit: u3 = if (button) |b| @as(u3, 1) << @intCast(@intFromEnum(b)) else 0;
const at = p.presentation.pointer(p.screen_w, p.screen_h, @min(m.col, p.screen_w -| 1), @min(m.row, p.screen_h -| 1));
- const mods: panes.terminal.MouseMods = .{ .shift = m.shift, .alt = m.alt, .ctrl = m.ctrl };
+ var mods: panes.terminal.MouseMods = .{ .shift = m.shift, .alt = m.alt, .ctrl = m.ctrl };
var buf: [64]u8 = undefined;
if (p.mouse_grab) |*grab| {
+ if (grab.unshift) mods.shift = false;
const pane = p.panes[grab.id] orelse {
p.mouse_grab = null;
return false;
@@ -400,7 +410,10 @@ fn programMouse(p: *Pardes, m: Mouse) bool {
if (grab.held == 0) p.mouse_grab = null;
return true;
}
- if (m.shift or p.drag != .none or p.header_drag or p.header_sweep != null) return false;
+ if (p.drag != .none or p.header_drag or p.header_sweep != null) return false;
+ const shifted_to_program = m.button == .middle or m.button == .right;
+ if (m.shift != shifted_to_program) return false;
+ if (shifted_to_program) mods.shift = false;
const mapped = at orelse return false;
const id = paneAt(p, mapped.col, mapped.row) orelse return false;
const pane = p.panes[id].?;
@@ -415,7 +428,7 @@ fn programMouse(p: *Pardes, m: Mouse) bool {
p.active = id;
pane.focus = .body;
pane.closePrompt();
- p.mouse_grab = .{ .id = id, .serial = pane.serial, .held = bit };
+ p.mouse_grab = .{ .id = id, .serial = pane.serial, .held = bit, .unshift = shifted_to_program };
const bytes = panes.terminal.mouseBytes(pane, &buf, .press, button, mods, cell.col, cell.row, true);
if (bytes.len > 0) p.emitWrite(id, bytes);
return true;
@@ -2033,9 +2046,17 @@ test "a program tracking the mouse gets its clicks, drags and moves over its gri
// 1000 has no drags; the release is reported where it lands.
try std.testing.expectEqualStrings("", try send(p, &out, col, row, .{ .button = .left, .kind = .drag, .dx = 1 }));
try std.testing.expectEqualStrings("\x1b[<0;7;4m", try send(p, &out, col, row, .{ .button = .left, .kind = .release, .dx = 1 }));
- // Right with meta and ctrl: 2 + 8 + 16.
- try std.testing.expectEqualStrings("\x1b[<26;6;4M", try send(p, &out, col, row, .{ .button = .right, .kind = .press, .alt = true, .ctrl = true }));
- try std.testing.expectEqualStrings("\x1b[<2;6;4m", try send(p, &out, col, row, .{ .button = .right, .kind = .release }));
+ // B2 and B3 are pardes's execute and look, as in any pane.
+ try std.testing.expectEqualStrings("", try send(p, &out, col, row, .{ .button = .right, .kind = .press }));
+ try std.testing.expect(p.drag == .select);
+ try std.testing.expectEqualStrings("", try send(p, &out, col, row, .{ .button = .right, .kind = .release }));
+ try std.testing.expect(p.drag == .none);
+ // Shift-B3 is the program's, the Shift that chose it left out: meta
+ // and ctrl, 2 + 8 + 16.
+ try std.testing.expectEqualStrings("\x1b[<26;6;4M", try send(p, &out, col, row, .{ .button = .right, .kind = .press, .shift = true, .alt = true, .ctrl = true }));
+ try std.testing.expectEqualStrings("\x1b[<2;6;4m", try send(p, &out, col, row, .{ .button = .right, .kind = .release, .shift = true }));
+ try std.testing.expectEqualStrings("\x1b[<1;6;4M", try send(p, &out, col, row, .{ .button = .middle, .kind = .press, .shift = true }));
+ try std.testing.expectEqualStrings("\x1b[<1;6;4m", try send(p, &out, col, row, .{ .button = .middle, .kind = .release }));
// Moves are no 1000 program's business: pardes hovers.
try std.testing.expectEqualStrings("", try send(p, &out, col, row, .{ .button = .none, .kind = .motion }));
// Shift: pardes's own sweep, the program hears nothing of it.
@@ -2066,6 +2087,14 @@ test "a program tracking the mouse gets its clicks, drags and moves over its gri
try std.testing.expect(p.mouse_grab == null);
// Over the tag a bare move is pardes's.
try std.testing.expectEqualStrings("", try send(p, &out, col, tag_row, .{ .button = .none, .kind = .motion }));
+ // B2 held for pardes: its sweep's moves are not the program's; held
+ // for the program with Shift, they are its button-2 drags.
+ try std.testing.expectEqualStrings("", try send(p, &out, col, row, .{ .button = .middle, .kind = .press }));
+ try std.testing.expectEqualStrings("", try send(p, &out, col, row, .{ .button = .middle, .kind = .drag, .dx = 1 }));
+ try std.testing.expectEqualStrings("", try send(p, &out, col, row, .{ .button = .middle, .kind = .release, .dx = 1 }));
+ try std.testing.expectEqualStrings("\x1b[<1;6;4M", try send(p, &out, col, row, .{ .button = .middle, .kind = .press, .shift = true }));
+ try std.testing.expectEqualStrings("\x1b[<33;7;4M", try send(p, &out, col, row, .{ .button = .middle, .kind = .drag, .dx = 1, .shift = true }));
+ try std.testing.expectEqualStrings("\x1b[<1;7;4m", try send(p, &out, col, row, .{ .button = .middle, .kind = .release, .dx = 1, .shift = true }));
// Legacy X10 bytes: a release is button 3. (Turning 1003 off turns
// tracking off, as xterm's one tracking mode goes.)