mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-03 06:00:49 +00:00
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:
parent
a14d1e27bb
commit
d9328fa478
13 changed files with 4092 additions and 141 deletions
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -193,6 +193,67 @@ class NativeLspClient implements LspClient {
|
|||
limit,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hover information (documentation, type info) for a symbol.
|
||||
*/
|
||||
hover(
|
||||
location: Parameters<NativeLspService['hover']>[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<NativeLspService['implementations']>[0],
|
||||
serverName?: string,
|
||||
limit?: number,
|
||||
) {
|
||||
return this.service.implementations(location, serverName, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare call hierarchy item at a position (functions/methods).
|
||||
*/
|
||||
prepareCallHierarchy(
|
||||
location: Parameters<NativeLspService['prepareCallHierarchy']>[0],
|
||||
serverName?: string,
|
||||
limit?: number,
|
||||
) {
|
||||
return this.service.prepareCallHierarchy(location, serverName, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all functions/methods that call the given function.
|
||||
*/
|
||||
incomingCalls(
|
||||
item: Parameters<NativeLspService['incomingCalls']>[0],
|
||||
serverName?: string,
|
||||
limit?: number,
|
||||
) {
|
||||
return this.service.incomingCalls(item, serverName, limit);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all functions/methods called by the given function.
|
||||
*/
|
||||
outgoingCalls(
|
||||
item: Parameters<NativeLspService['outgoingCalls']>[0],
|
||||
serverName?: string,
|
||||
limit?: number,
|
||||
) {
|
||||
return this.service.outgoingCalls(item, serverName, limit);
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeOutputFormat(
|
||||
|
|
@ -812,6 +873,7 @@ export async function loadCliConfig(
|
|||
const lspEnabled = settings.lsp?.enabled ?? false;
|
||||
const lspAllowed = settings.lsp?.allowed ?? settings.mcp?.allowed;
|
||||
const lspExcluded = settings.lsp?.excluded ?? settings.mcp?.excluded;
|
||||
const lspLanguageServers = settings.lsp?.languageServers;
|
||||
let lspClient: LspClient | undefined;
|
||||
const question = argv.promptInteractive || argv.prompt || '';
|
||||
const inputFormat: InputFormat =
|
||||
|
|
@ -1149,6 +1211,7 @@ export async function loadCliConfig(
|
|||
allowedServers: lspAllowed,
|
||||
excludedServers: lspExcluded,
|
||||
requireTrustedWorkspace: folderTrust,
|
||||
inlineServerConfigs: lspLanguageServers,
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -1083,6 +1083,17 @@ const SETTINGS_SCHEMA = {
|
|||
'Optional blocklist of LSP server names that should not start.',
|
||||
showInDialog: false,
|
||||
},
|
||||
languageServers: {
|
||||
type: 'object',
|
||||
label: 'LSP Language Servers',
|
||||
category: 'LSP',
|
||||
requiresRestart: true,
|
||||
default: {} as Record<string, unknown>,
|
||||
description:
|
||||
'Inline LSP server configuration (same format as .lsp.json).',
|
||||
showInDialog: false,
|
||||
mergeStrategy: MergeStrategy.SHALLOW_MERGE,
|
||||
},
|
||||
},
|
||||
},
|
||||
useSmartEdit: {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue