feat: 统一LSP工具并扩展操作支持

- 创建统一的LSP工具,整合了之前的多个分散LSP工具
- 增加对更多LSP操作的支持,包括hover、documentSymbol、goToImplementation等
- 扩展LSP类型定义,支持Call Hierarchy等高级功能
- 更新配置和测试文件以适配新的LSP工具架构
- 保持向后兼容性,同时引入新工具名称映射

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>

此更改是LSP工具重构计划的一部分,旨在提供更统一和功能完备的LSP集成体验。
This commit is contained in:
yiliang114 2026-01-18 19:34:17 +08:00
parent a14d1e27bb
commit d9328fa478
13 changed files with 4092 additions and 141 deletions

View file

@ -20,22 +20,24 @@ import { ExtensionStorage, type Extension } from './extension.js';
import * as ServerConfig from '@qwen-code/qwen-code-core';
import { isWorkspaceTrusted } from './trustedFolders.js';
import { ExtensionEnablementManager } from './extensions/extensionEnablement.js';
import { NativeLspService } from '../services/lsp/NativeLspService.js';
const mockDiscoverAndPrepare = vi.fn();
const mockStartLsp = vi.fn();
const mockDefinitions = vi.fn().mockResolvedValue([]);
const mockReferences = vi.fn().mockResolvedValue([]);
const mockWorkspaceSymbols = vi.fn().mockResolvedValue([]);
const nativeLspServiceMock = vi.fn().mockImplementation(() => ({
discoverAndPrepare: mockDiscoverAndPrepare,
start: mockStartLsp,
definitions: mockDefinitions,
references: mockReferences,
workspaceSymbols: mockWorkspaceSymbols,
}));
const createNativeLspServiceInstance = () => ({
discoverAndPrepare: vi.fn(),
start: vi.fn(),
definitions: vi.fn().mockResolvedValue([]),
references: vi.fn().mockResolvedValue([]),
workspaceSymbols: vi.fn().mockResolvedValue([]),
});
vi.mock('../services/lsp/NativeLspService.js', () => ({
NativeLspService: nativeLspServiceMock,
NativeLspService: vi.fn().mockImplementation(() => ({
discoverAndPrepare: vi.fn(),
start: vi.fn(),
definitions: vi.fn().mockResolvedValue([]),
references: vi.fn().mockResolvedValue([]),
workspaceSymbols: vi.fn().mockResolvedValue([]),
})),
}));
vi.mock('./trustedFolders.js', () => ({
@ -44,6 +46,17 @@ vi.mock('./trustedFolders.js', () => ({
.mockReturnValue({ isTrusted: true, source: 'file' }), // Default to trusted
}));
const nativeLspServiceMock = vi.mocked(NativeLspService);
const getLastLspInstance = () => {
const results = nativeLspServiceMock.mock.results;
if (results.length === 0) {
return undefined;
}
return results[results.length - 1]?.value as ReturnType<
typeof createNativeLspServiceInstance
>;
};
vi.mock('fs', async (importOriginal) => {
const actualFs = await importOriginal<typeof import('fs')>();
const pathMod = await import('node:path');
@ -533,16 +546,10 @@ describe('loadCliConfig', () => {
beforeEach(() => {
vi.resetAllMocks();
mockDiscoverAndPrepare.mockReset();
mockStartLsp.mockReset();
mockWorkspaceSymbols.mockReset();
mockWorkspaceSymbols.mockResolvedValue([]);
nativeLspServiceMock.mockReset();
nativeLspServiceMock.mockImplementation(() => ({
discoverAndPrepare: mockDiscoverAndPrepare,
start: mockStartLsp,
workspaceSymbols: mockWorkspaceSymbols,
}));
nativeLspServiceMock.mockImplementation(() =>
createNativeLspServiceInstance(),
);
vi.mocked(os.homedir).mockReturnValue('/mock/home/user');
vi.stubEnv('GEMINI_API_KEY', 'test-api-key');
});
@ -637,8 +644,10 @@ describe('loadCliConfig', () => {
expect(config.getLspAllowed()).toEqual(['typescript-language-server']);
expect(config.getLspExcluded()).toEqual(['pylsp']);
expect(nativeLspServiceMock).toHaveBeenCalledTimes(1);
expect(mockDiscoverAndPrepare).toHaveBeenCalledTimes(1);
expect(mockStartLsp).toHaveBeenCalledTimes(1);
const lspInstance = getLastLspInstance();
expect(lspInstance).toBeDefined();
expect(lspInstance?.discoverAndPrepare).toHaveBeenCalledTimes(1);
expect(lspInstance?.start).toHaveBeenCalledTimes(1);
const options = nativeLspServiceMock.mock.calls[0][5];
expect(options?.allowedServers).toEqual(['typescript-language-server']);
@ -664,7 +673,7 @@ describe('loadCliConfig', () => {
expect(config.isLspEnabled()).toBe(true);
expect(nativeLspServiceMock).not.toHaveBeenCalled();
expect(mockDiscoverAndPrepare).not.toHaveBeenCalled();
expect(getLastLspInstance()).toBeUndefined();
});
it('should set showMemoryUsage to false by default from settings if CLI flag is not present', async () => {