{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../src/core/lsp/discovery.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AAEH,MAAM,WAAW,eAAe;IAC/B,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,MAAM,WAAW,kBAAkB;IAClC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,UAAU,EAAE,MAAM,EAAE,CAAC;CACrB;AAoBD;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,kBAAkB,EAsCxD,CAAC;AAEF,MAAM,WAAW,uBAAuB;IACvC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAED,wBAAgB,cAAc,CAC7B,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,kBAAkB,EAA6B,GACpD,uBAAuB,CASzB;AAED,MAAM,WAAW,gBAAgB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAsB,aAAa,CAClC,UAAU,EAAE,MAAM,EAClB,KAAK,GAAE,kBAAkB,EAA6B,GACpD,OAAO,CAAC,gBAAgB,CAAC,CA6B3B;AAED,MAAM,WAAW,uBAAuB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,sEAAsE;AACtE,wBAAsB,gBAAgB,CACrC,KAAK,GAAE,kBAAkB,EAA6B,GACpD,OAAO,CAAC,uBAAuB,EAAE,CAAC,CAepC;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAErE;AAED,wBAAgB,uBAAuB,IAAI,OAAO,CAIjD","sourcesContent":["import { execFile } from \"node:child_process\";\nimport { access } from \"node:fs/promises\";\nimport nodePath from \"node:path\";\n\n/**\n * Language detection and language-server discovery. Capability-driven: never\n * assumes a server is installed. Candidate precedence is deterministic and\n * documented; the first executable found on PATH wins.\n */\n\nexport interface ServerCandidate {\n\t/** Pragmatic server command (as installed on PATH). */\n\texecutable: string;\n\targs?: string[];\n}\n\nexport interface LanguageServerSpec {\n\tlanguageId: string;\n\tcandidates: ServerCandidate[];\n\textensions: string[];\n}\n\nconst WINDOWS_EXTS = new Set([\".cmd\", \".bat\", \".exe\", \".ps1\", \".com\"]);\n\nasync function resolveOnPath(command: string): Promise<string | null> {\n\ttry {\n\t\tconst resolved = await new Promise<string>((resolve, reject) => {\n\t\t\texecFile(\"which\", [command], { timeout: 3000 }, (err, stdout) => {\n\t\t\t\tif (err) reject(err);\n\t\t\t\telse resolve(stdout.trim().split(\"\\n\")[0]);\n\t\t\t});\n\t\t});\n\t\tif (!resolved) return null;\n\t\tawait access(resolved);\n\t\treturn resolved;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Default language-server candidates with documented precedence. Order within\n * `candidates` is the precedence order. These are conventions, not\n * installations — discovery reports which are actually available.\n */\nexport const DEFAULT_LANGUAGE_SERVERS: LanguageServerSpec[] = [\n\t{\n\t\tlanguageId: \"typescript\",\n\t\tcandidates: [{ executable: \"typescript-language-server\", args: [\"--stdio\"] }],\n\t\textensions: [\".ts\", \".tsx\", \".mts\", \".cts\", \".js\", \".jsx\", \".mjs\", \".cjs\"],\n\t},\n\t{\n\t\tlanguageId: \"python\",\n\t\tcandidates: [\n\t\t\t{ executable: \"basedpyright-langserver\", args: [\"--stdio\"] },\n\t\t\t{ executable: \"pyright-langserver\", args: [\"--stdio\"] },\n\t\t\t{ executable: \"pyright\", args: [\"--stdio\"] },\n\t\t],\n\t\textensions: [\".py\", \".pyw\"],\n\t},\n\t{\n\t\tlanguageId: \"csharp\",\n\t\tcandidates: [\n\t\t\t{ executable: \"OmniSharp\", args: [\"-lsp\"] },\n\t\t\t{ executable: \"csharp-ls\", args: [] },\n\t\t],\n\t\textensions: [\".cs\", \".csx\"],\n\t},\n\t{\n\t\tlanguageId: \"java\",\n\t\tcandidates: [{ executable: \"jdtls\", args: [] }],\n\t\textensions: [\".java\"],\n\t},\n\t{\n\t\tlanguageId: \"go\",\n\t\tcandidates: [{ executable: \"gopls\", args: [\"serve\"] }],\n\t\textensions: [\".go\"],\n\t},\n\t{\n\t\tlanguageId: \"rust\",\n\t\tcandidates: [{ executable: \"rust-analyzer\", args: [] }],\n\t\textensions: [\".rs\"],\n\t},\n];\n\nexport interface LanguageDetectionResult {\n\tlanguageId: string | null;\n\tmatchedExtension: string | null;\n}\n\nexport function detectLanguage(\n\tfilePath: string,\n\tspecs: LanguageServerSpec[] = DEFAULT_LANGUAGE_SERVERS,\n): LanguageDetectionResult {\n\tconst ext = nodePath.extname(filePath).toLowerCase();\n\tif (!ext) return { languageId: null, matchedExtension: null };\n\tfor (const spec of specs) {\n\t\tif (spec.extensions.includes(ext)) {\n\t\t\treturn { languageId: spec.languageId, matchedExtension: ext };\n\t\t}\n\t}\n\treturn { languageId: null, matchedExtension: ext };\n}\n\nexport interface ServerResolution {\n\tlanguageId: string;\n\texecutable: string | null;\n\tresolvedPath: string | null;\n\targs: string[];\n\treason?: string;\n}\n\n/**\n * Deterministically resolve the best available server candidate for a\n * language. Returns `executable: null` when none is installed.\n */\nexport async function resolveServer(\n\tlanguageId: string,\n\tspecs: LanguageServerSpec[] = DEFAULT_LANGUAGE_SERVERS,\n): Promise<ServerResolution> {\n\tconst spec = specs.find((s) => s.languageId === languageId);\n\tif (!spec) {\n\t\treturn {\n\t\t\tlanguageId,\n\t\t\texecutable: null,\n\t\t\tresolvedPath: null,\n\t\t\targs: [],\n\t\t\treason: `unsupported_language:${languageId}`,\n\t\t};\n\t}\n\tfor (const candidate of spec.candidates) {\n\t\tconst resolved = await resolveOnPath(candidate.executable);\n\t\tif (resolved) {\n\t\t\treturn {\n\t\t\t\tlanguageId,\n\t\t\t\texecutable: candidate.executable,\n\t\t\t\tresolvedPath: resolved,\n\t\t\t\targs: candidate.args ?? [],\n\t\t\t};\n\t\t}\n\t}\n\treturn {\n\t\tlanguageId,\n\t\texecutable: null,\n\t\tresolvedPath: null,\n\t\targs: spec.candidates[0]?.args ?? [],\n\t\treason: `server_not_installed:${languageId}`,\n\t};\n}\n\nexport interface LanguageServerStatusRow {\n\tlanguageId: string;\n\tcandidate: string;\n\tavailable: boolean;\n\tresolvedPath: string | null;\n\treason?: string;\n}\n\n/** Report availability for all configured languages (diagnostics). */\nexport async function reportAllServers(\n\tspecs: LanguageServerSpec[] = DEFAULT_LANGUAGE_SERVERS,\n): Promise<LanguageServerStatusRow[]> {\n\tconst rows: LanguageServerStatusRow[] = [];\n\tfor (const spec of specs) {\n\t\tfor (const cand of spec.candidates) {\n\t\t\tconst resolved = await resolveOnPath(cand.executable);\n\t\t\trows.push({\n\t\t\t\tlanguageId: spec.languageId,\n\t\t\t\tcandidate: cand.executable,\n\t\t\t\tavailable: resolved !== null,\n\t\t\t\tresolvedPath: resolved,\n\t\t\t\treason: resolved ? undefined : \"server_not_installed\",\n\t\t\t});\n\t\t}\n\t}\n\treturn rows;\n}\n\nexport async function commandExists(command: string): Promise<boolean> {\n\treturn (await resolveOnPath(command)) !== null;\n}\n\nexport function isWindowsPathExecutable(): boolean {\n\tconst isWin = process.platform === \"win32\";\n\tvoid WINDOWS_EXTS;\n\treturn isWin;\n}\n"]}