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:
@@ -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
|
||||
|
||||
18
src/root.ts
18
src/root.ts
@@ -4,6 +4,7 @@ import { pathToFileURL, fileURLToPath } from "node:url";
|
||||
import { servers, globalRootMarkers } from "../server.ts";
|
||||
import type { ServerConfig } from "./types.ts";
|
||||
import { UnsupportedExtensionError } from "./types.ts";
|
||||
import { isOnPath } from "./util.ts";
|
||||
|
||||
// Resolve File URI To Local Path
|
||||
export function uriToPath(uri: string): string {
|
||||
@@ -15,11 +16,24 @@ export function pathToUri(p: string): string {
|
||||
return pathToFileURL(path.resolve(p)).toString();
|
||||
}
|
||||
|
||||
// Server Availability Cache - Checked once per process lifetime per server.
|
||||
// Avoids repeated filesystem lookups on every tool call.
|
||||
const serverAvailability = new Map<string, boolean>();
|
||||
|
||||
// Is Server Available - Returns true if the server binary is on PATH.
|
||||
// Result is cached for the lifetime of this process.
|
||||
export function isServerAvailable(server: ServerConfig): boolean {
|
||||
if (serverAvailability.has(server.id)) return serverAvailability.get(server.id)!;
|
||||
const available = isOnPath(server.command, process.env);
|
||||
serverAvailability.set(server.id, available);
|
||||
return available;
|
||||
}
|
||||
|
||||
// Pick Server By File Extension - match[] entries are matched against the
|
||||
// file's extension (no dot). First server in the registry wins.
|
||||
// file's extension (no dot). First available server in the registry wins.
|
||||
export function pickServer(filePath: string): ServerConfig {
|
||||
const ext = path.extname(filePath).replace(/^\./, "");
|
||||
const hit = servers.find((s) => s.match.includes(ext));
|
||||
const hit = servers.find((s) => s.match.includes(ext) && isServerAvailable(s));
|
||||
if (!hit) {
|
||||
throw new UnsupportedExtensionError(`.${ext}`);
|
||||
}
|
||||
|
||||
27
src/util.ts
Normal file
27
src/util.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
// Is On PATH - Returns true if `cmd` resolves to an executable via the
|
||||
// supplied PATH. Absolute/relative paths are checked directly.
|
||||
export 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;
|
||||
}
|
||||
Reference in New Issue
Block a user