import type { LspServerSpec, ResolvedCommand } from "./servers.js"; /** LSP diagnostic shape (the subset we render). */ export interface LspDiagnostic { range: { start: { line: number; character: number; }; end: { line: number; character: number; }; }; severity?: number; message: string; source?: string; code?: string | number; } /** Zero-based LSP text position. */ export interface LspPosition { line: number; character: number; } export interface LspRange { start: LspPosition; end: LspPosition; } /** A resolved place in the workspace. */ export interface LspLocation { uri: string; range: LspRange; } /** One symbol from `textDocument/documentSymbol`, flattened to a depth path. */ export interface LspSymbolEntry { name: string; kind: number; detail?: string; range: LspRange; /** Ancestor names, outermost first — `["ClassName"]` for a method. */ containers: string[]; /** * SymbolKind of each ancestor, parallel to `containers`. Lets a caller tell a * class member from a local variable buried in a function body — the two are * indistinguishable by name alone, and only one belongs in a file outline. * Empty when the server sent flat `SymbolInformation` (no hierarchy to read). */ containerKinds: number[]; } /** * Every navigation request answers with one of these. `unsupported` and * `timeout` are values rather than exceptions on purpose: a language server * that cannot answer must produce a statement the model can act on, not an * empty result that reads exactly like "this symbol has no references". */ export type LspRequestOutcome = { status: "ok"; value: T; } | { status: "unsupported"; } | { status: "timeout"; } | { status: "failed"; message: string; }; /** * Canonical cache key for a `file://` URI. * * Diagnostics live in a Map keyed by URI: we `set` what the server sends and * `get` what we built from the edited path, so the two must be the SAME STRING. * There is no single canonical spelling of a file URI, and on Windows we and * tsserver disagreed in two independent ways at once. Measured on CI: * * we sent: file:///c:/Users/RUNNER%7E1/…/main.ts * server sent: file:///c%3A/Users/RUNNER~1/…/main.ts * * `~` is an RFC 3986 unreserved character that `pathToFileURL` percent-encodes * and tsserver leaves literal; the drive colon is the reverse. Every lookup * missed, so the diagnostics arrived (the wire trace shows `diagnostics=1`) * and were then dropped on the floor — reported to the user as a clean file, * because LSP degrades silently by design. 8.3 short names like `RUNNER~1` are * not exotic: that IS the Windows temp path on GitHub runners, and `PROGRA~1` * and friends show up in real user paths. * * Fix: decode percent-escapes and lower-case the drive letter, so both * spellings collapse to one key. Used ONLY as a Map key — the URI actually put * on the wire is still the properly encoded one from `pathToFileURL`. * * Windows paths are also case-insensitive, but case is deliberately NOT folded: * both sides derive from the same path string, and folding would break the * genuinely case-sensitive POSIX servers that share this code. */ export declare function normalizeUri(uri: string): string; /** * One language-server process bound to one project root. Owns document sync * (didOpen / didChange / didSave with per-uri version counters), the * push-diagnostics cache, and LSP 3.17 pull diagnostics with the * push-vs-pull race that the POC proved necessary for rust-analyzer. */ export declare class LspClient { private readonly spec; private readonly rootPath; private readonly proc; private readonly conn; private readonly versions; private readonly published; private waiters; private hasPullDiagnostics; /** Server capabilities from `initialize`; undefined until the handshake lands. */ private serverCapabilities?; private readonly activeProgressTokens; private sawProgress; private alive; private readonly initializationOptions; private stderrBuffer; constructor(spec: LspServerSpec, rootPath: string, command: ResolvedCommand); get isAlive(): boolean; /** True while the server reports indexing/analysis through LSP work progress. */ get hasActiveProgress(): boolean; /** * True once the server has reported ANY work progress, even if it has since * ended. A server that loads a project this way can publish an empty * diagnostic set the instant loading finishes but before the open file has * actually been analysed, so an empty FIRST result from one is not yet * trustworthy. Servers that never report progress never pay for this. */ get hasReportedProgress(): boolean; /** * Wait for the NEXT publishDiagnostics for `uri`, deliberately ignoring what * is already cached. Resolves null when none arrives inside `timeoutMs`, * which means "no correction came", not a failure. */ awaitNextPublish(uri: string, timeoutMs: number): Promise; /** * Drain the server's stderr into a bounded ring, and mirror it to the debug * log so a misbehaving server is diagnosable from `~/.gg/*.log` alone. * Bounded because a looping server can emit stderr without limit. */ private captureStderr; /** Most recent server stderr, for failure diagnostics. */ stderrTail(): string; initialize(timeoutMs: number): Promise; /** * Does the server advertise a capability? Unknown (no initialize result yet) * counts as advertised: some servers under-report and answer anyway, and a * real refusal still surfaces as `unsupported` from the request itself. */ private advertises; /** * One navigation request, with LSP's three legible failure modes separated: * the server does not implement the method, it did not answer inside the * budget, or it errored. Silence is never reported as an empty result. */ private navigate; /** Where a symbol at `position` is defined. */ definition(uri: string, position: LspPosition, timeoutMs: number): Promise>; /** Every reference to the symbol at `position`, declaration included. */ references(uri: string, position: LspPosition, timeoutMs: number, includeDeclaration?: boolean): Promise>; /** Flattened symbol outline for a whole document. */ documentSymbols(uri: string, timeoutMs: number): Promise>; /** Type/signature summary at `position`, as plain text. */ hover(uri: string, position: LspPosition, timeoutMs: number): Promise>; /** * Sync `content` into the server's overlay for `filePath` — didOpen the * first time, didChange (full text) + didSave afterwards. Clears the * push-diagnostics cache for the uri so a subsequent collect waits for a * report computed against THIS content rather than a stale one. */ syncDocument(filePath: string, content: string): string; /** * Current diagnostics for `uri`, racing the push channel (next * publishDiagnostics after the last sync) against a pull-diagnostics poll * loop when the server supports LSP 3.17 pull. Returns null on timeout. */ collectDiagnostics(uri: string, timeoutMs: number): Promise; /** * Graceful shutdown/exit handshake with SIGKILL fallback. Synchronous so it * is safe inside `process.on("exit")` handlers: the shutdown request and * exit notification are written immediately; the SIGKILL timer covers * servers that ignore them (and stdin EOF reaps them when we die first). */ shutdown(): void; /** * Force-kill the server and everything it spawned, immediately. * * For a server that never completed the handshake, the polite * `shutdown`/`exit` sequence is pointless — it has already proven it isn't * answering — so skip straight to the kill. * * Uses `killProcessTree`, not `proc.kill()`: language servers spawn children * (typescript-language-server runs tsserver), Windows has no process groups, * and killing only the parent leaves those children alive holding file * handles in the project directory. */ terminate(): void; private markDead; private waitForPublish; private pullDiagnostics; } //# sourceMappingURL=client.d.ts.map