Move all server matching logic to the extension/CLI side. The daemon no longer calls pickServer() — it receives an explicit serverId (or serverIds[] for diagnostics) and uses it directly for cache lookup and server spawning. Key changes: - request op requires serverId: string - diagnostics op requires serverIds: string[] — daemon fans out in parallel via Promise.allSettled and returns grouped map - formatDiagnostics() handles grouped results with per-server headers when multiple servers contribute (single-server omits header) - CLI picks servers locally before calling daemon helpers - New pickDiagnosticServers() in extension returns all available, non-disabled servers matching the file extension This makes multi-server diagnostics (e.g., typescript-language-server + oxlint) work naturally — the extension decides which servers to query, the daemon just executes.
79 lines
2.4 KiB
TypeScript
79 lines
2.4 KiB
TypeScript
// Daemon Client - High-level helpers used by cli.ts and index.ts to send
|
|
// LSP work to the long-lived daemon. The first call autospawns the
|
|
// daemon; subsequent calls reuse it.
|
|
//
|
|
// Why Not One Persistent Socket - For now we open a fresh connection per
|
|
// request. The cost is negligible (Unix socket, same machine) compared to
|
|
// the LSP request itself, and it keeps client code stateless.
|
|
import {
|
|
buildLaunchContext,
|
|
sendOnce,
|
|
type DaemonResponse,
|
|
} from "./daemonProtocol.ts";
|
|
|
|
// Unwrap - Throws on { ok: false }, returns result on { ok: true }. All
|
|
// callers want the result-or-throw shape, so we centralize it.
|
|
function unwrap(resp: DaemonResponse): unknown {
|
|
if (resp.ok) return resp.result;
|
|
throw new Error(resp.error);
|
|
}
|
|
|
|
// Send LSP Request - Forwards an arbitrary LSP method to the daemon. The
|
|
// daemon injects textDocument.uri from `file`, so callers omit it.
|
|
export async function daemonRequest(
|
|
file: string,
|
|
serverId: string,
|
|
method: string,
|
|
params: Record<string, unknown>,
|
|
): Promise<unknown> {
|
|
return unwrap(
|
|
await sendOnce({
|
|
op: "request",
|
|
file,
|
|
serverId,
|
|
method,
|
|
params,
|
|
launch: buildLaunchContext(),
|
|
}),
|
|
);
|
|
}
|
|
|
|
// Wait For Diagnostics - Diagnostics arrive as a notification, not a
|
|
// response, so the daemon has a dedicated op that awaits the next publish.
|
|
// Accepts an array of server IDs; daemon fans out in parallel and returns
|
|
// a grouped map: { [serverId]: { uri, diagnostics[] } }.
|
|
export async function daemonDiagnostics(
|
|
file: string,
|
|
serverIds: string[],
|
|
timeoutMs = 1500,
|
|
): Promise<unknown> {
|
|
return unwrap(
|
|
await sendOnce({
|
|
op: "diagnostics",
|
|
file,
|
|
serverIds,
|
|
timeoutMs,
|
|
launch: buildLaunchContext(),
|
|
}),
|
|
);
|
|
}
|
|
|
|
// Status - Lists currently-cached LSP servers (id, root, opened files,
|
|
// idle time). Useful for `pi-lsp daemon status`.
|
|
export async function daemonStatus(): Promise<unknown> {
|
|
return unwrap(await sendOnce({ op: "status" }));
|
|
}
|
|
|
|
// Shutdown - Asks the daemon to dispose all LspClients and exit.
|
|
export async function daemonShutdown(): Promise<unknown> {
|
|
return unwrap(await sendOnce({ op: "shutdown" }));
|
|
}
|
|
|
|
// Destroy Server - Kills running LspClient entries matching a server ID,
|
|
// or all entries if no ID is given. Entries can respawn on next request.
|
|
export async function daemonDestroyServer(
|
|
serverId?: string,
|
|
): Promise<unknown> {
|
|
return unwrap(await sendOnce({ op: "destroy_server", serverId }));
|
|
}
|