Skip to content

Commit a67b2dc

Browse files
Naituwwutian
authored andcommitted
fix: Improve @ file search compatibility with large or complext projects
- Increase file listing limit to handle larger projects (5000 -> 500000) - Implement caching for file lists to prevent performance degradation - Respect VSCode search configuration (`search.useIgnoreFiles`, `search.useGlobalIgnoreFiles`, `search.useParentIgnoreFiles`) when listing files fixes #5721
1 parent 36d56e8 commit a67b2dc

File tree

5 files changed

+495
-4
lines changed

5 files changed

+495
-4
lines changed

src/core/task/__tests__/Task.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ vi.mock("vscode", () => {
116116
stat: vi.fn().mockResolvedValue({ type: 1 }), // FileType.File = 1
117117
},
118118
onDidSaveTextDocument: vi.fn(() => mockDisposable),
119+
onDidChangeConfiguration: vi.fn(() => mockDisposable),
119120
getConfiguration: vi.fn(() => ({ get: (key: string, defaultValue: any) => defaultValue })),
120121
},
121122
env: {

src/core/webview/webviewMessageHandler.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1400,10 +1400,16 @@ export const webviewMessageHandler = async (
14001400
break
14011401
}
14021402
try {
1403-
// Call file search service with query from message
1403+
// Get cached file list from WorkspaceTracker
1404+
const workspaceFiles = provider.workspaceTracker
1405+
? await provider.workspaceTracker.getRipgrepFileList()
1406+
: []
1407+
1408+
// Call file search service with query and cached files from message
14041409
const results = await searchWorkspaceFiles(
14051410
message.query || "",
14061411
workspacePath,
1412+
workspaceFiles,
14071413
20, // Use default limit, as filtering is now done in the backend
14081414
)
14091415

src/integrations/workspace/WorkspaceTracker.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import * as path from "path"
44
import { listFiles } from "../../services/glob/list-files"
55
import { ClineProvider } from "../../core/webview/ClineProvider"
66
import { toRelativePath, getWorkspacePath } from "../../utils/path"
7+
import { executeRipgrepForFiles, FileResult } from "../../services/search/file-search"
8+
import { isPathInIgnoredDirectory } from "../../services/glob/ignore-utils"
79

810
const MAX_INITIAL_FILES = 1_000
911

@@ -16,14 +18,105 @@ class WorkspaceTracker {
1618
private prevWorkSpacePath: string | undefined
1719
private resetTimer: NodeJS.Timeout | null = null
1820

21+
// Ripgrep cache related properties
22+
private ripgrepFileCache: FileResult[] | null = null
23+
private ripgrepCacheWorkspacePath: string | undefined = undefined
24+
private ripgrepOperationPromise: Promise<FileResult[]> | null = null
25+
1926
get cwd() {
2027
return getWorkspacePath()
2128
}
29+
2230
constructor(provider: ClineProvider) {
2331
this.providerRef = new WeakRef(provider)
2432
this.registerListeners()
2533
}
2634

35+
/**
36+
* Get ripgrep extra options based on VSCode search configuration
37+
*/
38+
private getRipgrepExtraOptions(): string[] {
39+
const config = vscode.workspace.getConfiguration("search")
40+
const extraOptions: string[] = []
41+
42+
const useIgnoreFiles = config.get<boolean>("useIgnoreFiles", true)
43+
44+
if (!useIgnoreFiles) {
45+
extraOptions.push("--no-ignore")
46+
} else {
47+
const useGlobalIgnoreFiles = config.get<boolean>("useGlobalIgnoreFiles", true)
48+
const useParentIgnoreFiles = config.get<boolean>("useParentIgnoreFiles", true)
49+
50+
if (!useGlobalIgnoreFiles) {
51+
extraOptions.push("--no-ignore-global")
52+
}
53+
54+
if (!useParentIgnoreFiles) {
55+
extraOptions.push("--no-ignore-parent")
56+
}
57+
}
58+
59+
return extraOptions
60+
}
61+
62+
/**
63+
* Get comprehensive file list using ripgrep with caching
64+
* This provides a more complete file list than the limited filePaths set
65+
*/
66+
async getRipgrepFileList(): Promise<FileResult[]> {
67+
const currentWorkspacePath = this.cwd
68+
69+
if (!currentWorkspacePath) {
70+
return []
71+
}
72+
73+
// Return cached results if available and workspace hasn't changed
74+
if (this.ripgrepFileCache && this.ripgrepCacheWorkspacePath === currentWorkspacePath) {
75+
return this.ripgrepFileCache
76+
}
77+
78+
// If there's an ongoing operation, wait for it
79+
if (this.ripgrepOperationPromise) {
80+
try {
81+
return await this.ripgrepOperationPromise
82+
} catch (error) {
83+
// If the ongoing operation failed, we'll start a new one below
84+
this.ripgrepOperationPromise = null
85+
}
86+
}
87+
88+
try {
89+
// Start new operation and store the promise
90+
this.ripgrepOperationPromise = executeRipgrepForFiles(
91+
currentWorkspacePath,
92+
500000,
93+
this.getRipgrepExtraOptions(),
94+
)
95+
const fileResults = await this.ripgrepOperationPromise
96+
97+
// Cache the results and clear the operation promise
98+
this.ripgrepFileCache = fileResults
99+
this.ripgrepCacheWorkspacePath = currentWorkspacePath
100+
this.ripgrepOperationPromise = null
101+
102+
return fileResults
103+
} catch (error) {
104+
console.error("Error getting ripgrep file list:", error)
105+
this.ripgrepOperationPromise = null
106+
return []
107+
}
108+
}
109+
110+
/**
111+
* Clear the ripgrep file cache
112+
* Called when workspace changes or files are modified
113+
*/
114+
private clearRipgrepCache() {
115+
this.ripgrepFileCache = null
116+
this.ripgrepCacheWorkspacePath = undefined
117+
this.ripgrepOperationPromise = null
118+
}
119+
27120
async initializeFilePaths() {
28121
// should not auto get filepaths for desktop since it would immediately show permission popup before cline ever creates a file
29122
if (!this.cwd) {
@@ -36,13 +129,19 @@ class WorkspaceTracker {
36129
}
37130
files.slice(0, MAX_INITIAL_FILES).forEach((file) => this.filePaths.add(this.normalizeFilePath(file)))
38131
this.workspaceDidUpdate()
132+
133+
// preheat filelist
134+
this.getRipgrepFileList()
39135
}
40136

41137
private registerListeners() {
42138
const watcher = vscode.workspace.createFileSystemWatcher("**")
43139
this.prevWorkSpacePath = this.cwd
44140
this.disposables.push(
45141
watcher.onDidCreate(async (uri) => {
142+
if (!isPathInIgnoredDirectory(uri.fsPath)) {
143+
this.clearRipgrepCache()
144+
}
46145
await this.addFilePath(uri.fsPath)
47146
this.workspaceDidUpdate()
48147
}),
@@ -51,6 +150,9 @@ class WorkspaceTracker {
51150
// Renaming files triggers a delete and create event
52151
this.disposables.push(
53152
watcher.onDidDelete(async (uri) => {
153+
if (!isPathInIgnoredDirectory(uri.fsPath)) {
154+
this.clearRipgrepCache()
155+
}
54156
if (await this.removeFilePath(uri.fsPath)) {
55157
this.workspaceDidUpdate()
56158
}
@@ -71,6 +173,20 @@ class WorkspaceTracker {
71173
}
72174
}),
73175
)
176+
177+
// Listen for VSCode configuration changes
178+
this.disposables.push(
179+
vscode.workspace.onDidChangeConfiguration((event: vscode.ConfigurationChangeEvent) => {
180+
// Clear cache when search-related configuration changes
181+
if (
182+
event.affectsConfiguration("search.useIgnoreFiles") ||
183+
event.affectsConfiguration("search.useGlobalIgnoreFiles") ||
184+
event.affectsConfiguration("search.useParentIgnoreFiles")
185+
) {
186+
this.clearRipgrepCache()
187+
}
188+
}),
189+
)
74190
}
75191

76192
private getOpenedTabsInfo() {
@@ -97,6 +213,8 @@ class WorkspaceTracker {
97213
}
98214
this.resetTimer = setTimeout(async () => {
99215
if (this.prevWorkSpacePath !== this.cwd) {
216+
// Clear cache when workspace changes
217+
this.clearRipgrepCache()
100218
await this.providerRef.deref()?.postMessageToWebview({
101219
type: "workspaceUpdated",
102220
filePaths: [],

0 commit comments

Comments
 (0)