diff --git a/packages/cli/LSP_DEBUGGING_GUIDE.md b/packages/cli/LSP_DEBUGGING_GUIDE.md deleted file mode 100644 index d837adb4d..000000000 --- a/packages/cli/LSP_DEBUGGING_GUIDE.md +++ /dev/null @@ -1,156 +0,0 @@ -# LSP 调试指南 - -本指南介绍如何调试 packages/cli 中的 LSP (Language Server Protocol) 功能。 - -## 1. 启用调试模式 - -CLI 支持调试模式,可以提供额外的日志信息: - -```bash -# 使用 debug 标志运行 -qwen --debug [你的命令] - -# 或设置环境变量 -DEBUG=true qwen [你的命令] -DEBUG_MODE=true qwen [你的命令] -``` - -## 2. LSP 配置选项 - -LSP 功能通过 `--experimental-lsp` 命令行参数启用。服务器配置通过以下方式定义: - -- `.lsp.json` 文件:在项目根目录创建配置文件 -- `lsp.languageServers`:在 `settings.json` 中内联配置 - -### 在 settings.json 中的示例配置 - -```json -{ - "lsp": { - "languageServers": { - "typescript-language-server": { - "languages": ["typescript", "javascript"], - "command": "typescript-language-server", - "args": ["--stdio"] - } - } - } -} -``` - -### 在 .lsp.json 中的示例配置 - -```json -{ - "typescript": { - "command": "typescript-language-server", - "args": ["--stdio"], - "extensionToLanguage": { - ".ts": "typescript", - ".tsx": "typescriptreact" - } - } -} -``` - -## 3. NativeLspService 调试功能 - -`NativeLspService` 类包含几个调试功能: - -### 3.1 控制台日志 - -服务向控制台输出状态消息: - -- `LSP 服务器 ${name} 启动成功` - 服务器成功启动 -- `LSP 服务器 ${name} 启动失败` - 服务器启动失败 -- `工作区不受信任,跳过 LSP 服务器发现` - 工作区不受信任,跳过发现 - -### 3.2 错误处理 - -服务具有全面的错误处理和详细的错误消息 - -### 3.3 状态跟踪 - -您可以通过 `getStatus()` 方法检查所有 LSP 服务器的状态 - -## 4. 调试命令 - -```bash -# 启用调试运行 -qwen --debug --prompt "调试 LSP 功能" - -# 检查在您的项目中检测到哪些 LSP 服务器 -# 系统会自动检测语言和相应的 LSP 服务器 -``` - -## 5. 手动 LSP 服务器配置 - -您还可以在项目根目录使用 `.lsp.json` 文件手动配置 LSP 服务器。 -推荐使用新格式(以服务器名称为键),旧格式仍然兼容但会提示迁移: - -```json -{ - "languageServers": { - "pylsp": { - "command": "pylsp", - "args": [], - "languages": ["python"], - "transport": "stdio", - "settings": {}, - "workspaceFolder": null, - "startupTimeout": 10000, - "shutdownTimeout": 3000, - "restartOnCrash": true, - "maxRestarts": 3, - "trustRequired": true - } - } -} -``` - -旧格式示例: - -```json -{ - "python": { - "command": "pylsp", - "args": [], - "transport": "stdio", - "trustRequired": true - } -} -``` - -## 6. LSP 问题排查 - -### 6.1 检查 LSP 服务器是否已安装 - -- 对于 TypeScript/JavaScript: `typescript-language-server` -- 对于 Python: `pylsp` -- 对于 Go: `gopls` - -### 6.2 验证工作区信任 - -- LSP 服务器可能需要受信任的工作区才能启动 -- 检查 `security.folderTrust.enabled` 设置 - -### 6.3 查看日志 - -- 查找以 `LSP 服务器` 开头的控制台消息 -- 检查命令存在性和路径安全性问题 - -## 7. LSP 服务启动流程 - -LSP 服务的启动遵循以下流程: - -1. **发现和准备**: `discoverAndPrepare()` 方法检测工作区中的编程语言 -2. **创建服务器句柄**: 根据检测到的语言创建对应的服务器句柄 -3. **启动服务器**: `start()` 方法启动所有服务器句柄 -4. **状态管理**: 服务器状态在 `NOT_STARTED`, `IN_PROGRESS`, `READY`, `FAILED` 之间转换 - -## 8. 调试技巧 - -- 使用 `--debug` 标志查看详细的启动过程 -- 检查工作区是否受信任(影响 LSP 服务器启动) -- 确认 LSP 服务器命令在系统 PATH 中可用 -- 使用 `getStatus()` 方法监控服务器运行状态 diff --git a/packages/cli/src/config/config.ts b/packages/cli/src/config/config.ts index f04486894..165ec8a43 100755 --- a/packages/cli/src/config/config.ts +++ b/packages/cli/src/config/config.ts @@ -45,6 +45,7 @@ import { getCliVersion } from '../utils/version.js'; import { loadSandboxConfig } from './sandboxConfig.js'; import { appEvents } from '../utils/events.js'; import { mcpCommand } from '../commands/mcp.js'; +import { NativeLspClient } from '../services/lsp/NativeLspClient.js'; import { NativeLspService } from '../services/lsp/NativeLspService.js'; import { isWorkspaceTrusted } from './trustedFolders.js'; @@ -151,134 +152,6 @@ export interface CliArgs { channel: string | undefined; } -class NativeLspClient implements LspClient { - constructor(private readonly service: NativeLspService) {} - - workspaceSymbols(query: string, limit?: number) { - return this.service.workspaceSymbols(query, limit); - } - - definitions( - location: Parameters[0], - serverName?: string, - limit?: number, - ) { - return this.service.definitions(location, serverName, limit); - } - - references( - location: Parameters[0], - serverName?: string, - includeDeclaration?: boolean, - limit?: number, - ) { - return this.service.references( - location, - serverName, - includeDeclaration, - limit, - ); - } - - /** - * Get hover information (documentation, type info) for a symbol. - */ - hover( - location: Parameters[0], - serverName?: string, - ) { - return this.service.hover(location, serverName); - } - - /** - * Get all symbols in a document. - */ - documentSymbols(uri: string, serverName?: string, limit?: number) { - return this.service.documentSymbols(uri, serverName, limit); - } - - /** - * Find implementations of an interface or abstract method. - */ - implementations( - location: Parameters[0], - serverName?: string, - limit?: number, - ) { - return this.service.implementations(location, serverName, limit); - } - - /** - * Prepare call hierarchy item at a position (functions/methods). - */ - prepareCallHierarchy( - location: Parameters[0], - serverName?: string, - limit?: number, - ) { - return this.service.prepareCallHierarchy(location, serverName, limit); - } - - /** - * Find all functions/methods that call the given function. - */ - incomingCalls( - item: Parameters[0], - serverName?: string, - limit?: number, - ) { - return this.service.incomingCalls(item, serverName, limit); - } - - /** - * Find all functions/methods called by the given function. - */ - outgoingCalls( - item: Parameters[0], - serverName?: string, - limit?: number, - ) { - return this.service.outgoingCalls(item, serverName, limit); - } - - /** - * Get diagnostics for a specific document. - */ - diagnostics(uri: string, serverName?: string) { - return this.service.diagnostics(uri, serverName); - } - - /** - * Get diagnostics for all open documents in the workspace. - */ - workspaceDiagnostics(serverName?: string, limit?: number) { - return this.service.workspaceDiagnostics(serverName, limit); - } - - /** - * Get code actions available at a specific location. - */ - codeActions( - uri: string, - range: Parameters[1], - context: Parameters[2], - serverName?: string, - limit?: number, - ) { - return this.service.codeActions(uri, range, context, serverName, limit); - } - - /** - * Apply a workspace edit (from code action or other sources). - */ - applyWorkspaceEdit( - edit: Parameters[0], - serverName?: string, - ) { - return this.service.applyWorkspaceEdit(edit, serverName); - } -} - function normalizeOutputFormat( format: string | OutputFormat | undefined, ): OutputFormat | undefined { diff --git a/packages/cli/src/services/lsp/LspConfigLoader.ts b/packages/cli/src/services/lsp/LspConfigLoader.ts index 89f56ee64..a0c5cd08d 100644 --- a/packages/cli/src/services/lsp/LspConfigLoader.ts +++ b/packages/cli/src/services/lsp/LspConfigLoader.ts @@ -14,39 +14,28 @@ import type { } from './LspTypes.js'; export class LspConfigLoader { - private warnedLegacyConfig = false; - constructor(private readonly workspaceRoot: string) {} /** - * Load user .lsp.json configuration + * Load user .lsp.json configuration. + * Supports two official formats: + * 1. Basic format: { "language": { "command": "...", "extensionToLanguage": {...} } } + * 2. LanguageServers format: { "languageServers": { "server-name": { "languages": [...], ... } } } */ async loadUserConfigs(): Promise { - const configs: LspServerConfig[] = []; - const sources: Array<{ origin: string; data: unknown }> = []; - const lspConfigPath = path.join(this.workspaceRoot, '.lsp.json'); - if (fs.existsSync(lspConfigPath)) { - try { - const configContent = fs.readFileSync(lspConfigPath, 'utf-8'); - sources.push({ - origin: lspConfigPath, - data: JSON.parse(configContent), - }); - } catch (error) { - console.warn('Failed to load user .lsp.json config:', error); - } + if (!fs.existsSync(lspConfigPath)) { + return []; } - for (const source of sources) { - const parsed = this.parseConfigSource(source.data, source.origin); - if (parsed.usedLegacyFormat && parsed.configs.length > 0) { - this.warnLegacyConfig(source.origin); - } - configs.push(...parsed.configs); + try { + const configContent = fs.readFileSync(lspConfigPath, 'utf-8'); + const data = JSON.parse(configContent); + return this.parseConfigSource(data, lspConfigPath); + } catch (error) { + console.warn('Failed to load user .lsp.json config:', error); + return []; } - - return configs; } /** @@ -164,40 +153,43 @@ export class LspConfigLoader { return presets; } + /** + * Parse configuration source and extract server configs. + * Detects format based on presence of 'languageServers' key. + */ private parseConfigSource( source: unknown, origin: string, - ): { configs: LspServerConfig[]; usedLegacyFormat: boolean } { + ): LspServerConfig[] { if (!this.isRecord(source)) { - return { configs: [], usedLegacyFormat: false }; + return []; } const configs: LspServerConfig[] = []; - let serverMap: Record = source; - let usedLegacyFormat = false; - if (this.isRecord(source['languageServers'])) { - serverMap = source['languageServers'] as Record; - } else if (this.isNewFormatServerMap(source)) { - serverMap = source; - } else { - usedLegacyFormat = true; - } + // Determine format: languageServers wrapper vs basic format + const hasLanguageServersWrapper = this.isRecord(source['languageServers']); + const serverMap = hasLanguageServersWrapper + ? (source['languageServers'] as Record) + : source; for (const [key, spec] of Object.entries(serverMap)) { if (!this.isRecord(spec)) { continue; } - const languagesValue = spec['languages']; - const languages = usedLegacyFormat - ? [key] - : (this.normalizeStringArray(languagesValue) ?? - (typeof languagesValue === 'string' ? [languagesValue] : [])); + // In basic format: key is language name, server name comes from command + // In languageServers format: key is server name, languages come from 'languages' array + const isBasicFormat = !hasLanguageServersWrapper && !spec['languages']; - const name = usedLegacyFormat + const languages = isBasicFormat + ? [key] + : (this.normalizeStringArray(spec['languages']) ?? + (typeof spec['languages'] === 'string' ? [spec['languages']] : [])); + + const name = isBasicFormat ? typeof spec['command'] === 'string' - ? (spec['command'] as string) + ? spec['command'] : key : key; @@ -207,7 +199,7 @@ export class LspConfigLoader { } } - return { configs, usedLegacyFormat }; + return configs; } private buildServerConfig( @@ -282,37 +274,6 @@ export class LspConfigLoader { }; } - private isNewFormatServerMap(value: Record): boolean { - return Object.values(value).some( - (entry) => this.isRecord(entry) && this.isNewFormatServerSpec(entry), - ); - } - - private isNewFormatServerSpec(value: Record): boolean { - return ( - Array.isArray(value['languages']) || - this.isRecord(value['extensionToLanguage']) || - this.isRecord(value['settings']) || - value['workspaceFolder'] !== undefined || - value['startupTimeout'] !== undefined || - value['shutdownTimeout'] !== undefined || - value['restartOnCrash'] !== undefined || - value['maxRestarts'] !== undefined || - this.isRecord(value['env']) || - value['socket'] !== undefined - ); - } - - private warnLegacyConfig(origin: string): void { - if (this.warnedLegacyConfig) { - return; - } - console.warn( - `Legacy LSP config detected in ${origin}. Please migrate to the languageServers format.`, - ); - this.warnedLegacyConfig = true; - } - private isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } diff --git a/packages/cli/src/services/lsp/LspLanguageDetector.ts b/packages/cli/src/services/lsp/LspLanguageDetector.ts index 694cf14f1..863332867 100644 --- a/packages/cli/src/services/lsp/LspLanguageDetector.ts +++ b/packages/cli/src/services/lsp/LspLanguageDetector.ts @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + /** * LSP Language Detector * diff --git a/packages/cli/src/services/lsp/LspResponseNormalizer.ts b/packages/cli/src/services/lsp/LspResponseNormalizer.ts index ee789bc73..a9720a8a4 100644 --- a/packages/cli/src/services/lsp/LspResponseNormalizer.ts +++ b/packages/cli/src/services/lsp/LspResponseNormalizer.ts @@ -1,3 +1,9 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + /** * LSP Response Normalizer * diff --git a/packages/cli/src/services/lsp/LspServerManager.ts b/packages/cli/src/services/lsp/LspServerManager.ts index af2e9a4f6..0bb129529 100644 --- a/packages/cli/src/services/lsp/LspServerManager.ts +++ b/packages/cli/src/services/lsp/LspServerManager.ts @@ -143,7 +143,13 @@ export class LspServerManager { } } - private isTypescriptServer(handle: LspServerHandle): boolean { + /** + * Check if the given handle is a TypeScript language server. + * + * @param handle - The LSP server handle + * @returns true if it's a TypeScript server + */ + isTypescriptServer(handle: LspServerHandle): boolean { return ( handle.config.name.includes('typescript') || (handle.config.command?.includes('typescript') ?? false) diff --git a/packages/cli/src/services/lsp/NativeLspClient.ts b/packages/cli/src/services/lsp/NativeLspClient.ts new file mode 100644 index 000000000..890ed0755 --- /dev/null +++ b/packages/cli/src/services/lsp/NativeLspClient.ts @@ -0,0 +1,259 @@ +/** + * @license + * Copyright 2025 Qwen Team + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * NativeLspClient is an adapter that implements the LspClient interface + * by delegating all calls to NativeLspService. + * + * This class bridges the gap between the generic LspClient interface (defined in core) + * and the CLI-specific NativeLspService implementation. + */ + +import type { + LspCallHierarchyIncomingCall, + LspCallHierarchyItem, + LspCallHierarchyOutgoingCall, + LspClient, + LspCodeAction, + LspCodeActionContext, + LspDefinition, + LspDiagnostic, + LspFileDiagnostics, + LspHoverResult, + LspLocation, + LspRange, + LspReference, + LspSymbolInformation, + LspWorkspaceEdit, +} from '@qwen-code/qwen-code-core'; + +import type { NativeLspService } from './NativeLspService.js'; + +/** + * Adapter class that implements LspClient by delegating to NativeLspService. + * + * @example + * ```typescript + * const lspService = new NativeLspService(config, workspaceContext, ...); + * await lspService.start(); + * const lspClient = new NativeLspClient(lspService); + * config.setLspClient(lspClient); + * ``` + */ +export class NativeLspClient implements LspClient { + /** + * Creates a new NativeLspClient instance. + * + * @param service - The NativeLspService instance to delegate calls to + */ + constructor(private readonly service: NativeLspService) {} + + /** + * Search for symbols across the workspace. + * + * @param query - The search query string + * @param limit - Maximum number of results to return + * @returns Promise resolving to array of symbol information + */ + workspaceSymbols( + query: string, + limit?: number, + ): Promise { + return this.service.workspaceSymbols(query, limit); + } + + /** + * Find where a symbol is defined. + * + * @param location - The source location to find definitions for + * @param serverName - Optional specific LSP server to query + * @param limit - Maximum number of results to return + * @returns Promise resolving to array of definition locations + */ + definitions( + location: LspLocation, + serverName?: string, + limit?: number, + ): Promise { + return this.service.definitions(location, serverName, limit); + } + + /** + * Find all references to a symbol. + * + * @param location - The source location to find references for + * @param serverName - Optional specific LSP server to query + * @param includeDeclaration - Whether to include the declaration in results + * @param limit - Maximum number of results to return + * @returns Promise resolving to array of reference locations + */ + references( + location: LspLocation, + serverName?: string, + includeDeclaration?: boolean, + limit?: number, + ): Promise { + return this.service.references( + location, + serverName, + includeDeclaration, + limit, + ); + } + + /** + * Get hover information (documentation, type info) for a symbol. + * + * @param location - The source location to get hover info for + * @param serverName - Optional specific LSP server to query + * @returns Promise resolving to hover result or null if not available + */ + hover( + location: LspLocation, + serverName?: string, + ): Promise { + return this.service.hover(location, serverName); + } + + /** + * Get all symbols in a document. + * + * @param uri - The document URI to get symbols for + * @param serverName - Optional specific LSP server to query + * @param limit - Maximum number of results to return + * @returns Promise resolving to array of symbol information + */ + documentSymbols( + uri: string, + serverName?: string, + limit?: number, + ): Promise { + return this.service.documentSymbols(uri, serverName, limit); + } + + /** + * Find implementations of an interface or abstract method. + * + * @param location - The source location to find implementations for + * @param serverName - Optional specific LSP server to query + * @param limit - Maximum number of results to return + * @returns Promise resolving to array of implementation locations + */ + implementations( + location: LspLocation, + serverName?: string, + limit?: number, + ): Promise { + return this.service.implementations(location, serverName, limit); + } + + /** + * Prepare call hierarchy item at a position (functions/methods). + * + * @param location - The source location to prepare call hierarchy for + * @param serverName - Optional specific LSP server to query + * @param limit - Maximum number of results to return + * @returns Promise resolving to array of call hierarchy items + */ + prepareCallHierarchy( + location: LspLocation, + serverName?: string, + limit?: number, + ): Promise { + return this.service.prepareCallHierarchy(location, serverName, limit); + } + + /** + * Find all functions/methods that call the given function. + * + * @param item - The call hierarchy item to find callers for + * @param serverName - Optional specific LSP server to query + * @param limit - Maximum number of results to return + * @returns Promise resolving to array of incoming calls + */ + incomingCalls( + item: LspCallHierarchyItem, + serverName?: string, + limit?: number, + ): Promise { + return this.service.incomingCalls(item, serverName, limit); + } + + /** + * Find all functions/methods called by the given function. + * + * @param item - The call hierarchy item to find callees for + * @param serverName - Optional specific LSP server to query + * @param limit - Maximum number of results to return + * @returns Promise resolving to array of outgoing calls + */ + outgoingCalls( + item: LspCallHierarchyItem, + serverName?: string, + limit?: number, + ): Promise { + return this.service.outgoingCalls(item, serverName, limit); + } + + /** + * Get diagnostics for a specific document. + * + * @param uri - The document URI to get diagnostics for + * @param serverName - Optional specific LSP server to query + * @returns Promise resolving to array of diagnostics + */ + diagnostics(uri: string, serverName?: string): Promise { + return this.service.diagnostics(uri, serverName); + } + + /** + * Get diagnostics for all open documents in the workspace. + * + * @param serverName - Optional specific LSP server to query + * @param limit - Maximum number of file diagnostics to return + * @returns Promise resolving to array of file diagnostics + */ + workspaceDiagnostics( + serverName?: string, + limit?: number, + ): Promise { + return this.service.workspaceDiagnostics(serverName, limit); + } + + /** + * Get code actions available at a specific location. + * + * @param uri - The document URI + * @param range - The range to get code actions for + * @param context - The code action context including diagnostics + * @param serverName - Optional specific LSP server to query + * @param limit - Maximum number of code actions to return + * @returns Promise resolving to array of code actions + */ + codeActions( + uri: string, + range: LspRange, + context: LspCodeActionContext, + serverName?: string, + limit?: number, + ): Promise { + return this.service.codeActions(uri, range, context, serverName, limit); + } + + /** + * Apply a workspace edit (from code action or other sources). + * + * @param edit - The workspace edit to apply + * @param serverName - Optional specific LSP server context + * @returns Promise resolving to true if edit was applied successfully + */ + applyWorkspaceEdit( + edit: LspWorkspaceEdit, + serverName?: string, + ): Promise { + return this.service.applyWorkspaceEdit(edit, serverName); + } +} diff --git a/packages/cli/src/services/lsp/NativeLspService.ts b/packages/cli/src/services/lsp/NativeLspService.ts index 306e706a7..a7e12cfcf 100644 --- a/packages/cli/src/services/lsp/NativeLspService.ts +++ b/packages/cli/src/services/lsp/NativeLspService.ts @@ -34,6 +34,7 @@ import type { LspServerHandle, LspServerStatus, NativeLspServiceOptions, + LspConnectionInterface, } from './LspTypes.js'; import * as path from 'path'; import { fileURLToPath } from 'url'; @@ -131,6 +132,29 @@ export class NativeLspService { return this.serverManager.getStatus(); } + /** + * Get ready server handles filtered by optional server name. + * Each handle is guaranteed to have a valid connection. + * + * @param serverName - Optional server name to filter by + * @returns Array of [serverName, handle] tuples with active connections + */ + private getReadyHandles( + serverName?: string, + ): Array<[string, LspServerHandle & { connection: LspConnectionInterface }]> { + return Array.from(this.serverManager.getHandles().entries()).filter( + ( + entry, + ): entry is [ + string, + LspServerHandle & { connection: LspConnectionInterface }, + ] => + entry[1].status === 'READY' && + entry[1].connection !== undefined && + (!serverName || entry[0] === serverName), + ); + } + /** * Workspace symbol search across all ready LSP servers. */ @@ -152,7 +176,7 @@ export class NativeLspService { query, }); if ( - this.isTypescriptServer(handle) && + this.serverManager.isTypescriptServer(handle) && this.isNoProjectErrorResponse(response) ) { await this.serverManager.warmupTypescriptServer(handle, true); @@ -191,19 +215,9 @@ export class NativeLspService { serverName?: string, limit = 50, ): Promise { - const handles = Array.from( - this.serverManager.getHandles().entries(), - ).filter( - ([name, handle]) => - handle.status === 'READY' && - handle.connection && - (!serverName || name === serverName), - ); + const handles = this.getReadyHandles(serverName); for (const [name, handle] of handles) { - if (!handle.connection) { - continue; - } try { await this.serverManager.warmupTypescriptServer(handle); const response = await handle.connection.request( @@ -248,19 +262,9 @@ export class NativeLspService { includeDeclaration = false, limit = 200, ): Promise { - const handles = Array.from( - this.serverManager.getHandles().entries(), - ).filter( - ([name, handle]) => - handle.status === 'READY' && - handle.connection && - (!serverName || name === serverName), - ); + const handles = this.getReadyHandles(serverName); for (const [name, handle] of handles) { - if (!handle.connection) { - continue; - } try { await this.serverManager.warmupTypescriptServer(handle); const response = await handle.connection.request( @@ -302,19 +306,9 @@ export class NativeLspService { location: LspLocation, serverName?: string, ): Promise { - const handles = Array.from( - this.serverManager.getHandles().entries(), - ).filter( - ([name, handle]) => - handle.status === 'READY' && - handle.connection && - (!serverName || name === serverName), - ); + const handles = this.getReadyHandles(serverName); for (const [name, handle] of handles) { - if (!handle.connection) { - continue; - } try { await this.serverManager.warmupTypescriptServer(handle); const response = await handle.connection.request('textDocument/hover', { @@ -341,19 +335,9 @@ export class NativeLspService { serverName?: string, limit = 200, ): Promise { - const handles = Array.from( - this.serverManager.getHandles().entries(), - ).filter( - ([name, handle]) => - handle.status === 'READY' && - handle.connection && - (!serverName || name === serverName), - ); + const handles = this.getReadyHandles(serverName); for (const [name, handle] of handles) { - if (!handle.connection) { - continue; - } try { await this.serverManager.warmupTypescriptServer(handle); const response = await handle.connection.request( @@ -414,19 +398,9 @@ export class NativeLspService { serverName?: string, limit = 50, ): Promise { - const handles = Array.from( - this.serverManager.getHandles().entries(), - ).filter( - ([name, handle]) => - handle.status === 'READY' && - handle.connection && - (!serverName || name === serverName), - ); + const handles = this.getReadyHandles(serverName); for (const [name, handle] of handles) { - if (!handle.connection) { - continue; - } try { await this.serverManager.warmupTypescriptServer(handle); const response = await handle.connection.request( @@ -476,19 +450,9 @@ export class NativeLspService { serverName?: string, limit = 50, ): Promise { - const handles = Array.from( - this.serverManager.getHandles().entries(), - ).filter( - ([name, handle]) => - handle.status === 'READY' && - handle.connection && - (!serverName || name === serverName), - ); + const handles = this.getReadyHandles(serverName); for (const [name, handle] of handles) { - if (!handle.connection) { - continue; - } try { await this.serverManager.warmupTypescriptServer(handle); const response = await handle.connection.request( @@ -539,19 +503,9 @@ export class NativeLspService { limit = 50, ): Promise { const targetServer = serverName ?? item.serverName; - const handles = Array.from( - this.serverManager.getHandles().entries(), - ).filter( - ([name, handle]) => - handle.status === 'READY' && - handle.connection && - (!targetServer || name === targetServer), - ); + const handles = this.getReadyHandles(targetServer); for (const [name, handle] of handles) { - if (!handle.connection) { - continue; - } try { await this.serverManager.warmupTypescriptServer(handle); const response = await handle.connection.request( @@ -596,19 +550,9 @@ export class NativeLspService { limit = 50, ): Promise { const targetServer = serverName ?? item.serverName; - const handles = Array.from( - this.serverManager.getHandles().entries(), - ).filter( - ([name, handle]) => - handle.status === 'READY' && - handle.connection && - (!targetServer || name === targetServer), - ); + const handles = this.getReadyHandles(targetServer); for (const [name, handle] of handles) { - if (!handle.connection) { - continue; - } try { await this.serverManager.warmupTypescriptServer(handle); const response = await handle.connection.request( @@ -651,21 +595,10 @@ export class NativeLspService { uri: string, serverName?: string, ): Promise { - const handles = Array.from( - this.serverManager.getHandles().entries(), - ).filter( - ([name, handle]) => - handle.status === 'READY' && - handle.connection && - (!serverName || name === serverName), - ); - + const handles = this.getReadyHandles(serverName); const allDiagnostics: LspDiagnostic[] = []; for (const [name, handle] of handles) { - if (!handle.connection) { - continue; - } try { await this.serverManager.warmupTypescriptServer(handle); @@ -709,21 +642,10 @@ export class NativeLspService { serverName?: string, limit = 100, ): Promise { - const handles = Array.from( - this.serverManager.getHandles().entries(), - ).filter( - ([name, handle]) => - handle.status === 'READY' && - handle.connection && - (!serverName || name === serverName), - ); - + const handles = this.getReadyHandles(serverName); const results: LspFileDiagnostics[] = []; for (const [name, handle] of handles) { - if (!handle.connection) { - continue; - } try { await this.serverManager.warmupTypescriptServer(handle); @@ -775,19 +697,9 @@ export class NativeLspService { serverName?: string, limit = 20, ): Promise { - const handles = Array.from( - this.serverManager.getHandles().entries(), - ).filter( - ([name, handle]) => - handle.status === 'READY' && - handle.connection && - (!serverName || name === serverName), - ); + const handles = this.getReadyHandles(serverName); for (const [name, handle] of handles) { - if (!handle.connection) { - continue; - } try { await this.serverManager.warmupTypescriptServer(handle); @@ -930,13 +842,6 @@ export class NativeLspService { fs.writeFileSync(filePath, lines.join('\n'), 'utf-8'); } - private isTypescriptServer(handle: LspServerHandle): boolean { - return ( - handle.config.name.includes('typescript') || - (handle.config.command?.includes('typescript') ?? false) - ); - } - private isNoProjectErrorResponse(response: unknown): boolean { if (!response) { return false; diff --git a/packages/cli/src/services/lsp/constants.ts b/packages/cli/src/services/lsp/constants.ts index b76c09aa7..e5874d9fc 100644 --- a/packages/cli/src/services/lsp/constants.ts +++ b/packages/cli/src/services/lsp/constants.ts @@ -25,9 +25,6 @@ export const DEFAULT_LSP_WARMUP_DELAY_MS = 150; /** Default timeout for command existence check in milliseconds */ export const DEFAULT_LSP_COMMAND_CHECK_TIMEOUT_MS = 2000; -/** Default timeout for LSP server shutdown in milliseconds */ -export const DEFAULT_LSP_SHUTDOWN_TIMEOUT_MS = 5000; - // ============================================================================ // Retry Constants // ============================================================================ @@ -105,106 +102,3 @@ export const CODE_ACTION_KIND_LABELS: Record = { 'source.organizeImports': 'source.organizeImports', 'source.fixAll': 'source.fixAll', }; - -// ============================================================================ -// Language Detection -// ============================================================================ - -/** - * Common root marker files that indicate project type/language. - */ -export const COMMON_ROOT_MARKERS = [ - 'package.json', - 'tsconfig.json', - 'pyproject.toml', - 'go.mod', - 'Cargo.toml', - 'pom.xml', - 'build.gradle', - 'composer.json', - 'Gemfile', - 'mix.exs', - 'deno.json', -] as const; - -/** - * Mapping from root marker files to programming languages. - */ -export const MARKER_TO_LANGUAGE: Record = { - 'package.json': 'javascript', - 'tsconfig.json': 'typescript', - 'pyproject.toml': 'python', - 'go.mod': 'go', - 'Cargo.toml': 'rust', - 'pom.xml': 'java', - 'build.gradle': 'java', - 'composer.json': 'php', - Gemfile: 'ruby', - '*.sln': 'csharp', - 'mix.exs': 'elixir', - 'deno.json': 'deno', -}; - -/** - * Default mapping from file extensions to language identifiers. - */ -export const DEFAULT_EXTENSION_TO_LANGUAGE: Record = { - js: 'javascript', - ts: 'typescript', - jsx: 'javascriptreact', - tsx: 'typescriptreact', - py: 'python', - go: 'go', - rs: 'rust', - java: 'java', - cpp: 'cpp', - c: 'c', - php: 'php', - rb: 'ruby', - cs: 'csharp', - vue: 'vue', - svelte: 'svelte', - html: 'html', - css: 'css', - json: 'json', - yaml: 'yaml', - yml: 'yaml', -}; - -/** - * Glob patterns to exclude when detecting languages. - */ -export const LANGUAGE_DETECTION_EXCLUDE_PATTERNS = [ - '**/node_modules/**', - '**/.git/**', - '**/dist/**', - '**/build/**', -] as const; - -// ============================================================================ -// Default Limits for LSP Operations -// ============================================================================ - -/** Default limit for workspace symbol search results */ -export const DEFAULT_LSP_WORKSPACE_SYMBOL_LIMIT = 50; - -/** Default limit for definition/implementation results */ -export const DEFAULT_LSP_DEFINITION_LIMIT = 50; - -/** Default limit for reference results */ -export const DEFAULT_LSP_REFERENCE_LIMIT = 200; - -/** Default limit for document symbol results */ -export const DEFAULT_LSP_DOCUMENT_SYMBOL_LIMIT = 200; - -/** Default limit for call hierarchy results */ -export const DEFAULT_LSP_CALL_HIERARCHY_LIMIT = 50; - -/** Default limit for diagnostics results */ -export const DEFAULT_LSP_DIAGNOSTICS_LIMIT = 100; - -/** Default limit for code action results */ -export const DEFAULT_LSP_CODE_ACTION_LIMIT = 20; - -/** Maximum number of files to scan during language detection */ -export const DEFAULT_LSP_LANGUAGE_DETECTION_FILE_LIMIT = 1000;