summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.zig10
-rw-r--r--src/tty.zig28
2 files changed, 32 insertions, 6 deletions
diff --git a/src/main.zig b/src/main.zig
index ff151323..9a8cd6da 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -18,6 +18,16 @@ pub const std_options_debug_io: std.Io = if (is_emscripten)
else
std_options_debug_threaded_io.?.io();
+// A panic must restore the terminal (cooked mode, main screen, mouse off)
+// before the trace prints, or it lands garbled in a raw alt screen. recover()
+// no-ops unless the vaxis tty is live, so gui/tty share the handler.
+pub const panic = if (is_emscripten) std.debug.FullPanic(std.debug.defaultPanic) else std.debug.FullPanic(struct {
+ fn call(msg: []const u8, ret_addr: ?usize) noreturn {
+ @import("vaxis").recover();
+ std.debug.defaultPanic(msg, ret_addr);
+ }
+}.call);
+
const help_text =
\\Usage: pardes [options]
\\
diff --git a/src/tty.zig b/src/tty.zig
index d9821343..f2b36ed4 100644
--- a/src/tty.zig
+++ b/src/tty.zig
@@ -40,6 +40,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
var tty_buf: [0x10000]u8 = undefined;
var tty = try vaxis.Tty.init(io, &tty_buf);
+ defer tty.deinit(); // restore cooked termios LAST, after vx flushed its resets
var vx = try vaxis.init(io, gpa, init.environ_map, .{ .system_clipboard_allocator = gpa });
defer vx.deinit(gpa, tty.writer());
try vx.enterAltScreen(tty.writer());
@@ -65,14 +66,26 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
var kitty_handles: [pardes.MAX_PANES]?vaxis.Image = @splat(null);
var ptys: [pardes.MAX_PANES]?Pty = @splat(null);
- defer for (&ptys) |*slot| if (slot.*) |*pt| {
- _ = linux.close(pt.file.handle);
- slot.* = null;
- };
+ var loop: Loop = .init(io, &tty, &vx);
+ defer {
+ // reap the reader tasks (cancel interrupts a blocked read) before
+ // closing the masters — the runtime joins those threads on exit and a
+ // reader stuck in read(2) would hang the process — then drain the
+ // queue: leftover events own gpa bytes and would dump as leaks.
+ for (&ptys) |*slot| if (slot.*) |*pt| {
+ pt.reader.cancel(io) catch {};
+ _ = linux.close(pt.file.handle);
+ slot.* = null;
+ };
+ while (loop.tryEvent() catch null) |ev| switch (ev) {
+ .pty_read => |pr| gpa.free(pr.bytes),
+ .paste => |b| gpa.free(@constCast(b)),
+ else => {},
+ };
+ }
// Perform the initial spawns BEFORE any worker thread exists: forkpty from
// a multithreaded process can wedge the child before exec.
- var loop: Loop = .init(io, &tty, &vx);
drainEffects(core, &ptys, io, gpa, &loop, &vx, &tty, false);
try loop.start();
@@ -104,6 +117,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
},
.pty_eof => |id| {
if (ptys[id]) |*pt| {
+ pt.reader.await(io) catch {}; // reader just finished; join it or its future leaks
_ = linux.close(pt.file.handle);
ptys[id] = null;
}
@@ -301,7 +315,9 @@ fn readPty(io: std.Io, gpa: std.mem.Allocator, pty: std.Io.File, id: usize, loop
break;
};
}
- loop.postEvent(.{ .pty_eof = id }) catch {};
+ // non-blocking: a teardown cancel only unblocks one wait, so a blocking
+ // post into a full queue here could hang the exit
+ _ = loop.tryPostEvent(.{ .pty_eof = id }) catch {};
}
/// The effective codepoint the way vaxis Key.matches sees it: a single-char