( plugin: LoadedPlugin, errors: PluginError[] = [], )
| 320 | * the proper environment variables and scope applied |
| 321 | */ |
| 322 | export async function getPluginLspServers( |
| 323 | plugin: LoadedPlugin, |
| 324 | errors: PluginError[] = [], |
| 325 | ): Promise<Record<string, ScopedLspServerConfig> | undefined> { |
| 326 | if (!plugin.enabled) { |
| 327 | return undefined |
| 328 | } |
| 329 | |
| 330 | // Use cached servers if available |
| 331 | const servers = |
| 332 | plugin.lspServers || (await loadPluginLspServers(plugin, errors)) |
| 333 | if (!servers) { |
| 334 | return undefined |
| 335 | } |
| 336 | |
| 337 | // Resolve environment variables. Top-level manifest.userConfig values |
| 338 | // become available as ${user_config.KEY} in LSP command/args/env. |
| 339 | // Gate on manifest.userConfig — same rationale as buildMcpUserConfig: |
| 340 | // loadPluginOptions always returns {} so without this guard userConfig is |
| 341 | // truthy for every plugin and substituteUserConfigVariables throws on any |
| 342 | // unresolved ${user_config.X}. Also skips unneeded keychain reads. |
| 343 | const userConfig = plugin.manifest.userConfig |
| 344 | ? loadPluginOptions(getPluginStorageId(plugin)) |
| 345 | : undefined |
| 346 | const resolvedServers: Record<string, LspServerConfig> = {} |
| 347 | for (const [name, config] of Object.entries(servers)) { |
| 348 | resolvedServers[name] = resolvePluginLspEnvironment( |
| 349 | config, |
| 350 | plugin, |
| 351 | userConfig, |
| 352 | errors, |
| 353 | ) |
| 354 | } |
| 355 | |
| 356 | // Add plugin scope |
| 357 | return addPluginScopeToLspServers(resolvedServers, plugin.name) |
| 358 | } |
| 359 | |
| 360 | /** |
| 361 | * Extract all LSP servers from loaded plugins |
no test coverage detected