summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-06 13:09:16 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commita9bc331ab74f12b6b11fa6578d829d0da50ed636 (patch)
tree15a142314ba0a44c623e423502a26a6208250a02 /src
parentde4def3548a6729b0dfd2120495a61beef8c8c2c (diff)
downloadpardes-a9bc331ab74f12b6b11fa6578d829d0da50ed636.tar.gz
pardes-a9bc331ab74f12b6b11fa6578d829d0da50ed636.zip
fix drag-select release crash: the 64-slot effect ring overflowed when one update emitted a big selection exec (emitWrite 64-byte chunks + padOutputBelowEdits CRs) and the emit assert panicked. Effects queue is now a growable ArrayList FIFO (payloads stay inline values); guard the two other Buf(256) asserts reachable from look (spawn cwd >256 -> error.PathTooLong in newShell, >256-char url skipped). New snap exec-bigdrag: giant middle-drag exec on a 60x200 grid, panicked before, alive-marker golden after.
Diffstat (limited to 'src')
-rw-r--r--src/pardes.zig22
1 files changed, 14 insertions, 8 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index c4893b25..d617e552 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -628,9 +628,11 @@ pub const Pardes = struct {
/// press during an active middle select-drag; consumed on middle release
chord_arg: ?[]u8 = null,
- effects: [64]Effect = undefined,
+ /// pending effects, drained by the shell after each update. Growable: one
+ /// update can emit unbounded writes (a big selection executed is chunked
+ /// into 64-byte write effects) — a fixed ring overflows and panics.
+ effects: std.ArrayList(Effect) = .empty,
effects_head: usize = 0,
- effects_tail: usize = 0,
/// modal yank register (gpa-owned); a yank also mirrors out to the system
/// clipboard via the set_clipboard effect.
@@ -678,6 +680,7 @@ pub const Pardes = struct {
if (p.yank) |y| gpa.free(y);
if (p.dump_out) |d| gpa.free(d);
if (p.chord_arg) |a| gpa.free(a);
+ p.effects.deinit(gpa);
p.scratch.deinit();
gpa.free(p.surface.cells);
gpa.destroy(p);
@@ -717,6 +720,7 @@ pub const Pardes = struct {
fn newShell(p: *Pardes, id: usize, cwd: []const u8) !*Pane {
std.debug.assert(p.panes[id] == null);
+ if (cwd.len > 256) return error.PathTooLong; // spawn effect cwd is a Buf(256)
const pane = try p.gpa.create(Pane);
pane.* = .{
.vt = try ghostty_vt.Terminal.init(p.gpa, .{
@@ -780,14 +784,16 @@ pub const Pardes = struct {
}
fn emit(p: *Pardes, e: Effect) void {
- std.debug.assert(p.effects_tail - p.effects_head < p.effects.len);
- p.effects[p.effects_tail % p.effects.len] = e;
- p.effects_tail += 1;
+ p.effects.append(p.gpa, e) catch {};
}
pub fn nextEffect(p: *Pardes) ?Effect {
- if (p.effects_head == p.effects_tail) return null;
- const e = p.effects[p.effects_head % p.effects.len];
+ if (p.effects_head == p.effects.items.len) {
+ p.effects.clearRetainingCapacity();
+ p.effects_head = 0;
+ return null;
+ }
+ const e = p.effects.items[p.effects_head];
p.effects_head += 1;
return e;
}
@@ -2607,7 +2613,7 @@ pub const Pardes = struct {
};
switch (look.resolve(txt, cwd, &realbuf)) {
.none => {},
- .url => |u| p.emit(.{ .open_link = .from(u) }),
+ .url => |u| if (u.len <= 256) p.emit(.{ .open_link = .from(u) }),
.dir => |dir| {
// focus an existing terminal on this dir, else fork one below
for (p.panes, 0..) |slot, i| {