diff options
Diffstat (limited to 'src/gui.zig')
| -rw-r--r-- | src/gui.zig | 122 |
1 files changed, 112 insertions, 10 deletions
diff --git a/src/gui.zig b/src/gui.zig index e2c39ac1..e157ef28 100644 --- a/src/gui.zig +++ b/src/gui.zig @@ -493,7 +493,32 @@ fn normCell(norm: f32, win: f32, cell: f32) u16 { const Pty = struct { fd: c_int, pid: libc.pid_t }; -const Msg = union(enum) { output: struct { pane: u8, gen: u32, bytes: []u8 }, eof: struct { pane: u8, gen: u32, fd: c_int } }; +const Msg = union(enum) { + output: struct { pane: u8, gen: u32, bytes: []u8 }, + eof: struct { pane: u8, gen: u32, fd: c_int }, + /// a language query finished on its own thread (see lspThread) + lsp: struct { id: u32, rows: []u8 }, +}; + +/// 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); + } +}; const Queue = struct { gpa: std.mem.Allocator, @@ -514,6 +539,7 @@ const Queue = struct { q.mutex.unlock(); switch (m) { .output => |o| q.gpa.free(o.bytes), + .lsp => |l| q.gpa.free(l.rows), .eof => {}, } return; @@ -522,6 +548,7 @@ const Queue = struct { q.mutex.unlock(); switch (m) { .output => |o| q.gpa.free(o.bytes), + .lsp => |l| q.gpa.free(l.rows), .eof => {}, } return; @@ -549,6 +576,7 @@ const Queue = struct { q.closed = true; for (q.items.items) |m| switch (m) { .output => |o| q.gpa.free(o.bytes), + .lsp => |l| q.gpa.free(l.rows), .eof => {}, }; q.items.deinit(q.gpa); @@ -575,6 +603,68 @@ fn spawnReader(gpa: std.mem.Allocator, pt: Pty, pane: u8, gen: u32, q: *Queue) v th.detach(); } +/// 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(gpa: std.mem.Allocator, job: *LspJob, q: *Queue) void { + defer job.free(gpa); + var arena: std.heap.ArenaAllocator = .init(gpa); + defer arena.deinit(); + var out: std.ArrayList(u8) = .empty; + defer out.deinit(gpa); + pardes.lsp.query(gpa, arena.allocator(), .{ + .kind = job.kind, + .path = job.path, + .source = job.source, + .offset = job.offset, + .arg = job.arg, + .root = job.root, + }, &out); + const rows = gpa.dupe(u8, out.items) catch return; + q.push(.{ .lsp = .{ .id = job.id, .rows = rows } }); +} + +/// Copy the query out of the core and hand it to a thread. A detached thread +/// per query is fine at this rate: one keystroke, one query, and the queue +/// already tolerates a late push after close. +fn spawnLsp(core: *pardes.Pardes, gpa: std.mem.Allocator, q: *Queue, e: anytype) void { + const pane = core.panes[e.pane] orelse return; + const f = pane.file orelse return; + const job = gpa.create(LspJob) catch return; + job.* = .{ + .id = e.id, + .kind = e.kind, + .offset = e.offset, + .path = gpa.dupe(u8, f.path) catch { + gpa.destroy(job); + return; + }, + .source = gpa.dupeZ(u8, f.content) catch { + gpa.free(job.path); + gpa.destroy(job); + return; + }, + .arg = gpa.dupe(u8, e.arg.slice()) catch { + gpa.free(job.path); + gpa.free(job.source); + gpa.destroy(job); + return; + }, + .root = gpa.dupe(u8, std.fs.path.dirname(f.path) orelse "/") catch { + gpa.free(job.path); + gpa.free(job.source); + gpa.free(job.arg); + gpa.destroy(job); + return; + }, + }; + const th = std.Thread.spawn(.{}, lspThread, .{ gpa, job, q }) catch { + job.free(gpa); + return; + }; + th.detach(); +} + // ---- the renderer state ---- const Gui = struct { @@ -920,6 +1010,10 @@ fn runNative(init: std.process.Init, opts_in: pardes.Options) !void { core.update(.{ .eof = .{ .pane = e.pane } }); } }, + .lsp => |l| { + core.update(.{ .lsp_resp = .{ .id = l.id, .rows = l.rows } }); + gpa.free(l.rows); + }, }; msgs.deinit(gpa); // 3. steamdeck: poll gamepad axes into virtual cursor / wheel events @@ -1332,6 +1426,11 @@ fn runGrid(init: std.process.Init, opts_in: pardes.Options) !void { } n_events += 1; }, + .lsp => |l| { + core.update(.{ .lsp_resp = .{ .id = l.id, .rows = l.rows } }); + gpa.free(l.rows); + n_events += 1; + }, }; msgs.deinit(gpa); drainEffects(core, &ptys, &gens, gpa, &queue, null, true); @@ -1808,14 +1907,16 @@ fn dispatch(g: *Gui, core: *pardes.Pardes, sev: *const c.SDL_Event) void { } }); } }, - .key => |k| core.update(.{ .key = switch (k) { - .n => .{ .cp = 'n', .text = "n" }, - .cap_n => .{ .cp = 'N', .text = "N" }, - .enter => .{ .cp = pardes.Key.enter }, - .tab => .{ .cp = pardes.Key.tab }, - // back paddle: Ctrl-<configured toggle key> flips tty mode - .tty_toggle => .{ .cp = core.opts.tty_toggle, .ctrl = true }, - } }), + .key => |k| core.update(.{ + .key = switch (k) { + .n => .{ .cp = 'n', .text = "n" }, + .cap_n => .{ .cp = 'N', .text = "N" }, + .enter => .{ .cp = pardes.Key.enter }, + .tab => .{ .cp = pardes.Key.tab }, + // back paddle: Ctrl-<configured toggle key> flips tty mode + .tty_toggle => .{ .cp = core.opts.tty_toggle, .ctrl = true }, + }, + }), // a brief gentle ack for execute/look, not a buzz .rumble => if (g.gamepad) |pad| { _ = c.SDL_RumbleGamepad(pad, 0x4000, 0x4000, 80); @@ -2024,6 +2125,7 @@ fn drainEffects( defer gpa.free(z); _ = c.SDL_SetClipboardText(z.ptr); }, + .lsp => |e| if (threads_ok) spawnLsp(core, gpa, queue, e), .quit => {}, }; } @@ -2062,7 +2164,7 @@ fn drainEffectsWeb(core: *pardes.Pardes, gpa: std.mem.Allocator, g: *Gui) void { // look on a URL → a new tab .open_link => |url| openLinkWeb(gpa, url.slice()), // nothing to spawn/write/resize/save/dump into — all no-ops - .spawn, .write, .resize_pty, .save_file, .write_dump, .quit => {}, + .spawn, .write, .resize_pty, .save_file, .write_dump, .lsp, .quit => {}, }; } |
