summaryrefslogtreecommitdiff
path: root/src/gui
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui')
-rw-r--r--src/gui/gui.zig221
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 = .{