diff options
Diffstat (limited to 'src/tty/tty.zig')
| -rw-r--r-- | src/tty/tty.zig | 152 |
1 files changed, 32 insertions, 120 deletions
diff --git a/src/tty/tty.zig b/src/tty/tty.zig index e8a36035..ca263f90 100644 --- a/src/tty/tty.zig +++ b/src/tty/tty.zig @@ -86,63 +86,14 @@ pub const Command = struct { }; const Loop = vaxis.Loop(@TypeOf(Command.value)); -/// One language query, owned by the worker that runs it. Everything the -/// backend may read is copied in here before the worker starts: the core goes -/// on editing the moment the effect is drained, so a borrowed slice would be a -/// use-after-free the length of one keystroke. -const LspJob = struct { - id: u32, - kind: pardes.lsp.Kind, - offset: u32, - path: []u8, - source: [:0]u8, - arg: []u8, - root: []u8, +/// The shared snapshot/worker pair every native shell uses. This file used to +/// carry its own `LspJob` and gui.zig carried a copy of it; the copies said so. +const lsp_host = @import("../lsp_host.zig"); - 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; - } -}; +/// The pipe in-flight set moved to `selection_pipe.Tasks`, beside the Job it +/// tracks: gui.zig carried this same table verbatim. +const PipeTask = selection_pipe.Tasks.Task; +const PipeTasks = selection_pipe.Tasks; const Pty = struct { file: std.Io.File, @@ -655,13 +606,10 @@ fn localSession( } else try pardes.Pardes.init(allocs.pardes, options); defer core.deinit(); - // Private, complete before any fork and retained until the last possible - // spawn; children borrow only these stable in-struct path buffers. - var prompt_rcs = shell_bin.PromptRcs.init(); + // PATH, the bash banner and the prompt rc files, in the one order that + // works. Children borrow only these stable in-struct path buffers. + var prompt_rcs = shell_bin.prepareForFork(); defer prompt_rcs.deinit(); - // macos: apple's bash 3.2 prints the zsh-deprecation banner into every - // pane unless this is in the env BEFORE bash starts (the rc is too late) - if (comptime builtin.os.tag.isDarwin()) _ = setenv("BASH_SILENCE_DEPRECATION_WARNING", "1", 1); var frame_arena: std.heap.ArenaAllocator = .init(allocs.frame); defer frame_arena.deinit(); @@ -1527,39 +1475,7 @@ const Shell = struct { fn lsp(ctx: ?*anyopaque, req: host_api.LspRequest) void { const s = of(ctx); if (!s.threads_ok) return; // pre-loop drain: nothing to answer to yet - const pane = s.core.panes[req.pane] orelse return; - // 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 a = s.lsp_gpa; - const job = a.create(LspJob) catch return; - job.* = .{ - .id = req.id, - .kind = req.kind, - .offset = req.offset, - .path = a.dupe(u8, if (f) |ff| ff.path else "") catch { - a.destroy(job); - return; - }, - .source = a.dupeZ(u8, if (f) |ff| ff.content else "") catch { - a.free(job.path); - a.destroy(job); - return; - }, - .arg = a.dupe(u8, req.arg) catch { - a.free(job.path); - a.free(job.source); - a.destroy(job); - return; - }, - .root = a.dupe(u8, if (f) |ff| (std.fs.path.dirname(ff.path) orelse "/") else pane.cwdSlice()) catch { - a.free(job.path); - a.free(job.source); - a.free(job.arg); - a.destroy(job); - return; - }, - }; + const job = lsp_host.snapshot(s.lsp_gpa, s.core, req) orelse return; // ponytail: ONE query in flight, so one future slot. Replacing it // cancels-then-joins the previous worker, which for a backend that // ignores cancellation means waiting out a query the user already @@ -1569,8 +1485,8 @@ const Shell = struct { old.cancel(s.io) catch {}; s.lsp_task = null; } - s.lsp_task = s.io.concurrent(lspWorker, .{ a, job, s.loop }) catch { - job.free(a); + s.lsp_task = s.io.concurrent(lspWorker, .{ s.lsp_gpa, job, s.loop }) catch { + job.free(s.lsp_gpa); return; }; } @@ -1588,7 +1504,9 @@ const Shell = struct { job.deinit(s.gpa); return; }; - s.pipe_tasks.add(.{ .id = id, .future = future }); + // `full()` was checked above, so this cannot fail; assert rather than + // discard, because a silently dropped task is a future nobody joins. + std.debug.assert(s.pipe_tasks.add(.{ .id = id, .future = future })); } }; @@ -1617,30 +1535,24 @@ fn requestClipboard(vx: *vaxis.Vaxis, tty: *vaxis.Tty) void { vx.requestSystemClipboard(tty.writer()) catch {}; } -/// Answer a language query off the event loop and post the rows back. This is -/// 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(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(allocator); - defer out.deinit(); - pardes.lsp.query(allocator, arena.allocator(), .{ - .kind = job.kind, - .path = job.path, - .source = job.source, - .offset = job.offset, - .arg = job.arg, - .root = job.root, - }, &out.writer); - const rows = allocator.dupe(u8, out.written()) catch return; - loop.postEvent(.{ .lsp_done = .{ .id = job.id, .rows = rows } }) catch allocator.free(rows); - return; +/// Answer a language query off the event loop and post the rows back. The +/// snapshot and the query body are `lsp_host`'s; the only part that is this +/// shell's is the vaxis event the rows travel home on. +fn lspWorker(allocator: std.mem.Allocator, job: *lsp_host.Job, loop: *Loop) anyerror!void { + var sink: LspRowSink = .{ .allocator = allocator, .loop = loop }; + lsp_host.work(allocator, job, &sink, LspRowSink.take); } +const LspRowSink = struct { + allocator: std.mem.Allocator, + loop: *Loop, + + fn take(ctx: ?*anyopaque, id: u32, rows: []u8) void { + const s: *LspRowSink = @ptrCast(@alignCast(ctx orelse return)); + s.loop.postEvent(.{ .lsp_done = .{ .id = id, .rows = rows } }) catch s.allocator.free(rows); + } +}; + /// The registered `lsp.setStatusSink` target, called from the protocol /// client's READER threads. Only thread-safe, NON-BLOCKING things happen /// here: a dupe with the concurrent lsp allocator and a TRY-post onto the |
