summaryrefslogtreecommitdiff
path: root/src/lsp.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-30 23:35:45 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commit19f7322100062b7c1adcde3376063ce6c1d8c72d (patch)
treeb76347dffe0347c29a38b4cad6031678683dba74 /src/lsp.zig
parent753d5451ca7d09dbc5ff44f3c2dcf9a47fb87f96 (diff)
downloadpardes-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.zig162
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";