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