diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-06 18:11:36 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-09-07 13:59:12 -0300 |
| commit | 60367d8fe23f6af98ec28e3cf6c2094dfe332df0 (patch) | |
| tree | 310fc734173cf771881f4691c71909135fadde97 /src/lsp_host.zig | |
| parent | fa82cac885cb4738fe36d1e49b4749b5a3e31a4a (diff) | |
| download | pardes-60367d8fe23f6af98ec28e3cf6c2094dfe332df0.tar.gz pardes-60367d8fe23f6af98ec28e3cf6c2094dfe332df0.zip | |
Refactor panes and filesystem; replace FUSE with 9P
Consolidate pane, layout, memory and host code. Serve 9P by default over Unix sockets, with runtime mounts and optional TCP/QUIC transports. Remove FUSE and obsolete proof-of-concept examples.
Fix highlighting and terminal-history performance, expand differential and stress-test infrastructure, sort navigation results while preserving the next occurrence, add syntax-colored Braille minimaps, remove SPC-k, and document 9P interaction as a repository skill.
Diffstat (limited to 'src/lsp_host.zig')
| -rw-r--r-- | src/lsp_host.zig | 206 |
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); -} |
