/ services / lsp / config.ts
config.ts
 1  import type { PluginError } from '../../types/plugin.js'
 2  import { logForDebugging } from '../../utils/debug.js'
 3  import { errorMessage, toError } from '../../utils/errors.js'
 4  import { logError } from '../../utils/log.js'
 5  import { getPluginLspServers } from '../../utils/plugins/lspPluginIntegration.js'
 6  import { loadAllPluginsCacheOnly } from '../../utils/plugins/pluginLoader.js'
 7  import type { ScopedLspServerConfig } from './types.js'
 8  
 9  /**
10   * Get all configured LSP servers from plugins.
11   * LSP servers are only supported via plugins, not user/project settings.
12   *
13   * @returns Object containing servers configuration keyed by scoped server name
14   */
15  export async function getAllLspServers(): Promise<{
16    servers: Record<string, ScopedLspServerConfig>
17  }> {
18    const allServers: Record<string, ScopedLspServerConfig> = {}
19  
20    try {
21      // Get all enabled plugins
22      const { enabled: plugins } = await loadAllPluginsCacheOnly()
23  
24      // Load LSP servers from each plugin in parallel.
25      // Each plugin is independent — results are merged in original order so
26      // Object.assign collision precedence (later plugins win) is preserved.
27      const results = await Promise.all(
28        plugins.map(async plugin => {
29          const errors: PluginError[] = []
30          try {
31            const scopedServers = await getPluginLspServers(plugin, errors)
32            return { plugin, scopedServers, errors }
33          } catch (e) {
34            // Defensive: if one plugin throws, don't lose results from the
35            // others. The previous serial loop implicitly tolerated this.
36            logForDebugging(
37              `Failed to load LSP servers for plugin ${plugin.name}: ${e}`,
38              { level: 'error' },
39            )
40            return { plugin, scopedServers: undefined, errors }
41          }
42        }),
43      )
44  
45      for (const { plugin, scopedServers, errors } of results) {
46        const serverCount = scopedServers ? Object.keys(scopedServers).length : 0
47        if (serverCount > 0) {
48          // Merge into all servers (already scoped by getPluginLspServers)
49          Object.assign(allServers, scopedServers)
50  
51          logForDebugging(
52            `Loaded ${serverCount} LSP server(s) from plugin: ${plugin.name}`,
53          )
54        }
55  
56        // Log any errors encountered
57        if (errors.length > 0) {
58          logForDebugging(
59            `${errors.length} error(s) loading LSP servers from plugin: ${plugin.name}`,
60          )
61        }
62      }
63  
64      logForDebugging(
65        `Total LSP servers loaded: ${Object.keys(allServers).length}`,
66      )
67    } catch (error) {
68      // Log error for monitoring production issues.
69      // LSP is optional, so we don't throw - but we need visibility
70      // into why plugin loading fails to improve the feature.
71      logError(toError(error))
72  
73      logForDebugging(`Error loading LSP servers: ${errorMessage(error)}`)
74    }
75  
76    return {
77      servers: allServers,
78    }
79  }