summaryrefslogtreecommitdiff
path: root/src/tty/tty.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/tty/tty.zig')
-rw-r--r--src/tty/tty.zig183
1 files changed, 114 insertions, 69 deletions
diff --git a/src/tty/tty.zig b/src/tty/tty.zig
index ad6f04b0..e8e3c1bd 100644
--- a/src/tty/tty.zig
+++ b/src/tty/tty.zig
@@ -37,7 +37,7 @@ pub const Command = struct {
winsize: vaxis.Winsize,
mouse: vaxis.Mouse,
paste: []const u8,
- /// a language query finished on a worker; rows are gpa-owned
+ /// a language query finished on a worker; rows are lsp-domain-owned
lsp_done: struct { id: u32, rows: []u8 },
/// a selection-filter worker finished; every stdout is gpa-owned
pipe_done: selection_pipe.Response,
@@ -83,20 +83,51 @@ const LspJob = struct {
arg: []u8,
root: []u8,
- fn free(j: *LspJob, gpa: std.mem.Allocator) void {
- gpa.free(j.path);
- gpa.free(j.source);
- gpa.free(j.arg);
- gpa.free(j.root);
- gpa.destroy(j);
+ fn free(j: *LspJob, allocator: std.mem.Allocator) void {
+ allocator.free(j.path);
+ allocator.free(j.source);
+ allocator.free(j.arg);
+ allocator.free(j.root);
+ allocator.destroy(j);
}
};
+const max_pipe_tasks = 16;
+
const PipeTask = struct {
id: u32,
future: std.Io.Future(anyerror!void),
};
+const PipeTasks = struct {
+ items: [max_pipe_tasks]PipeTask = undefined,
+ len: usize = 0,
+
+ fn full(tasks: *const PipeTasks) bool {
+ return tasks.len == tasks.items.len;
+ }
+
+ fn add(tasks: *PipeTasks, task: PipeTask) void {
+ std.debug.assert(!tasks.full());
+ tasks.items[tasks.len] = task;
+ tasks.len += 1;
+ }
+
+ fn finish(tasks: *PipeTasks, io: std.Io, id: u32) void {
+ for (tasks.items[0..tasks.len], 0..) |*task, i| if (task.id == id) {
+ task.future.await(io) catch {};
+ tasks.len -= 1;
+ std.mem.copyForwards(PipeTask, tasks.items[i..tasks.len], tasks.items[i + 1 .. tasks.len + 1]);
+ return;
+ };
+ }
+
+ fn cancelAll(tasks: *PipeTasks, io: std.Io) void {
+ for (tasks.items[0..tasks.len]) |*task| task.future.cancel(io) catch {};
+ tasks.len = 0;
+ }
+};
+
const Pty = struct {
file: std.Io.File,
pid: posix.pid_t,
@@ -338,6 +369,13 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
const io = init.io;
const gpa = init.gpa;
+ const allocs = pardes.allocators.init(gpa);
+ defer pardes.allocators.deinit();
+ var options = opts;
+ options.image_allocator = allocs.image;
+ options.pdf_allocator = allocs.pdf;
+ options.tree_sitter_allocator = allocs.tree_sitter;
+
// SIGWINCH must never run vaxis's signal handler: it posts the winsize
// event through std.Io.Mutex/Condition, and when the signal lands on a
// thread blocked inside an Io.Threaded syscall region (pty readers in
@@ -366,20 +404,25 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
// clicks there (see test/snapshots/ghostty-mid.snap).
try vx.setMouseMode(tty.writer(), true);
- var frame_arena: std.heap.ArenaAllocator = .init(gpa);
- defer frame_arena.deinit();
-
- // stb_image allocator for image panes
- pardes.image.start(io, gpa);
- defer pardes.image.stop();
+ pardes.image.start(io, allocs.image);
+ if (comptime pardes.pdf_enabled) pardes.pdf.start(allocs.pdf);
+ pardes.syntax.start(allocs.tree_sitter);
+ defer {
+ pardes.image.stop();
+ if (comptime pardes.pdf_enabled) pardes.pdf.stop();
+ pardes.syntax.stop();
+ }
- var core = if (opts.load_path) |lp| blk: {
+ var core = if (options.load_path) |lp| blk: {
const bytes = try look.readFile(gpa, lp);
defer gpa.free(bytes);
- break :blk try pardes.Pardes.initFromDump(gpa, opts, bytes);
- } else try pardes.Pardes.init(gpa, opts);
+ break :blk try pardes.Pardes.initFromDump(allocs.pardes, options, bytes);
+ } else try pardes.Pardes.init(allocs.pardes, options);
defer core.deinit();
+ var frame_arena: std.heap.ArenaAllocator = .init(allocs.frame);
+ defer frame_arena.deinit();
+
// shells emit OSC 133 prompt marks via this rc (prompt hiding, click-move)
writeFile(shell_bin.bash_rc_path, shell_bin.bash_rc);
writeFile(shell_bin.fish_rc_path, shell_bin.fish_rc);
@@ -401,7 +444,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
var lsp_task: ?std.Io.Future(anyerror!void) = null;
// Selection filters may overlap: a second submit supersedes the first in
// core without synchronously canceling a possibly slow shell command.
- var pipe_tasks: std.ArrayList(PipeTask) = .empty;
+ var pipe_tasks: PipeTasks = .{};
// One inotify instance for every watched pane, opened here — before any
// thread exists — so the pre-loop effect drain below can already mark the
// file a positional path argument opened. -1 off linux: watchPane goes
@@ -426,8 +469,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
t.cancel(io) catch {};
lsp_task = null;
}
- for (pipe_tasks.items) |*task| task.future.cancel(io) catch {};
- pipe_tasks.deinit(gpa);
+ pipe_tasks.cancelAll(io);
// same contract as the pty readers: cancel unblocks the watcher's
// read, and only then is the fd safe to close
if (watch_task) |*t| {
@@ -442,7 +484,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
.pty_read => |pr| gpa.free(pr.bytes),
.command => |line| gpa.free(line),
.paste => |b| gpa.free(@constCast(b)),
- .lsp_done => |d| gpa.free(d.rows),
+ .lsp_done => |d| allocs.lsp.free(d.rows),
.pipe_done => |response_value| {
var response = response_value;
response.deinit(gpa);
@@ -457,12 +499,12 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
// --nested opted out of the whole mechanism, including being an outer
// instance; so does any failure to bind, and then children simply open
// their own session.
- const sock_fd: c_int = if (opts.nested) -1 else nested.listen();
+ const sock_fd: c_int = if (options.nested) -1 else nested.listen();
defer nested.unlisten(sock_fd);
// Perform the initial spawns BEFORE any worker thread exists: forkpty from
// a multithreaded process can wedge the child before exec.
- drainEffects(core, &ptys, &gens, io, gpa, &loop, &vx, &tty, &lsp_task, &pipe_tasks, inotify_fd, &watches, false);
+ drainEffects(core, &ptys, &gens, io, gpa, allocs.lsp, &loop, &vx, &tty, &lsp_task, &pipe_tasks, inotify_fd, &watches, false);
try loop.start();
defer loop.stop();
@@ -621,7 +663,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
.files_changed => check_files = true,
.lsp_done => |d| {
core.update(.{ .lsp_resp = .{ .id = d.id, .rows = d.rows } });
- gpa.free(d.rows);
+ allocs.lsp.free(d.rows);
// the worker is finished; join it so its future does not
// leak (same contract as pty_eof above)
if (lsp_task) |*t| {
@@ -637,11 +679,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
.outputs = response.outputs,
} });
response.deinit(gpa);
- for (pipe_tasks.items, 0..) |*task, i| if (task.id == response_value.id) {
- task.future.await(io) catch {};
- _ = pipe_tasks.orderedRemove(i);
- break;
- };
+ pipe_tasks.finish(io, response_value.id);
},
}
batch += 1;
@@ -679,7 +717,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
}
}
- drainEffects(core, &ptys, &gens, io, gpa, &loop, &vx, &tty, &lsp_task, &pipe_tasks, inotify_fd, &watches, true);
+ drainEffects(core, &ptys, &gens, io, gpa, allocs.lsp, &loop, &vx, &tty, &lsp_task, &pipe_tasks, inotify_fd, &watches, true);
if (core.quit) break;
// Restore builtin: swap in a core rebuilt from the dump; the live
@@ -691,7 +729,7 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
var o = core.opts;
o.cols = core.screen_w;
o.rows = core.screen_h; // pre-size: dump panes never greet
- const nc = pardes.Pardes.initFromDump(gpa, o, bytes) catch break :blk;
+ const nc = pardes.Pardes.initFromDump(allocs.pardes, o, bytes) catch break :blk;
for (&ptys, 0..) |*slot, pid| if (slot.*) |*pt| {
pt.reader.cancel(io) catch {};
_ = libc.close(pt.file.handle);
@@ -811,14 +849,20 @@ pub fn run(init: std.process.Init, opts: pardes.Options) !void {
// Toggling PETSCII or closing a pane removes its attachment from the
// Surface. Release the terminal-side image then, not merely when that
// numeric pane slot happens to be reused.
- var stale: std.ArrayList(pardes.ImageCacheKey) = .empty;
- defer stale.deinit(gpa);
- var image_iterator = kitty_handles.iterator();
- while (image_iterator.next()) |entry|
- if (!surfaceHasKittyKey(surface, entry.key_ptr.*))
- stale.append(gpa, entry.key_ptr.*) catch {};
- for (stale.items) |key| if (kitty_handles.fetchRemove(key)) |removed|
- vx.freeImage(tty.writer(), removed.value.id);
+ while (true) {
+ var stale: [pardes.MAX_PANES]pardes.ImageCacheKey = undefined;
+ var stale_len: usize = 0;
+ var image_iterator = kitty_handles.iterator();
+ while (image_iterator.next()) |entry| {
+ if (surfaceHasKittyKey(surface, entry.key_ptr.*)) continue;
+ stale[stale_len] = entry.key_ptr.*;
+ stale_len += 1;
+ if (stale_len == stale.len) break;
+ }
+ for (stale[0..stale_len]) |key| if (kitty_handles.fetchRemove(key)) |removed|
+ vx.freeImage(tty.writer(), removed.value.id);
+ if (stale_len < stale.len) break;
+ }
if (surface.cursor) |cur| {
win.showCursor(cur.x, cur.y);
// insert = beam, everything else = the terminal's default shape
@@ -847,11 +891,12 @@ fn drainEffects(
gens: *[pardes.MAX_PANES]u32,
io: std.Io,
gpa: std.mem.Allocator,
+ lsp_allocator: std.mem.Allocator,
loop: *Loop,
vx: *vaxis.Vaxis,
tty: *vaxis.Tty,
lsp_task: *?std.Io.Future(anyerror!void),
- pipe_tasks: *std.ArrayList(PipeTask),
+ pipe_tasks: *PipeTasks,
inotify_fd: c_int,
watches: *[pardes.MAX_PANES]?Watch,
threads_ok: bool,
@@ -944,31 +989,31 @@ fn drainEffects(
// a pane with no file still asks `status` (it is about the backend,
// not the buffer): empty path and source, root from the pane's cwd
const f = pane.file;
- const job = gpa.create(LspJob) catch continue;
+ const job = lsp_allocator.create(LspJob) catch continue;
job.* = .{
.id = q.id,
.kind = q.kind,
.offset = q.offset,
- .path = gpa.dupe(u8, if (f) |ff| ff.path else "") catch {
- gpa.destroy(job);
+ .path = lsp_allocator.dupe(u8, if (f) |ff| ff.path else "") catch {
+ lsp_allocator.destroy(job);
continue;
},
- .source = gpa.dupeZ(u8, if (f) |ff| ff.content else "") catch {
- gpa.free(job.path);
- gpa.destroy(job);
+ .source = lsp_allocator.dupeZ(u8, if (f) |ff| ff.content else "") catch {
+ lsp_allocator.free(job.path);
+ lsp_allocator.destroy(job);
continue;
},
- .arg = gpa.dupe(u8, q.arg.slice()) catch {
- gpa.free(job.path);
- gpa.free(job.source);
- gpa.destroy(job);
+ .arg = lsp_allocator.dupe(u8, q.arg.slice()) catch {
+ lsp_allocator.free(job.path);
+ lsp_allocator.free(job.source);
+ lsp_allocator.destroy(job);
continue;
},
- .root = gpa.dupe(u8, if (f) |ff| (std.fs.path.dirname(ff.path) orelse "/") else pane.cwdSlice()) catch {
- gpa.free(job.path);
- gpa.free(job.source);
- gpa.free(job.arg);
- gpa.destroy(job);
+ .root = lsp_allocator.dupe(u8, if (f) |ff| (std.fs.path.dirname(ff.path) orelse "/") else pane.cwdSlice()) catch {
+ lsp_allocator.free(job.path);
+ lsp_allocator.free(job.source);
+ lsp_allocator.free(job.arg);
+ lsp_allocator.destroy(job);
continue;
},
};
@@ -981,24 +1026,24 @@ fn drainEffects(
old.cancel(io) catch {};
lsp_task.* = null;
}
- lsp_task.* = io.concurrent(lspWorker, .{ gpa, job, loop }) catch {
- job.free(gpa);
+ lsp_task.* = io.concurrent(lspWorker, .{ lsp_allocator, job, loop }) catch {
+ job.free(lsp_allocator);
continue;
};
},
.pipe => |request| {
if (!threads_ok) continue;
+ if (pipe_tasks.full()) {
+ core.update(.{ .pipe_resp = .{ .id = request.id, .success = false, .outputs = &.{} } });
+ continue;
+ }
const view = core.pipeRequest(request.id) orelse continue;
const job = selection_pipe.Job.copy(gpa, view) catch continue;
- pipe_tasks.ensureUnusedCapacity(gpa, 1) catch {
- job.deinit(gpa);
- continue;
- };
const future = io.concurrent(pipeWorker, .{ io, gpa, job, loop }) catch {
job.deinit(gpa);
continue;
};
- pipe_tasks.appendAssumeCapacity(.{ .id = request.id, .future = future });
+ pipe_tasks.add(.{ .id = request.id, .future = future });
},
.watch => |w| {
// starting, the path and the on-disk bytes are read off the core
@@ -1019,14 +1064,14 @@ fn drainEffects(
/// the whole async execution model: the same shape as readPty — do the slow
/// thing on a worker, hand the result to the loop as an event, let the core
/// stay a state machine that never blocks.
-fn lspWorker(gpa: std.mem.Allocator, job: *LspJob, loop: *Loop) anyerror!void {
- defer job.free(gpa);
- var arena: std.heap.ArenaAllocator = .init(gpa);
+fn lspWorker(allocator: std.mem.Allocator, job: *LspJob, loop: *Loop) anyerror!void {
+ defer job.free(allocator);
+ var arena: std.heap.ArenaAllocator = .init(allocator);
defer arena.deinit();
- // the shell owns the result buffer; the backend only ever writes to it
- var out: std.Io.Writer.Allocating = .init(gpa);
+ // The shell owns the result buffer; the backend only ever writes to it.
+ var out: std.Io.Writer.Allocating = .init(allocator);
defer out.deinit();
- pardes.lsp.query(gpa, arena.allocator(), .{
+ pardes.lsp.query(allocator, arena.allocator(), .{
.kind = job.kind,
.path = job.path,
.source = job.source,
@@ -1034,8 +1079,8 @@ fn lspWorker(gpa: std.mem.Allocator, job: *LspJob, loop: *Loop) anyerror!void {
.arg = job.arg,
.root = job.root,
}, &out.writer);
- const rows = gpa.dupe(u8, out.written()) catch return;
- loop.postEvent(.{ .lsp_done = .{ .id = job.id, .rows = rows } }) catch gpa.free(rows);
+ const rows = allocator.dupe(u8, out.written()) catch return;
+ loop.postEvent(.{ .lsp_done = .{ .id = job.id, .rows = rows } }) catch allocator.free(rows);
return;
}