33 * Detects installed language servers on the system
44 */
55
6- import { execSync } from 'child_process' ;
6+ import { execSync , spawn } from 'child_process' ;
77import { existsSync } from 'fs' ;
88import { join } from 'path' ;
99import type { LSPServerConfig } from './lsp-client' ;
@@ -14,6 +14,7 @@ interface LanguageServerDefinition {
1414 args : string [ ] ;
1515 languages : string [ ] ;
1616 checkCommand ?: string ; // Command to verify installation
17+ verificationMethod ?: 'version' | 'lsp' | 'none' ; // New verification method
1718 installHint ?: string ;
1819}
1920
@@ -28,6 +29,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
2829 args : [ '--stdio' ] ,
2930 languages : [ 'ts' , 'tsx' , 'js' , 'jsx' , 'mjs' , 'cjs' ] ,
3031 checkCommand : 'typescript-language-server --version' ,
32+ verificationMethod : 'version' ,
3133 installHint : 'npm install -g typescript-language-server typescript' ,
3234 } ,
3335 // Python - Pyright (preferred)
@@ -37,6 +39,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
3739 args : [ '--stdio' ] ,
3840 languages : [ 'py' , 'pyi' ] ,
3941 checkCommand : 'pyright-langserver --version' ,
42+ verificationMethod : 'lsp' ,
4043 installHint : 'npm install -g pyright' ,
4144 } ,
4245 // Python - pylsp (alternative)
@@ -46,6 +49,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
4649 args : [ ] ,
4750 languages : [ 'py' , 'pyi' ] ,
4851 checkCommand : 'pylsp --version' ,
52+ verificationMethod : 'version' ,
4953 installHint : 'pip install python-lsp-server' ,
5054 } ,
5155 // Rust
@@ -55,6 +59,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
5559 args : [ ] ,
5660 languages : [ 'rs' ] ,
5761 checkCommand : 'rust-analyzer --version' ,
62+ verificationMethod : 'version' ,
5863 installHint : 'rustup component add rust-analyzer' ,
5964 } ,
6065 // Go
@@ -64,6 +69,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
6469 args : [ 'serve' ] ,
6570 languages : [ 'go' ] ,
6671 checkCommand : 'gopls version' ,
72+ verificationMethod : 'version' ,
6773 installHint : 'go install golang.org/x/tools/gopls@latest' ,
6874 } ,
6975 // C/C++
@@ -73,6 +79,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
7379 args : [ '--background-index' ] ,
7480 languages : [ 'c' , 'cpp' , 'cc' , 'cxx' , 'h' , 'hpp' , 'hxx' ] ,
7581 checkCommand : 'clangd --version' ,
82+ verificationMethod : 'version' ,
7683 installHint : 'Install via system package manager (apt, brew, etc.)' ,
7784 } ,
7885 // JSON
@@ -82,6 +89,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
8289 args : [ '--stdio' ] ,
8390 languages : [ 'json' , 'jsonc' ] ,
8491 checkCommand : 'vscode-json-language-server --version' ,
92+ verificationMethod : 'lsp' ,
8593 installHint : 'npm install -g vscode-langservers-extracted' ,
8694 } ,
8795 // HTML
@@ -91,6 +99,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
9199 args : [ '--stdio' ] ,
92100 languages : [ 'html' , 'htm' ] ,
93101 checkCommand : 'vscode-html-language-server --version' ,
102+ verificationMethod : 'lsp' ,
94103 installHint : 'npm install -g vscode-langservers-extracted' ,
95104 } ,
96105 // CSS
@@ -100,6 +109,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
100109 args : [ '--stdio' ] ,
101110 languages : [ 'css' , 'scss' , 'less' ] ,
102111 checkCommand : 'vscode-css-language-server --version' ,
112+ verificationMethod : 'lsp' ,
103113 installHint : 'npm install -g vscode-langservers-extracted' ,
104114 } ,
105115 // YAML
@@ -109,6 +119,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
109119 args : [ '--stdio' ] ,
110120 languages : [ 'yaml' , 'yml' ] ,
111121 checkCommand : 'yaml-language-server --version' ,
122+ verificationMethod : 'lsp' ,
112123 installHint : 'npm install -g yaml-language-server' ,
113124 } ,
114125 // Bash/Shell
@@ -118,6 +129,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
118129 args : [ 'start' ] ,
119130 languages : [ 'sh' , 'bash' , 'zsh' ] ,
120131 checkCommand : 'bash-language-server --version' ,
132+ verificationMethod : 'version' ,
121133 installHint : 'npm install -g bash-language-server' ,
122134 } ,
123135 // Lua
@@ -127,6 +139,7 @@ const KNOWN_SERVERS: LanguageServerDefinition[] = [
127139 args : [ ] ,
128140 languages : [ 'lua' ] ,
129141 checkCommand : 'lua-language-server --version' ,
142+ verificationMethod : 'version' ,
130143 installHint : 'Install from https://github.com/LuaLS/lua-language-server' ,
131144 } ,
132145] ;
@@ -165,10 +178,52 @@ function verifyServer(checkCommand: string): boolean {
165178 }
166179}
167180
181+ /**
182+ * Verify an LSP server by attempting to start it with its required LSP arguments
183+ * and confirming that the process spawns successfully without immediate errors.
184+ */
185+ function verifyLSPServerWithCommunication (
186+ command : string ,
187+ args : string [ ] ,
188+ ) : Promise < boolean > {
189+ return new Promise ( resolve => {
190+ const child = spawn ( command , args , { stdio : [ 'pipe' , 'pipe' , 'pipe' ] } ) ;
191+
192+ // Set a timeout to prevent the process from hanging indefinitely
193+ const timeout = setTimeout ( ( ) => {
194+ child . kill ( ) ;
195+ resolve ( false ) ;
196+ } , 2000 ) ;
197+
198+ // Listen for errors during startup (e.g., command not found)
199+ child . on ( 'error' , ( ) => {
200+ clearTimeout ( timeout ) ;
201+ child . kill ( ) ;
202+ resolve ( false ) ;
203+ } ) ;
204+
205+ // If the process spawns successfully, we consider it valid.
206+ // We can then kill it immediately.
207+ child . on ( 'spawn' , ( ) => {
208+ clearTimeout ( timeout ) ;
209+ child . kill ( ) ; // Clean up the successfully spawned process
210+ resolve ( true ) ;
211+ } ) ;
212+
213+ // Handle cases where the process exits very quickly (either success or failure)
214+ child . on ( 'exit' , _code => {
215+ clearTimeout ( timeout ) ;
216+ // A clean exit can also indicate success for some servers
217+ // However, for LSP servers waiting for input, an immediate exit is often a failure
218+ // The 'spawn' event is a more reliable indicator for our purpose
219+ } ) ;
220+ } ) ;
221+ }
222+
168223/**
169224 * Discover all installed language servers
170225 */
171- export function discoverLanguageServers ( ) : LSPServerConfig [ ] {
226+ export async function discoverLanguageServers ( ) : Promise < LSPServerConfig [ ] > {
172227 const discovered : LSPServerConfig [ ] = [ ] ;
173228 const coveredLanguages = new Set < string > ( ) ;
174229
@@ -183,13 +238,38 @@ export function discoverLanguageServers(): LSPServerConfig[] {
183238 const commandPath = findCommand ( server . command ) ;
184239 if ( ! commandPath ) continue ;
185240
186- // Verify server works if check command provided
241+ // Verify server works based on verification method
187242 // Use the resolved command path for verification
188- if ( server . checkCommand ) {
189- const checkCmd = server . checkCommand . replace ( server . command , commandPath ) ;
190- if ( ! verifyServer ( checkCmd ) ) continue ;
243+ const verificationMethod = server . verificationMethod || 'version' ;
244+
245+ let verified = true ;
246+ switch ( verificationMethod ) {
247+ case 'version' :
248+ // Use the existing check command approach
249+ if ( server . checkCommand ) {
250+ const checkCmd = server . checkCommand . replace (
251+ server . command ,
252+ commandPath ,
253+ ) ;
254+ verified = verifyServer ( checkCmd ) ;
255+ }
256+ break ;
257+
258+ case 'lsp' :
259+ // Use the new LSP verification approach
260+ verified = await verifyLSPServerWithCommunication (
261+ commandPath ,
262+ server . args ,
263+ ) ;
264+ break ;
265+
266+ case 'none' :
267+ // Skip verification, only check if command exists
268+ break ;
191269 }
192270
271+ if ( ! verified ) continue ;
272+
193273 // Add to discovered servers with resolved command path
194274 discovered . push ( {
195275 name : server . name ,
0 commit comments