mirror of
https://github.com/QwenLM/qwen-code.git
synced 2025-12-20 16:57:46 +00:00
Improve the performance of filename completion over large repositories. (#938)
This commit is contained in:
@@ -42,6 +42,7 @@ describe('useCompletion git-aware filtering integration', () => {
|
||||
shouldIgnoreFile: vi.fn(),
|
||||
filterFiles: vi.fn(),
|
||||
getIgnoreInfo: vi.fn(() => ({ gitIgnored: [], customIgnored: [] })),
|
||||
glob: vi.fn().mockResolvedValue([]),
|
||||
};
|
||||
|
||||
mockConfig = {
|
||||
@@ -225,4 +226,27 @@ describe('useCompletion git-aware filtering integration', () => {
|
||||
{ label: 'component.tsx', value: 'component.tsx' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should use glob for top-level @ completions when available', async () => {
|
||||
const globResults = [`${testCwd}/src/index.ts`, `${testCwd}/README.md`];
|
||||
mockFileDiscoveryService.glob.mockResolvedValue(globResults);
|
||||
|
||||
const { result } = renderHook(() =>
|
||||
useCompletion('@s', testCwd, true, slashCommands, mockConfig),
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
});
|
||||
|
||||
expect(mockFileDiscoveryService.glob).toHaveBeenCalledWith('**/s*', {
|
||||
cwd: testCwd,
|
||||
dot: true,
|
||||
});
|
||||
expect(fs.readdir).not.toHaveBeenCalled(); // Ensure glob is used instead of readdir
|
||||
expect(result.current.suggestions).toEqual([
|
||||
{ label: 'README.md', value: 'README.md' },
|
||||
{ label: 'src/index.ts', value: 'src/index.ts' },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
unescapePath,
|
||||
getErrorMessage,
|
||||
Config,
|
||||
FileDiscoveryService,
|
||||
} from '@gemini-cli/core';
|
||||
import {
|
||||
MAX_SUGGESTIONS_TO_SHOW,
|
||||
@@ -251,21 +252,53 @@ export function useCompletion(
|
||||
return foundSuggestions.slice(0, maxResults);
|
||||
};
|
||||
|
||||
const findFilesWithGlob = async (
|
||||
searchPrefix: string,
|
||||
fileDiscoveryService: FileDiscoveryService,
|
||||
maxResults = 50,
|
||||
): Promise<Suggestion[]> => {
|
||||
const globPattern = `**/${searchPrefix}*`;
|
||||
const files = await fileDiscoveryService.glob(globPattern, {
|
||||
cwd,
|
||||
dot: true,
|
||||
});
|
||||
|
||||
const suggestions: Suggestion[] = files
|
||||
.map((file: string) => {
|
||||
const relativePath = path.relative(cwd, file);
|
||||
return {
|
||||
label: relativePath,
|
||||
value: escapePath(relativePath),
|
||||
};
|
||||
})
|
||||
.slice(0, maxResults);
|
||||
|
||||
return suggestions;
|
||||
};
|
||||
|
||||
const fetchSuggestions = async () => {
|
||||
setIsLoadingSuggestions(true);
|
||||
let fetchedSuggestions: Suggestion[] = [];
|
||||
|
||||
// Get centralized file discovery service if config is available
|
||||
const fileDiscovery = config ? await config.getFileService() : null;
|
||||
const fileDiscoveryService = config
|
||||
? await config.getFileService()
|
||||
: null;
|
||||
|
||||
try {
|
||||
// If there's no slash, or it's the root, do a recursive search from cwd
|
||||
if (partialPath.indexOf('/') === -1 && prefix) {
|
||||
fetchedSuggestions = await findFilesRecursively(
|
||||
cwd,
|
||||
prefix,
|
||||
fileDiscovery,
|
||||
);
|
||||
if (fileDiscoveryService) {
|
||||
fetchedSuggestions = await findFilesWithGlob(
|
||||
prefix,
|
||||
fileDiscoveryService,
|
||||
);
|
||||
} else {
|
||||
fetchedSuggestions = await findFilesRecursively(
|
||||
cwd,
|
||||
prefix,
|
||||
fileDiscoveryService,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// Original behavior: list files in the specific directory
|
||||
const lowerPrefix = prefix.toLowerCase();
|
||||
@@ -282,7 +315,10 @@ export function useCompletion(
|
||||
cwd,
|
||||
path.join(baseDirAbsolute, entry.name),
|
||||
);
|
||||
if (fileDiscovery && fileDiscovery.shouldIgnoreFile(relativePath)) {
|
||||
if (
|
||||
fileDiscoveryService &&
|
||||
fileDiscoveryService.shouldIgnoreFile(relativePath)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user