diff options
Diffstat (limited to 'src/gui')
| -rw-r--r-- | src/gui/gui.zig | 221 |
1 files changed, 54 insertions, 167 deletions
diff --git a/src/gui/gui.zig b/src/gui/gui.zig index 8442c197..44e28125 100644 --- a/src/gui/gui.zig +++ b/src/gui/gui.zig @@ -930,25 +930,10 @@ const Msg = union(enum) { } }; -/// One language query, owned by the thread running it — the gui twin of -/// tty.zig's LspJob, and copied for the same reason: the core edits on. -const LspJob = struct { - id: u32, - kind: pardes.lsp.Kind, - offset: u32, - path: []u8, - source: [:0]u8, - 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); - } -}; +/// The shared snapshot/worker pair. This file carried its own `LspJob` with +/// "tty.zig's LspJob, and copied for the same reason" over the top; both copies +/// are now one module, and the AppKit shell — which had neither — uses it too. +const lsp_host = @import("../lsp_host.zig"); const LspWorkers = struct { active: std.atomic.Value(usize) = .init(0), @@ -972,36 +957,10 @@ const LspWorkers = struct { 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 add(tasks: *PipeTasks, task: PipeTask) bool { - if (tasks.len == tasks.items.len) return false; - tasks.items[tasks.len] = task; - tasks.len += 1; - return true; - } - - 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; - } -}; +/// Moved to `selection_pipe.Tasks`, beside the Job it tracks — tty.zig carried +/// this same table verbatim. +const PipeTask = selection_pipe.Tasks.Task; +const PipeTasks = selection_pipe.Tasks; const queue_capacity = 512; @@ -1192,27 +1151,18 @@ fn lookThread(gpa: std.mem.Allocator, fd: c_int, q: *Queue) void { } } -/// Answer a language query off the render loop and push the rows to the queue -/// — the async execution model, spelled in the plumbing this shell already has -/// (a detached thread and the mutex queue the pty readers use). -fn lspThread(lsp_allocator: std.mem.Allocator, workers: *LspWorkers, job: *LspJob, q: *Queue) void { +/// Answer a language query off the render loop and push the rows to the queue. +/// The snapshot and the query body are `lsp_host`'s; what stays here is this +/// shell's own plumbing — a detached thread, the refcount that teardown joins +/// on, and the mutex queue the pty readers already use. +fn lspThread(lsp_allocator: std.mem.Allocator, workers: *LspWorkers, job: *lsp_host.Job, q: *Queue) void { defer workers.finish(); - defer job.free(lsp_allocator); - var arena: std.heap.ArenaAllocator = .init(lsp_allocator); - defer arena.deinit(); - // the shell owns the result buffer; the backend only ever writes to it - var out: std.Io.Writer.Allocating = .init(lsp_allocator); - defer out.deinit(); - pardes.lsp.query(lsp_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 = lsp_allocator.dupe(u8, out.written()) catch return; - q.push(.{ .lsp = .{ .id = job.id, .rows = rows } }); + lsp_host.work(lsp_allocator, job, q, pushLspRows); +} + +fn pushLspRows(ctx: ?*anyopaque, id: u32, rows: []u8) void { + const q: *Queue = @ptrCast(@alignCast(ctx orelse return)); + q.push(.{ .lsp = .{ .id = id, .rows = rows } }); } /// Copy the query out of the core and hand it to a thread. A detached thread @@ -1220,38 +1170,7 @@ fn lspThread(lsp_allocator: std.mem.Allocator, workers: *LspWorkers, job: *LspJo /// already tolerates a late push after close. fn spawnLsp(core: *pardes.Pardes, q: *Queue, e: host_api.LspRequest) void { const lsp_allocator = q.lsp_allocator; - const pane = core.panes[e.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 job = lsp_allocator.create(LspJob) catch return; - job.* = .{ - .id = e.id, - .kind = e.kind, - .offset = e.offset, - .path = lsp_allocator.dupe(u8, if (f) |ff| ff.path else "") catch { - lsp_allocator.destroy(job); - return; - }, - .source = lsp_allocator.dupeZ(u8, if (f) |ff| ff.content else "") catch { - lsp_allocator.free(job.path); - lsp_allocator.destroy(job); - return; - }, - .arg = lsp_allocator.dupe(u8, e.arg) catch { - lsp_allocator.free(job.path); - lsp_allocator.free(job.source); - lsp_allocator.destroy(job); - return; - }, - .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); - return; - }, - }; + const job = lsp_host.snapshot(lsp_allocator, core, e) orelse return; q.lsp_workers.start(); const th = std.Thread.spawn(.{}, lspThread, .{ lsp_allocator, q.lsp_workers, job, q }) catch { q.lsp_workers.finish(); @@ -1582,13 +1501,19 @@ fn compactTaglineLayout(g: *const Gui, origin_col: f32) CellLayout { }; } -/// Where a tagline cell's compact band begins. `core` is null in an attached -/// window, and then EVERY tagline cell takes the last line's fallback: the -/// wire carries cells, not the pane rects that placed them, so there is no -/// band origin to compact against. That is the same answer `gridCellAtDimensions` -/// reaches for the same reason, which is what keeps the two honest — a click -/// lands on the glyph it was aimed at, because both sides map through the body -/// grid. The visible cost is one tagline row's worth of loose tracking. +/// Where a tagline cell's compact band begins. The origin rule itself is +/// `pardes.taglineOriginCol` — moved to the core so the AppKit shell can call +/// the SAME rule over the C ABI instead of advancing its tag rows on body +/// pitch, which is the second copy of this that already went wrong once (see +/// `taglineBandOffset`). +/// +/// `core` is null in an attached window, and then EVERY tagline cell takes the +/// last line's fallback: the wire carries cells, not the pane rects that placed +/// them, so there is no band origin to compact against. That is the same answer +/// `gridCellAtDimensions` reaches for the same reason, which is what keeps the +/// two honest — a click lands on the glyph it was aimed at, because both sides +/// map through the body grid. The visible cost is one tagline row's worth of +/// loose tracking. fn taglineLayoutForCell( g: *const Gui, core: ?*const pardes.Pardes, @@ -1597,33 +1522,15 @@ fn taglineLayoutForCell( track: ?pardes.panel_animation.Track, ) CellLayout { if (row < pardes.TOPBAR_H) return compactTaglineLayout(g, 0); - if (core) |p| { - if (track) |active| { - const box = active.contentBox(); - const tag_y = if (p.settings.tag_bottom) box.y + box.h - @as(f32, @floatFromInt(pardes.BOX_H)) else box.y; - if (@as(f32, @floatFromInt(row)) >= tag_y and - @as(f32, @floatFromInt(row)) < tag_y + @as(f32, @floatFromInt(pardes.BOX_H))) - return compactTaglineLayout(g, box.x); - } - for (p.panes, 0..) |slot, id| { - if (slot == null) continue; - const r = p.rects[id]; - const tag_y = if (p.settings.tag_bottom) r.y + r.h -| pardes.BOX_H else r.y; - if (row == tag_y and col >= r.x and col < r.x + r.w) - return compactTaglineLayout(g, @floatFromInt(r.x)); - } - } - // A stale/closing cell without a live pane should still remain legible, - // and so should every cell of an attached window. Its body-grid origin is - // the only safe fallback available. - return compactTaglineLayout(g, @floatFromInt(col)); + const p = core orelse return compactTaglineLayout(g, @floatFromInt(col)); + return compactTaglineLayout(g, pardes.taglineOriginCol(p, col, row, track)); } -fn boxContains(box: pardes.panel_animation.Box, col: u16, row: u16) bool { - const x: f32 = @floatFromInt(col); - const y: f32 = @floatFromInt(row); - return x >= box.x and x < box.x + box.w and y >= box.y and y < box.y + box.h; -} +/// `panel_animation.Box.contains` under this file's older name. Kept as an +/// alias rather than renamed at three call sites so the predicate has exactly +/// one definition — it was a fourth copy of the same half-open cell test the +/// core, `taglineOriginCol` and ScenePostprocessor.swift all make. +const boxContains = pardes.panel_animation.Box.contains; // EFFECT_CODE_PANEL_HOST_BEGIN const PaintBatch = struct { @@ -2077,13 +1984,10 @@ fn localSession( observeGuiFont(g, core); syncTaglineFont(g, core); - // 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 ptys: [pardes.MAX_PANES]?Pty = @splat(null); // per-slot spawn generation: drops a dead shell's late output/eof when its @@ -2549,11 +2453,8 @@ fn runGrid(init: std.process.Init, opts_in: pardes.Options) !void { if (opts.load_path == null) core.update(.{ .resize = .{ .cols = grid_cols, .rows = grid_rows } }); - var prompt_rcs = shell_bin.PromptRcs.init(); + 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 ptys: [pardes.MAX_PANES]?Pty = @splat(null); // per-slot spawn generation: drops a dead shell's late output/eof when its @@ -3420,11 +3321,13 @@ fn pixelCell(px: f32, cell: u32) u16 { return @intFromFloat(@min(idx, 10_000)); } -/// Which grid cell a physical point is in. `core` is null in an attached -/// window, and then the pane loop is skipped and the body grid answers — the -/// same fallback `taglineLayoutForCell` takes for the same missing fact, which -/// is what makes a click on an attached tagline land on the glyph it was aimed -/// at. +/// Which grid cell a physical point is in. The COLUMN rule is +/// `pardes.gridColAt` — the inverse of the compact tagline layout, and in the +/// core beside it so the two cannot be compacted independently. `core` is null +/// in an attached window, and then the pane loop inside it is skipped and the +/// body grid answers: the same fallback `taglineLayoutForCell` takes for the +/// same missing fact, which is what makes a click on an attached tagline land +/// on the glyph it was aimed at. fn gridCellAtDimensions( core: ?*const pardes.Pardes, x: f32, @@ -3433,25 +3336,8 @@ fn gridCellAtDimensions( body_h: f32, tagline_w: f32, ) MouseCell { - const safe_body_w = @max(body_w, 1); - const safe_body_h = @max(body_h, 1); - const tag_w = @max(tagline_w, 1); - const row: u16 = @intFromFloat(@min(@floor(@max(y, 0) / safe_body_h), 10_000)); - if (row < pardes.TOPBAR_H) - return .{ .col = @intFromFloat(@min(@floor(@max(x, 0) / tag_w), 10_000)), .row = row }; - - if (core) |p| for (p.panes, 0..) |slot, id| { - if (slot == null) continue; - const r = p.rects[id]; - const tag_y = if (p.settings.tag_bottom) r.y + r.h -| pardes.BOX_H else r.y; - if (row != tag_y or r.w == 0) continue; - const left = @as(f32, @floatFromInt(r.x)) * safe_body_w; - const right = @as(f32, @floatFromInt(r.x + r.w)) * safe_body_w; - if (x < left or x >= right) continue; - const within: u16 = @intFromFloat(@min(@floor(@max(0, x - left) / tag_w), @as(f32, @floatFromInt(r.w - 1)))); - return .{ .col = r.x + within, .row = row }; - }; - return .{ .col = @intFromFloat(@min(@floor(@max(x, 0) / safe_body_w), 10_000)), .row = row }; + const row: u16 = @intFromFloat(@min(@floor(@max(y, 0) / @max(body_h, 1)), 10_000)); + return .{ .col = pardes.gridColAt(core, x, row, body_w, tagline_w), .row = row }; } fn gridCellAtPixels(g: *const Gui, core: ?*const pardes.Pardes, x: f32, y: f32) MouseCell { @@ -4159,6 +4045,7 @@ test "the headless grid host round-trips a yank back as a paste" { }; var pipe_tasks: PipeTasks = .{}; var watches: file_watch.Table = @splat(null); + shell_bin.adoptSystemPath(); var prompt_rcs = shell_bin.PromptRcs.init(); defer prompt_rcs.deinit(); var shell: Shell = .{ |
