@@ -4,6 +4,8 @@ import * as path from "path"
44import { listFiles } from "../../services/glob/list-files"
55import { ClineProvider } from "../../core/webview/ClineProvider"
66import { toRelativePath , getWorkspacePath } from "../../utils/path"
7+ import { executeRipgrepForFiles , FileResult } from "../../services/search/file-search"
8+ import { isPathInIgnoredDirectory } from "../../services/glob/ignore-utils"
79
810const 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