@@ -11,9 +11,13 @@ import { GlobalFileNames } from "../../shared/globalFileNames"
1111const ROOMODES_FILENAME = ".roomodes"
1212
1313export class CustomModesManager {
14+ private static readonly cacheTTL = 10_000
15+
1416 private disposables : vscode . Disposable [ ] = [ ]
1517 private isWriting = false
1618 private writeQueue : Array < ( ) => Promise < void > > = [ ]
19+ private cachedModes : ModeConfig [ ] | null = null
20+ private cachedAt : number = 0
1721
1822 constructor (
1923 private readonly context : vscode . ExtensionContext ,
@@ -25,6 +29,7 @@ export class CustomModesManager {
2529
2630 private async queueWrite ( operation : ( ) => Promise < void > ) : Promise < void > {
2731 this . writeQueue . push ( operation )
32+
2833 if ( ! this . isWriting ) {
2934 await this . processWriteQueue ( )
3035 }
@@ -36,9 +41,11 @@ export class CustomModesManager {
3641 }
3742
3843 this . isWriting = true
44+
3945 try {
4046 while ( this . writeQueue . length > 0 ) {
4147 const operation = this . writeQueue . shift ( )
48+
4249 if ( operation ) {
4350 await operation ( )
4451 }
@@ -50,9 +57,11 @@ export class CustomModesManager {
5057
5158 private async getWorkspaceRoomodes ( ) : Promise < string | undefined > {
5259 const workspaceFolders = vscode . workspace . workspaceFolders
60+
5361 if ( ! workspaceFolders || workspaceFolders . length === 0 ) {
5462 return undefined
5563 }
64+
5665 const workspaceRoot = getWorkspacePath ( )
5766 const roomodesPath = path . join ( workspaceRoot , ROOMODES_FILENAME )
5867 const exists = await fileExistsAtPath ( roomodesPath )
@@ -73,10 +82,7 @@ export class CustomModesManager {
7382 const source = isRoomodes ? ( "project" as const ) : ( "global" as const )
7483
7584 // Add source to each mode
76- return result . data . customModes . map ( ( mode ) => ( {
77- ...mode ,
78- source,
79- } ) )
85+ return result . data . customModes . map ( ( mode ) => ( { ...mode , source } ) )
8086 } catch ( error ) {
8187 const errorMsg = `Failed to load modes from ${ filePath } : ${ error instanceof Error ? error . message : String ( error ) } `
8288 console . error ( `[CustomModesManager] ${ errorMsg } ` )
@@ -92,36 +98,30 @@ export class CustomModesManager {
9298 for ( const mode of projectModes ) {
9399 if ( ! slugs . has ( mode . slug ) ) {
94100 slugs . add ( mode . slug )
95- merged . push ( {
96- ...mode ,
97- source : "project" ,
98- } )
101+ merged . push ( { ...mode , source : "project" } )
99102 }
100103 }
101104
102105 // Add non-duplicate global modes
103106 for ( const mode of globalModes ) {
104107 if ( ! slugs . has ( mode . slug ) ) {
105108 slugs . add ( mode . slug )
106- merged . push ( {
107- ...mode ,
108- source : "global" ,
109- } )
109+ merged . push ( { ...mode , source : "global" } )
110110 }
111111 }
112112
113113 return merged
114114 }
115115
116- async getCustomModesFilePath ( ) : Promise < string > {
116+ public async getCustomModesFilePath ( ) : Promise < string > {
117117 const settingsDir = await this . ensureSettingsDirectoryExists ( )
118118 const filePath = path . join ( settingsDir , GlobalFileNames . customModes )
119119 const fileExists = await fileExistsAtPath ( filePath )
120+
120121 if ( ! fileExists ) {
121- await this . queueWrite ( async ( ) => {
122- await fs . writeFile ( filePath , JSON . stringify ( { customModes : [ ] } , null , 2 ) )
123- } )
122+ await this . queueWrite ( ( ) => fs . writeFile ( filePath , JSON . stringify ( { customModes : [ ] } , null , 2 ) ) )
124123 }
124+
125125 return filePath
126126 }
127127
@@ -133,10 +133,12 @@ export class CustomModesManager {
133133 vscode . workspace . onDidSaveTextDocument ( async ( document ) => {
134134 if ( arePathsEqual ( document . uri . fsPath , settingsPath ) ) {
135135 const content = await fs . readFile ( settingsPath , "utf-8" )
136+
136137 const errorMessage =
137138 "Invalid custom modes format. Please ensure your settings follow the correct JSON format."
138139
139140 let config : any
141+
140142 try {
141143 config = JSON . parse ( content )
142144 } catch ( error ) {
@@ -159,13 +161,15 @@ export class CustomModesManager {
159161 // Merge modes from both sources (.roomodes takes precedence)
160162 const mergedModes = await this . mergeCustomModes ( roomodesModes , result . data . customModes )
161163 await this . context . globalState . update ( "customModes" , mergedModes )
164+ this . clearCache ( )
162165 await this . onUpdate ( )
163166 }
164167 } ) ,
165168 )
166169
167170 // Watch .roomodes file if it exists
168171 const roomodesPath = await this . getWorkspaceRoomodes ( )
172+
169173 if ( roomodesPath ) {
170174 this . disposables . push (
171175 vscode . workspace . onDidSaveTextDocument ( async ( document ) => {
@@ -175,39 +179,47 @@ export class CustomModesManager {
175179 // .roomodes takes precedence
176180 const mergedModes = await this . mergeCustomModes ( roomodesModes , settingsModes )
177181 await this . context . globalState . update ( "customModes" , mergedModes )
182+ this . clearCache ( )
178183 await this . onUpdate ( )
179184 }
180185 } ) ,
181186 )
182187 }
183188 }
184189
185- async getCustomModes ( ) : Promise < ModeConfig [ ] > {
186- // Get modes from settings file
190+ public async getCustomModes ( ) : Promise < ModeConfig [ ] > {
191+ // Check if we have a valid cached result.
192+ const now = Date . now ( )
193+
194+ if ( this . cachedModes && now - this . cachedAt < CustomModesManager . cacheTTL ) {
195+ return this . cachedModes
196+ }
197+
198+ // Get modes from settings file.
187199 const settingsPath = await this . getCustomModesFilePath ( )
188200 const settingsModes = await this . loadModesFromFile ( settingsPath )
189201
190- // Get modes from .roomodes if it exists
202+ // Get modes from .roomodes if it exists.
191203 const roomodesPath = await this . getWorkspaceRoomodes ( )
192204 const roomodesModes = roomodesPath ? await this . loadModesFromFile ( roomodesPath ) : [ ]
193205
194- // Create maps to store modes by source
206+ // Create maps to store modes by source.
195207 const projectModes = new Map < string , ModeConfig > ( )
196208 const globalModes = new Map < string , ModeConfig > ( )
197209
198- // Add project modes (they take precedence)
210+ // Add project modes (they take precedence).
199211 for ( const mode of roomodesModes ) {
200212 projectModes . set ( mode . slug , { ...mode , source : "project" as const } )
201213 }
202214
203- // Add global modes
215+ // Add global modes.
204216 for ( const mode of settingsModes ) {
205217 if ( ! projectModes . has ( mode . slug ) ) {
206218 globalModes . set ( mode . slug , { ...mode , source : "global" as const } )
207219 }
208220 }
209221
210- // Combine modes in the correct order: project modes first, then global modes
222+ // Combine modes in the correct order: project modes first, then global modes.
211223 const mergedModes = [
212224 ...roomodesModes . map ( ( mode ) => ( { ...mode , source : "project" as const } ) ) ,
213225 ...settingsModes
@@ -216,22 +228,30 @@ export class CustomModesManager {
216228 ]
217229
218230 await this . context . globalState . update ( "customModes" , mergedModes )
231+
232+ this . cachedModes = mergedModes
233+ this . cachedAt = now
234+
219235 return mergedModes
220236 }
221- async updateCustomMode ( slug : string , config : ModeConfig ) : Promise < void > {
237+
238+ public async updateCustomMode ( slug : string , config : ModeConfig ) : Promise < void > {
222239 try {
223240 const isProjectMode = config . source === "project"
224241 let targetPath : string
225242
226243 if ( isProjectMode ) {
227244 const workspaceFolders = vscode . workspace . workspaceFolders
245+
228246 if ( ! workspaceFolders || workspaceFolders . length === 0 ) {
229247 logger . error ( "Failed to update project mode: No workspace folder found" , { slug } )
230248 throw new Error ( "No workspace folder found for project-specific mode" )
231249 }
250+
232251 const workspaceRoot = getWorkspacePath ( )
233252 targetPath = path . join ( workspaceRoot , ROOMODES_FILENAME )
234253 const exists = await fileExistsAtPath ( targetPath )
254+
235255 logger . info ( `${ exists ? "Updating" : "Creating" } project mode in ${ ROOMODES_FILENAME } ` , {
236256 slug,
237257 workspace : workspaceRoot ,
@@ -241,7 +261,7 @@ export class CustomModesManager {
241261 }
242262
243263 await this . queueWrite ( async ( ) => {
244- // Ensure source is set correctly based on target file
264+ // Ensure source is set correctly based on target file.
245265 const modeWithSource = {
246266 ...config ,
247267 source : isProjectMode ? ( "project" as const ) : ( "global" as const ) ,
@@ -253,6 +273,7 @@ export class CustomModesManager {
253273 return updatedModes
254274 } )
255275
276+ this . clearCache ( )
256277 await this . refreshMergedState ( )
257278 } )
258279 } catch ( error ) {
@@ -261,22 +282,26 @@ export class CustomModesManager {
261282 vscode . window . showErrorMessage ( `Failed to update custom mode: ${ errorMessage } ` )
262283 }
263284 }
285+
264286 private async updateModesInFile ( filePath : string , operation : ( modes : ModeConfig [ ] ) => ModeConfig [ ] ) : Promise < void > {
265287 let content = "{}"
288+
266289 try {
267290 content = await fs . readFile ( filePath , "utf-8" )
268291 } catch ( error ) {
269- // File might not exist yet
292+ // File might not exist yet.
270293 content = JSON . stringify ( { customModes : [ ] } )
271294 }
272295
273296 let settings
297+
274298 try {
275299 settings = JSON . parse ( content )
276300 } catch ( error ) {
277301 console . error ( `[CustomModesManager] Failed to parse JSON from ${ filePath } :` , error )
278302 settings = { customModes : [ ] }
279303 }
304+
280305 settings . customModes = operation ( settings . customModes || [ ] )
281306 await fs . writeFile ( filePath , JSON . stringify ( settings , null , 2 ) , "utf-8" )
282307 }
@@ -290,10 +315,13 @@ export class CustomModesManager {
290315 const mergedModes = await this . mergeCustomModes ( roomodesModes , settingsModes )
291316
292317 await this . context . globalState . update ( "customModes" , mergedModes )
318+
319+ this . clearCache ( )
320+
293321 await this . onUpdate ( )
294322 }
295323
296- async deleteCustomMode ( slug : string ) : Promise < void > {
324+ public async deleteCustomMode ( slug : string ) : Promise < void > {
297325 try {
298326 const settingsPath = await this . getCustomModesFilePath ( )
299327 const roomodesPath = await this . getWorkspaceRoomodes ( )
@@ -320,6 +348,8 @@ export class CustomModesManager {
320348 await this . updateModesInFile ( settingsPath , ( modes ) => modes . filter ( ( m ) => m . slug !== slug ) )
321349 }
322350
351+ // Clear cache when modes are deleted
352+ this . clearCache ( )
323353 await this . refreshMergedState ( )
324354 } )
325355 } catch ( error ) {
@@ -335,11 +365,12 @@ export class CustomModesManager {
335365 return settingsDir
336366 }
337367
338- async resetCustomModes ( ) : Promise < void > {
368+ public async resetCustomModes ( ) : Promise < void > {
339369 try {
340370 const filePath = await this . getCustomModesFilePath ( )
341371 await fs . writeFile ( filePath , JSON . stringify ( { customModes : [ ] } , null , 2 ) )
342372 await this . context . globalState . update ( "customModes" , [ ] )
373+ this . clearCache ( )
343374 await this . onUpdate ( )
344375 } catch ( error ) {
345376 vscode . window . showErrorMessage (
@@ -348,10 +379,16 @@ export class CustomModesManager {
348379 }
349380 }
350381
382+ private clearCache ( ) : void {
383+ this . cachedModes = null
384+ this . cachedAt = 0
385+ }
386+
351387 dispose ( ) : void {
352388 for ( const disposable of this . disposables ) {
353389 disposable . dispose ( )
354390 }
391+
355392 this . disposables = [ ]
356393 }
357394}
0 commit comments