diff options
| author | Gabriel Schneider <[email protected]> | 2026-07-30 23:35:45 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-08-01 15:02:07 -0300 |
| commit | 19f7322100062b7c1adcde3376063ce6c1d8c72d (patch) | |
| tree | b76347dffe0347c29a38b4cad6031678683dba74 /src/lsp.zig | |
| parent | 753d5451ca7d09dbc5ff44f3c2dcf9a47fb87f96 (diff) | |
| download | pardes-19f7322100062b7c1adcde3376063ce6c1d8c72d.tar.gz pardes-19f7322100062b7c1adcde3376063ce6c1d8c72d.zip | |
structure: backends into src/{tty,gui,lsp}, pane kinds and builtins into their own files
The core now lies FLAT at src/ and every subdirectory is one backend, so a
file being in no directory at all is what says it is core. Pane-kind bodies
leave pardes.zig for term_pane.zig / file_pane.zig / output_pane.zig, leaving
it the layout, the event/effect machine and the generic render loop.
Builtins are one struct each in builtins.zig, and the enum is folded out of
the file's own declaration list at comptime — a zig file IS a struct, so the
list of builtins and the builtins themselves are the same text. Adding one is
writing a struct. Key paths deliberately stay one table for the config pass.
Pure refactor: no golden moved.
Diffstat (limited to 'src/lsp.zig')
| -rw-r--r-- | src/lsp.zig | 162 |
1 files changed, 0 insertions, 162 deletions
diff --git a/src/lsp.zig b/src/lsp.zig deleted file mode 100644 index 536047a3..00000000 --- a/src/lsp.zig +++ /dev/null @@ -1,162 +0,0 @@ -//! The language-intelligence seam. -//! -//! The core never speaks a protocol and never blocks. It emits an `lsp` Effect -//! naming a Kind, a file and a byte offset; a shell runs `query` on a worker -//! and posts the answer back as an `lsp_resp` Event. That is the whole async -//! execution model — the same shape the pty readers already use, because a -//! language query is just another thing that answers later. -//! -//! Every backend renders into ONE format: `+Search` rows. A location is -//! `path:LINE:COL text`, which is what look.zig already resolves and what n/N -//! already steps, so a multi-result answer IS helix's picker and a single -//! result IS a jump — with no picker UI written for it. Free text (hover, -//! a rename's diff) rides the same buffer as plain lines. -//! -//! `query` is the ONLY thing an implementation supplies. Swapping backends is -//! swapping this one function, which is also how the three competing -//! implementations are measured against each other: same core, same harness, -//! same rows, different `query`. -const std = @import("std"); - -/// What the caller wants to know. The helix command each one backs is named -/// alongside, because the keymap is helix's and these are its verbs. -pub const Kind = enum { - /// gd - definition, - /// gD - declaration, - /// gy - type_definition, - /// gi - implementation, - /// gr - references, - /// SPC k - hover, - /// SPC s - document_symbols, - /// SPC S (arg = the query) - workspace_symbols, - /// SPC d, and the list that ]d / [d step - diagnostics, - /// SPC D - workspace_diagnostics, - /// SPC r (arg = the new name) - rename, - /// SPC a - code_action, - /// = - format, - /// SPC h - select_refs, - - // The two introspection kinds. A backend that answers nothing is - // indistinguishable from a backend that is broken, so these exist to tell - // those apart — they are the only Kinds whose answer is ABOUT the backend - // rather than about the code. - /// SPC l i — configuration, capabilities and the recent-query log - status, - /// SPC l w — why the query at the cursor answers what it does. Narrates - /// the REAL resolution path rather than re-deriving it, so it cannot drift - /// away from what `gd` actually did. - explain, - - /// Whether an answer of exactly one row should JUMP rather than open a - /// results buffer. Helix: the five gotos jump on a single location and - /// show a picker on several; a symbol list is always a picker. - pub fn jumpsWhenSingle(k: Kind) bool { - return switch (k) { - .definition, .declaration, .type_definition, .implementation, .references => true, - else => false, - }; - } - - /// The buffer an answer opens. Kept distinct from `+Search` only where the - /// content is not a list of locations — n/N over prose is nonsense. - pub fn bufferName(k: Kind) []const u8 { - return switch (k) { - .hover => "+Hover", - .code_action, .format, .rename => "+Lsp", - // prose about the backend, never a list of locations - .status, .explain => "+Lsp", - else => "+Search", - }; - } -}; - -/// One question. `source` is a snapshot of the buffer taken by the shell -/// before the worker starts — the core keeps editing while this is in flight, -/// so a backend must never reach back into core memory. -pub const Req = struct { - kind: Kind, - /// absolute path of the file the offset is in - path: []const u8, - /// the buffer's bytes, NUL-terminated (std.zig.Ast and zls both want a - /// sentinel, and every backend has to parse this same text) - source: [:0]const u8, - /// cursor position, a byte offset into `source` - offset: u32, - /// kind-specific argument: the new name for a rename, the query for - /// workspace symbols. Empty otherwise. - arg: []const u8 = "", - /// where the project starts — the directory of the pane that asked. A - /// backend that indexes more than one file walks from here. - root: []const u8 = "", -}; - -/// Emit one `path:LINE:COL text` row. Line and column are 1-based, the way -/// every other row in a `+Search` buffer is (and the way look.zig parses one). -pub fn row( - out: *std.Io.Writer, - path: []const u8, - line: usize, - col: usize, - text: []const u8, -) void { - out.print("{s}:{d}:{d} {s}\n", .{ - path, line + 1, col + 1, std.mem.trim(u8, text, " \t\r\n"), - }) catch {}; -} - -/// Byte offset -> (line, column), both 0-based. Every backend needs it to turn -/// an AST token into a row, so it lives here rather than three times over. -pub fn lineCol(source: []const u8, offset: usize) struct { line: usize, col: usize } { - const upto = source[0..@min(offset, source.len)]; - const line = std.mem.count(u8, upto, "\n"); - const bol = if (std.mem.lastIndexOfScalar(u8, upto, '\n')) |i| i + 1 else 0; - return .{ .line = line, .col = upto.len - bol }; -} - -/// Answer `req`, writing rows to `out`. Runs on a worker thread with no -/// access to the core: everything it may read is in `req`. -/// -/// `out` is a plain `std.Io.Writer` — the shell owns the buffer behind it (an -/// `Io.Writer.Allocating`), so a backend never allocates the result, never -/// frees it, and cannot get the allocator wrong. Write failures are the -/// writer's problem; a backend may ignore them. -/// -/// `arena` is freed wholesale when the query returns; `gpa` is for a backend's -/// own longer-lived scratch. Errors are not reported — a backend that cannot -/// answer writes nothing, and the core treats "no rows" as "no result", which -/// is also what a language server still starting up looks like. -pub fn query(gpa: std.mem.Allocator, arena: std.mem.Allocator, req: Req, out: *std.Io.Writer) void { - backend.query(gpa, arena, req, out); -} - -/// ZLS, linked in as a module and called directly — no subprocess, no -/// JSON-RPC. See `lsp_zls.zig`. The web shell has no threads, never emits the -/// effect, and cannot build ZLS anyway, so there it is the empty backend the -/// base tree shipped with. -const backend = if (@import("pardes_config").zls_backend) @import("lsp_zls.zig") else struct { - pub fn query(_: std.mem.Allocator, _: std.mem.Allocator, _: Req, _: *std.Io.Writer) void {} - pub const supports: std.EnumSet(Kind) = .initEmpty(); -}; - -/// What this backend can actually answer, for the evaluation harness and for -/// the core (a Kind that is not supported never leaves the keymap). An -/// implementation narrows this to what it really does — claiming a feature it -/// does not have shows up immediately in the harness's matrix. -pub const supports: std.EnumSet(Kind) = backend.supports; - -/// Name shown by the harness and in `SPC ?`. Each implementation renames it. -pub const backend_name = "zls-inproc"; |
