@@ -19,7 +19,7 @@ import { API as GitAPI, Repository as GitAPIRepository } from './typings/git';
1919
2020interface CheckboxStateInfo {
2121 state : TreeItemCheckboxState ;
22- timestamp : number ; // When the checkbox was checked
22+ timestamp : number ; // Timestamp when the checkbox was checked (not set for unchecked states)
2323}
2424
2525class FileElement implements IDiffStatus {
@@ -140,6 +140,7 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
140140 private treeView : TreeView < Element > ;
141141 private isPaused : boolean ;
142142 private checkboxStates : Map < string , CheckboxStateInfo > = new Map < string , CheckboxStateInfo > ( ) ;
143+ private folderCheckboxStateCache : Map < string , TreeItemCheckboxState > = new Map < string , TreeItemCheckboxState > ( ) ;
143144
144145 // Other
145146 private readonly disposables : Disposable [ ] = [ ] ;
@@ -311,12 +312,33 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
311312 }
312313
313314 private async handleChangeCheckboxState ( e : TreeCheckboxChangeEvent < Element > ) {
315+ // Clear the folder checkbox state cache since states are changing
316+ this . folderCheckboxStateCache . clear ( ) ;
317+
314318 for ( let [ element , state ] of e . items ) {
315319 if ( element instanceof FileElement || element instanceof FolderElement ) {
316- this . checkboxStates . set ( element . dstAbsPath , {
317- state : state ,
318- timestamp : Date . now ( )
319- } ) ;
320+ const key = element . dstAbsPath ;
321+ const existing = this . checkboxStates . get ( key ) ;
322+
323+ if ( state === TreeItemCheckboxState . Checked ) {
324+ // Only set timestamp when checking the item
325+ this . checkboxStates . set ( key , {
326+ state : state ,
327+ timestamp : Date . now ( )
328+ } ) ;
329+ } else if ( existing ) {
330+ // When unchecking, preserve the timestamp of when it was last checked
331+ this . checkboxStates . set ( key , {
332+ state : state ,
333+ timestamp : existing . timestamp
334+ } ) ;
335+ } else {
336+ // No existing state, just set unchecked with current timestamp
337+ this . checkboxStates . set ( key , {
338+ state : state ,
339+ timestamp : Date . now ( )
340+ } ) ;
341+ }
320342 }
321343 }
322344 }
@@ -403,10 +425,18 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
403425 }
404426
405427 private computeFolderCheckboxState ( folder : FolderElement ) : TreeItemCheckboxState {
428+ // Check cache first
429+ const cached = this . folderCheckboxStateCache . get ( folder . dstAbsPath ) ;
430+ if ( cached !== undefined ) {
431+ return cached ;
432+ }
433+
406434 // Check if user explicitly set state on this folder
407435 const explicitState = this . checkboxStates . get ( folder . dstAbsPath ) ;
408436 if ( explicitState ) {
409- return explicitState . state ;
437+ const result = explicitState . state ;
438+ this . folderCheckboxStateCache . set ( folder . dstAbsPath , result ) ;
439+ return result ;
410440 }
411441
412442 // Otherwise derive from files: folder is checked only if ALL files under it are checked
@@ -429,7 +459,9 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
429459 }
430460 }
431461
432- return ( hasFiles && allChecked ) ? TreeItemCheckboxState . Checked : TreeItemCheckboxState . Unchecked ;
462+ const result = ( hasFiles && allChecked ) ? TreeItemCheckboxState . Checked : TreeItemCheckboxState . Unchecked ;
463+ this . folderCheckboxStateCache . set ( folder . dstAbsPath , result ) ;
464+ return result ;
433465 }
434466
435467 async getChildren ( element ?: Element ) : Promise < Element [ ] > {
@@ -555,8 +587,10 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
555587 this . log ( `${ diff . length } diff entries (${ untrackedCount } untracked)` ) ;
556588
557589 const newFilePaths = new Set < string > ( ) ;
590+ const newFolderPaths = new Set < string > ( ) ;
558591 // Collect files that need mtime checking for async batch processing
559592 const filesToCheckMtime : Array < { filePath : string , stateInfo : CheckboxStateInfo } > = [ ] ;
593+ let checkboxesWereReset = false ;
560594
561595 for ( const entry of diff ) {
562596 const folder = path . dirname ( entry . dstAbsPath ) ;
@@ -575,6 +609,7 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
575609 if ( ! files . has ( currentFolder ) ) {
576610 files . set ( currentFolder , new Array ( ) ) ;
577611 }
612+ newFolderPaths . add ( currentFolder ) ;
578613 currentFolder = path . dirname ( currentFolder )
579614 }
580615
@@ -600,8 +635,8 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
600635 const stats = await fs . promises . stat ( filePath ) ;
601636 const fileMtime = stats . mtimeMs ;
602637
603- // If file was modified after checkbox was checked, reset it
604- if ( fileMtime > stateInfo . timestamp ) {
638+ // If file was modified at or after the checkbox was checked, reset it
639+ if ( fileMtime >= stateInfo . timestamp ) {
605640 return filePath ;
606641 }
607642 } catch ( error : unknown ) {
@@ -614,23 +649,30 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
614649
615650 const pathsToReset = await Promise . all ( statPromises ) ;
616651 const actualPathsToReset = pathsToReset . filter ( ( filePath ) : filePath is string => filePath !== null ) ;
617- actualPathsToReset . forEach ( filePath => this . checkboxStates . delete ( filePath ) ) ;
618652
619- // Fire tree refresh to update checkbox UI
620653 if ( actualPathsToReset . length > 0 ) {
621- this . _onDidChangeTreeData . fire ( ) ;
654+ // Clear folder checkbox state cache before deleting checkbox states
655+ this . folderCheckboxStateCache . clear ( ) ;
656+ actualPathsToReset . forEach ( filePath => this . checkboxStates . delete ( filePath ) ) ;
622657 }
658+
659+ // Track if checkboxes were reset to consolidate tree refresh later
660+ checkboxesWereReset = actualPathsToReset . length > 0 ;
623661 }
624662
625- // Clear checkbox state for files that no longer exist in the diff
663+ // Clear checkbox state for files and folders that no longer exist in the diff
626664 const pathsToDelete : string [ ] = [ ] ;
627- for ( const [ filePath ] of this . checkboxStates ) {
628- if ( ! newFilePaths . has ( filePath ) ) {
629- pathsToDelete . push ( filePath ) ;
665+ for ( const [ itemPath ] of this . checkboxStates ) {
666+ // Only delete if it's not in files AND not in folders
667+ if ( ! newFilePaths . has ( itemPath ) && ! newFolderPaths . has ( itemPath ) ) {
668+ pathsToDelete . push ( itemPath ) ;
630669 }
631670 }
632- for ( const filePath of pathsToDelete ) {
633- this . checkboxStates . delete ( filePath ) ;
671+ if ( pathsToDelete . length > 0 ) {
672+ this . folderCheckboxStateCache . clear ( ) ;
673+ for ( const itemPath of pathsToDelete ) {
674+ this . checkboxStates . delete ( itemPath ) ;
675+ }
634676 }
635677
636678 let treeHasChanged = false ;
@@ -679,7 +721,7 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
679721 this . filesInsideTreeRoot = filesInsideTreeRoot ;
680722 this . filesOutsideTreeRoot = filesOutsideTreeRoot ;
681723
682- if ( fireChangeEvents && treeHasChanged ) {
724+ if ( fireChangeEvents && ( treeHasChanged || checkboxesWereReset ) ) {
683725 this . log ( 'Refreshing tree' )
684726 this . _onDidChangeTreeData . fire ( ) ;
685727 }
0 commit comments