Something went wrong. Try again.
source dump of claude code forked from oppi.li/claude-code
Something went wrong. Try again.
10 kB · 289 lines
TypeScript
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290import { logForDebugging } from '../../utils/debug.js'import { isBareMode } from '../../utils/envUtils.js'import { errorMessage } from '../../utils/errors.js'import { logError } from '../../utils/log.js'import { createLSPServerManager, type LSPServerManager,} from './LSPServerManager.js'import { registerLSPNotificationHandlers } from './passiveFeedback.js'
/** * Initialization state of the LSP server manager */type InitializationState = 'not-started' | 'pending' | 'success' | 'failed'
/** * Global singleton instance of the LSP server manager. * Initialized during Claude Code startup. */let lspManagerInstance: LSPServerManager | undefined
/** * Current initialization state */let initializationState: InitializationState = 'not-started'
/** * Error from last initialization attempt, if any */let initializationError: Error | undefined
/** * Generation counter to prevent stale initialization promises from updating state */let initializationGeneration = 0
/** * Promise that resolves when initialization completes (success or failure) */let initializationPromise: Promise<void> | undefined
/** * Test-only sync reset. shutdownLspServerManager() is async and tears down * real connections; this only clears the module-scope singleton state so * reinitializeLspServerManager() early-returns on 'not-started' in downstream * tests on the same shard. */export function _resetLspManagerForTesting(): void { initializationState = 'not-started' initializationError = undefined initializationPromise = undefined initializationGeneration++}
/** * Get the singleton LSP server manager instance. * Returns undefined if not yet initialized, initialization failed, or still pending. * * Callers should check for undefined and handle gracefully, as initialization happens * asynchronously during Claude Code startup. Use getInitializationStatus() to * distinguish between pending, failed, and not-started states. */export function getLspServerManager(): LSPServerManager | undefined { // Don't return a broken instance if initialization failed if (initializationState === 'failed') { return undefined } return lspManagerInstance}
/** * Get the current initialization status of the LSP server manager. * * @returns Status object with current state and error (if failed) */export function getInitializationStatus(): | { status: 'not-started' } | { status: 'pending' } | { status: 'success' } | { status: 'failed'; error: Error } { if (initializationState === 'failed') { return { status: 'failed', error: initializationError || new Error('Initialization failed'), } } if (initializationState === 'not-started') { return { status: 'not-started' } } if (initializationState === 'pending') { return { status: 'pending' } } return { status: 'success' }}
/** * Check whether at least one language server is connected and healthy. * Backs LSPTool.isEnabled(). */export function isLspConnected(): boolean { if (initializationState === 'failed') return false const manager = getLspServerManager() if (!manager) return false const servers = manager.getAllServers() if (servers.size === 0) return false for (const server of servers.values()) { if (server.state !== 'error') return true } return false}
/** * Wait for LSP server manager initialization to complete. * * Returns immediately if initialization has already completed (success or failure). * If initialization is pending, waits for it to complete. * If initialization hasn't started, returns immediately. * * @returns Promise that resolves when initialization is complete */export async function waitForInitialization(): Promise<void> { // If already initialized or failed, return immediately if (initializationState === 'success' || initializationState === 'failed') { return }
// If pending and we have a promise, wait for it if (initializationState === 'pending' && initializationPromise) { await initializationPromise }
// If not started, return immediately (nothing to wait for)}
/** * Initialize the LSP server manager singleton. * * This function is called during Claude Code startup. It synchronously creates * the manager instance, then starts async initialization (loading LSP configs) * in the background without blocking the startup process. * * Safe to call multiple times - will only initialize once (idempotent). * However, if initialization previously failed, calling again will retry. */export function initializeLspServerManager(): void { // --bare / SIMPLE: no LSP. LSP is for editor integration (diagnostics, // hover, go-to-def in the REPL). Scripted -p calls have no use for it. if (isBareMode()) { return } logForDebugging('[LSP MANAGER] initializeLspServerManager() called')
// Skip if already initialized or currently initializing if (lspManagerInstance !== undefined && initializationState !== 'failed') { logForDebugging( '[LSP MANAGER] Already initialized or initializing, skipping', ) return }
// Reset state for retry if previous initialization failed if (initializationState === 'failed') { lspManagerInstance = undefined initializationError = undefined }
// Create the manager instance and mark as pending lspManagerInstance = createLSPServerManager() initializationState = 'pending' logForDebugging('[LSP MANAGER] Created manager instance, state=pending')
// Increment generation to invalidate any pending initializations const currentGeneration = ++initializationGeneration logForDebugging( `[LSP MANAGER] Starting async initialization (generation ${currentGeneration})`, )
// Start initialization asynchronously without blocking // Store the promise so callers can await it via waitForInitialization() initializationPromise = lspManagerInstance .initialize() .then(() => { // Only update state if this is still the current initialization if (currentGeneration === initializationGeneration) { initializationState = 'success' logForDebugging('LSP server manager initialized successfully')
// Register passive notification handlers for diagnostics if (lspManagerInstance) { registerLSPNotificationHandlers(lspManagerInstance) } } }) .catch((error: unknown) => { // Only update state if this is still the current initialization if (currentGeneration === initializationGeneration) { initializationState = 'failed' initializationError = error as Error // Clear the instance since it's not usable lspManagerInstance = undefined
logError(error as Error) logForDebugging( `Failed to initialize LSP server manager: ${errorMessage(error)}`, ) } })}
/** * Force re-initialization of the LSP server manager, even after a prior * successful init. Called from refreshActivePlugins() after plugin caches * are cleared, so newly-loaded plugin LSP servers are picked up. * * Fixes https://github.com/anthropics/claude-code/issues/15521: * loadAllPlugins() is memoized and can be called very early in startup * (via getCommands prefetch in setup.ts) before marketplaces are reconciled, * caching an empty plugin list. initializeLspServerManager() then reads that * stale memoized result and initializes with 0 servers. Unlike commands/agents/ * hooks/MCP, LSP was never re-initialized on plugin refresh. * * Safe to call when no LSP plugins changed: initialize() is just config * parsing (servers are lazy-started on first use). Also safe during pending * init: the generation counter invalidates the in-flight promise. */export function reinitializeLspServerManager(): void { if (initializationState === 'not-started') { // initializeLspServerManager() was never called (e.g. headless subcommand // path). Don't start it now. return }
logForDebugging('[LSP MANAGER] reinitializeLspServerManager() called')
// Best-effort shutdown of any running servers on the old instance so // /reload-plugins doesn't leak child processes. Fire-and-forget: the // primary use case (issue #15521) has 0 servers so this is usually a no-op. if (lspManagerInstance) { void lspManagerInstance.shutdown().catch(err => { logForDebugging( `[LSP MANAGER] old instance shutdown during reinit failed: ${errorMessage(err)}`, ) }) }
// Force the idempotence check in initializeLspServerManager() to fall // through. Generation counter handles invalidating any in-flight init. lspManagerInstance = undefined initializationState = 'not-started' initializationError = undefined
initializeLspServerManager()}
/** * Shutdown the LSP server manager and clean up resources. * * This should be called during Claude Code shutdown. Stops all running LSP servers * and clears internal state. Safe to call when not initialized (no-op). * * NOTE: Errors during shutdown are logged for monitoring but NOT propagated to the caller. * State is always cleared even if shutdown fails, to prevent resource accumulation. * This is acceptable during application exit when recovery is not possible. * * @returns Promise that resolves when shutdown completes (errors are swallowed) */export async function shutdownLspServerManager(): Promise<void> { if (lspManagerInstance === undefined) { return }
try { await lspManagerInstance.shutdown() logForDebugging('LSP server manager shut down successfully') } catch (error: unknown) { logError(error as Error) logForDebugging( `Failed to shutdown LSP server manager: ${errorMessage(error)}`, ) } finally { // Always clear state even if shutdown failed lspManagerInstance = undefined initializationState = 'not-started' initializationError = undefined initializationPromise = undefined // Increment generation to invalidate any pending initializations initializationGeneration++ }}