summaryrefslogtreecommitdiff
path: root/src/lsp_host.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/lsp_host.zig')
-rw-r--r--src/lsp_host.zig206
1 files changed, 0 insertions, 206 deletions
diff --git a/src/lsp_host.zig b/src/lsp_host.zig
deleted file mode 100644
index 803beb31..00000000
--- a/src/lsp_host.zig
+++ /dev/null
@@ -1,206 +0,0 @@
-//! Turning the core's `lsp` effect into work on a thread, and its rows back
-//! into something a loop can deliver. Native-shell side, like host_io.zig and
-//! shell_bin.zig, and here for the reason those are: all three native shells
-//! need it and none of them needs a different one.
-//!
-//! It was two copies before it was this. tty.zig had it inline and gui.zig had
-//! it again with `tty.zig's LspJob, and copied for the same reason` written
-//! over the top — identical down to the comment about a pane with no file. The
-//! AppKit shell had NEITHER, so its vtable left `pull_lsp` null, the core
-//! answered its own requests with no rows, and every language query did nothing
-//! at all in the shell most people run. None of that looked like a missing
-//! feature from the outside: `gd` just moved no cursor. A third copy is what
-//! this module exists instead of.
-//!
-//! What is genuinely per-host stays per-host, and it is small: which allocator,
-//! how a finished job reaches the loop (a mutex queue, a vaxis event, an inbox
-//! plus a wakeup), and what bounds the in-flight set (a refcount to join at
-//! teardown, or one future to cancel). What is NOT per-host is everything
-//! below: the core goes on editing the moment the effect is drained, so every
-//! byte the backend may read has to be COPIED first, and getting that ladder
-//! subtly different in three places is how one shell reads freed text one
-//! keystroke later.
-const std = @import("std");
-const pardes = @import("pardes.zig");
-const host_api = @import("host.zig");
-
-/// One language query, owned by the worker that runs it.
-pub const Job = struct {
- id: u32,
- kind: pardes.lsp.Kind,
- offset: u32,
- path: []u8,
- source: [:0]u8,
- arg: []u8,
- root: []u8,
-
- pub fn free(job: *Job, gpa: std.mem.Allocator) void {
- gpa.free(job.path);
- gpa.free(job.source);
- gpa.free(job.arg);
- gpa.free(job.root);
- gpa.destroy(job);
- }
-};
-
-/// Copy the query out of the core. Null when the pane is gone or an allocation
-/// failed, and nothing leaks on either path — the ladder frees exactly what it
-/// had managed to take.
-///
-/// A pane with no file still asks `status`: that query is about the BACKEND,
-/// not the buffer. Empty path and source then, and the root comes off the
-/// pane's cwd so a bare terminal still reports which servers it would reach.
-pub fn snapshot(gpa: std.mem.Allocator, core: *const pardes.Pardes, req: host_api.LspRequest) ?*Job {
- const pane = core.panes[req.pane] orelse return null;
- const file = pane.file;
- const job = gpa.create(Job) catch return null;
- job.* = .{
- .id = req.id,
- .kind = req.kind,
- .offset = req.offset,
- .path = gpa.dupe(u8, if (file) |f| f.path else "") catch {
- gpa.destroy(job);
- return null;
- },
- .source = gpa.dupeZ(u8, if (file) |f| f.content else "") catch {
- gpa.free(job.path);
- gpa.destroy(job);
- return null;
- },
- .arg = gpa.dupe(u8, req.arg) catch {
- gpa.free(job.path);
- gpa.free(job.source);
- gpa.destroy(job);
- return null;
- },
- .root = gpa.dupe(u8, if (file) |f|
- (std.fs.path.dirname(f.path) orelse "/")
- else
- pane.cwdSlice()) catch {
- gpa.free(job.path);
- gpa.free(job.source);
- gpa.free(job.arg);
- gpa.destroy(job);
- return null;
- },
- };
- return job;
-}
-
-/// What a host does with finished rows. It TAKES OWNERSHIP of `rows`, which
-/// were allocated with the same allocator the job was.
-pub const Deliver = *const fn (ctx: ?*anyopaque, id: u32, rows: []u8) void;
-
-/// Run `job` to completion and hand its rows to `deliver`. Consumes the job
-/// either way.
-///
-/// This is the whole async execution model, and it is the one every shell
-/// already uses for its pty reader: do the slow thing off the loop, hand the
-/// result over as an event, let the core stay a state machine that never
-/// blocks. The shell owns the result buffer; the backend only writes into it.
-pub fn work(gpa: std.mem.Allocator, job: *Job, ctx: ?*anyopaque, deliver: Deliver) void {
- defer job.free(gpa);
- var arena: std.heap.ArenaAllocator = .init(gpa);
- defer arena.deinit();
- var out: std.Io.Writer.Allocating = .init(gpa);
- defer out.deinit();
- 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.writer);
- // Duped out of the writer: `deliver` outlives this frame and the writer
- // does not. A failed dupe drops the answer, which the core survives — the
- // request times out into no rows, exactly as an empty answer would.
- const rows = gpa.dupe(u8, out.written()) catch return;
- deliver(ctx, job.id, rows);
-}
-
-test "a snapshot owns every byte the backend will read" {
- const gpa = std.testing.allocator;
- const core = try pardes.Pardes.init(gpa, .{ .tty_only = true });
- defer core.deinit();
-
- var needle: [6]u8 = "needle".*;
- const job = snapshot(gpa, core, .{
- .id = 7,
- .kind = .status,
- .pane = @intCast(core.active),
- .offset = 0,
- .arg = &needle,
- }) orelse return error.SnapshotFailed;
- defer job.free(gpa);
-
- try std.testing.expectEqual(@as(u32, 7), job.id);
- try std.testing.expectEqual(pardes.lsp.Kind.status, job.kind);
- // `arg` is the caller's buffer on the way in and the job's own bytes on the
- // way out. THIS is the property the whole ladder exists for: the core reuses
- // that buffer for the next builtin's argument the moment the effect drains.
- try std.testing.expectEqualStrings("needle", job.arg);
- try std.testing.expect(job.arg.ptr != &needle);
- // A terminal pane has no file and the query still has to be answerable:
- // empty path, a NUL-terminated empty source, and the pane's own cwd as the
- // root so a bare terminal still reports which servers it would reach. The
- // cwd may legitimately be empty in a core that has never spawned a shell;
- // what matters is that the job OWNS it rather than borrowing it.
- try std.testing.expectEqualStrings("", job.path);
- try std.testing.expectEqual(@as(usize, 0), job.source.len);
- try std.testing.expectEqual(@as(u8, 0), job.source[0]);
- const pane = core.panes[core.active].?;
- try std.testing.expectEqualStrings(pane.cwdSlice(), job.root);
- if (job.root.len > 0) try std.testing.expect(job.root.ptr != pane.cwdSlice().ptr);
-}
-
-test "a pane that is gone yields no job rather than a null deref" {
- const gpa = std.testing.allocator;
- const core = try pardes.Pardes.init(gpa, .{ .tty_only = true });
- defer core.deinit();
-
- // The effect is drained after the core has moved on, so the pane it names
- // may already have been deleted. Every shell open-coded this check.
- const empty = for (core.panes, 0..) |slot, id| {
- if (slot == null) break @as(u8, @intCast(id));
- } else return error.NoEmptyPane;
- try std.testing.expect(snapshot(gpa, core, .{
- .id = 1,
- .kind = .definition,
- .pane = empty,
- .offset = 0,
- .arg = "",
- }) == null);
-}
-
-test "work consumes the job and hands its rows to the sink" {
- const gpa = std.testing.allocator;
- const core = try pardes.Pardes.init(gpa, .{ .tty_only = true });
- defer core.deinit();
-
- const Sink = struct {
- var seen_id: u32 = 0;
- var seen_rows: ?[]u8 = null;
- fn take(_: ?*anyopaque, id: u32, rows: []u8) void {
- seen_id = id;
- seen_rows = rows;
- }
- };
- Sink.seen_id = 0;
- Sink.seen_rows = null;
-
- const job = snapshot(gpa, core, .{
- .id = 42,
- .kind = .status,
- .pane = @intCast(core.active),
- .offset = 0,
- .arg = "",
- }) orelse return error.SnapshotFailed;
- work(gpa, job, null, Sink.take);
-
- // `status` is the one kind that answers with no file and no cursor, which
- // is what makes it assertable here without a language server on the box.
- try std.testing.expectEqual(@as(u32, 42), Sink.seen_id);
- const rows = Sink.seen_rows orelse return error.SinkNeverCalled;
- defer gpa.free(rows);
-}