refactor(root): extract isOnPath and add extension-side server qualification

Extract isOnPath() to shared src/util.ts so both the daemon (client.ts)
and extension (root.ts) can use it. Add isServerAvailable() with a
per-process cache to pickServer(), skipping servers whose binary isn't
on PATH before sending requests to the daemon.

This avoids wasted daemon round-trips for missing binaries and sets up
for upcoming multi-server diagnostics fan-out.
This commit is contained in:
2026-05-04 07:24:59 -04:00
parent 630226a00a
commit d24e2e94f4
3 changed files with 44 additions and 28 deletions

View File

@@ -10,35 +10,10 @@ import type {
InitializeParams,
PublishDiagnosticsParams,
} from "vscode-languageserver-protocol";
import * as path from "node:path";
import type { ServerConfig } from "./types.ts";
import { ServerNotFoundError } from "./types.ts";
import { findRoot, pathToUri, uriToPath } from "./root.ts";
// Is On PATH - Returns true if `cmd` resolves to an executable via the
// supplied PATH. Absolute/relative paths are checked directly.
function isOnPath(cmd: string, env: NodeJS.ProcessEnv): boolean {
const isExec = (p: string) => {
try {
fs.accessSync(p, fs.constants.X_OK);
return true;
} catch {
return false;
}
};
if (cmd.includes(path.sep)) return isExec(cmd);
const exts =
process.platform === "win32"
? (env.PATHEXT ?? ".EXE;.CMD;.BAT").split(";")
: [""];
for (const dir of (env.PATH ?? "").split(path.delimiter)) {
if (!dir) continue;
for (const ext of exts) {
if (isExec(path.join(dir, cmd + ext))) return true;
}
}
return false;
}
import { isOnPath } from "./util.ts";
// LspClient - Thin wrapper that spawns a language server, performs the
// initialize handshake, auto-opens a file, and exposes sendRequest so the