(
command: string,
args: string[],
options?: {
env?: Record<string, string>
cwd?: string
},
)
| 86 | }, |
| 87 | |
| 88 | async start( |
| 89 | command: string, |
| 90 | args: string[], |
| 91 | options?: { |
| 92 | env?: Record<string, string> |
| 93 | cwd?: string |
| 94 | }, |
| 95 | ): Promise<void> { |
| 96 | try { |
| 97 | // 1. Spawn LSP server process |
| 98 | process = spawn(command, args, { |
| 99 | stdio: ['pipe', 'pipe', 'pipe'], |
| 100 | env: { ...subprocessEnv(), ...options?.env }, |
| 101 | cwd: options?.cwd, |
| 102 | // Prevent visible console window on Windows (no-op on other platforms) |
| 103 | windowsHide: true, |
| 104 | }) |
| 105 | |
| 106 | if (!process.stdout || !process.stdin) { |
| 107 | throw new Error('LSP server process stdio not available') |
| 108 | } |
| 109 | |
| 110 | // 1.5. Wait for process to successfully spawn before using streams |
| 111 | // This is CRITICAL: spawn() returns immediately, but the 'error' event |
| 112 | // (e.g., ENOENT for command not found) fires asynchronously. |
| 113 | // If we use the streams before confirming spawn succeeded, we get |
| 114 | // unhandled promise rejections when writes fail on invalid streams. |
| 115 | const spawnedProcess = process // Capture for closure |
| 116 | await new Promise<void>((resolve, reject) => { |
| 117 | const onSpawn = (): void => { |
| 118 | cleanup() |
| 119 | resolve() |
| 120 | } |
| 121 | const onError = (error: Error): void => { |
| 122 | cleanup() |
| 123 | reject(error) |
| 124 | } |
| 125 | const cleanup = (): void => { |
| 126 | spawnedProcess.removeListener('spawn', onSpawn) |
| 127 | spawnedProcess.removeListener('error', onError) |
| 128 | } |
| 129 | spawnedProcess.once('spawn', onSpawn) |
| 130 | spawnedProcess.once('error', onError) |
| 131 | }) |
| 132 | |
| 133 | // Capture stderr for server diagnostics and errors |
| 134 | if (process.stderr) { |
| 135 | process.stderr.on('data', (data: Buffer) => { |
| 136 | const output = data.toString().trim() |
| 137 | if (output) { |
| 138 | logForDebugging(`[LSP SERVER ${serverName}] ${output}`) |
| 139 | } |
| 140 | }) |
| 141 | } |
| 142 | |
| 143 | // Handle process errors (after successful spawn, e.g., crash during operation) |
| 144 | process.on('error', error => { |
| 145 | if (!isStopping) { |
nothing calls this directly
no test coverage detected