@@ -134,6 +134,7 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
134134 private repository : Repository | undefined ;
135135 private baseRef : string ;
136136 private viewAsList = false ;
137+ private searchFilter : string | undefined ;
137138
138139 // Static state of repository
139140 private workspaceFolder : string ;
@@ -240,16 +241,28 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
240241 this . updateTreeRootFolder ( ) ;
241242 this . log ( 'Using repository: ' + this . repoRoot ) ;
242243
243- const repoName = path . basename ( repoRoot ) ;
244- this . treeView . title = repoName ;
244+ this . updateTreeTitle ( ) ;
245+ }
246+
247+ private updateTreeTitle ( ) {
248+ if ( ! this . repository ) {
249+ this . treeView . title = 'none' ;
250+ return ;
251+ }
252+ const repoName = path . basename ( this . repoRoot ) ;
253+ if ( this . searchFilter ) {
254+ this . treeView . title = `${ repoName } (filtered)` ;
255+ } else {
256+ this . treeView . title = repoName ;
257+ }
245258 }
246259
247260 async unsetRepository ( ) {
248261 this . repository = undefined ;
249262 this . _onDidChangeTreeData . fire ( ) ;
250263 this . log ( 'No repository selected' ) ;
251264
252- this . treeView . title = 'none' ;
265+ this . updateTreeTitle ( ) ;
253266 }
254267
255268 async changeRepository ( repositoryRoot : string ) {
@@ -264,6 +277,8 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
264277 return ;
265278 }
266279 this . checkboxStates . clear ( ) ;
280+ this . searchFilter = undefined ;
281+ this . updateFilterContext ( ) ;
267282 this . _onDidChangeTreeData . fire ( ) ;
268283 }
269284
@@ -831,6 +846,36 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
831846 }
832847 }
833848
849+ private matchesFilter ( filePath : string , relPathBase : string ) : boolean {
850+ if ( ! this . searchFilter ) {
851+ return true ;
852+ }
853+ const fileName = path . basename ( filePath ) ;
854+ const relativePath = path . relative ( relPathBase , filePath ) ;
855+ const searchLower = this . searchFilter . toLowerCase ( ) ;
856+ return fileName . toLowerCase ( ) . includes ( searchLower ) ||
857+ relativePath . toLowerCase ( ) . includes ( searchLower ) ;
858+ }
859+
860+ private folderHasMatchingFiles ( folder : string , useFilesOutsideTreeRoot : boolean ) : boolean {
861+ if ( ! this . searchFilter ) {
862+ return true ;
863+ }
864+ const files = useFilesOutsideTreeRoot ? this . filesOutsideTreeRoot : this . filesInsideTreeRoot ;
865+ const relPathBase = useFilesOutsideTreeRoot ? this . repoRoot : this . treeRoot ;
866+
867+ for ( const [ folderPath , fileEntries ] of files . entries ( ) ) {
868+ if ( folderPath === folder || folderPath . startsWith ( folder + path . sep ) ) {
869+ for ( const file of fileEntries ) {
870+ if ( this . matchesFilter ( file . dstAbsPath , relPathBase ) ) {
871+ return true ;
872+ }
873+ }
874+ }
875+ }
876+ return false ;
877+ }
878+
834879 private getFileSystemEntries ( folder : string , useFilesOutsideTreeRoot : boolean ) : FileSystemElement [ ] {
835880 const entries : FileSystemElement [ ] = [ ] ;
836881 const files = useFilesOutsideTreeRoot ? this . filesOutsideTreeRoot : this . filesInsideTreeRoot ;
@@ -849,14 +894,19 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
849894 for ( const folder2 of folders ) {
850895 const fileEntries = files . get ( folder2 ) ! ;
851896 for ( const file of fileEntries ) {
852- const dstRelPath = path . relative ( relPathBase , file . dstAbsPath ) ;
853- entries . push ( new FileElement ( file . srcAbsPath , file . dstAbsPath , dstRelPath , file . status , file . isSubmodule ) ) ;
897+ if ( this . matchesFilter ( file . dstAbsPath , relPathBase ) ) {
898+ const dstRelPath = path . relative ( relPathBase , file . dstAbsPath ) ;
899+ entries . push ( new FileElement ( file . srcAbsPath , file . dstAbsPath , dstRelPath , file . status , file . isSubmodule ) ) ;
900+ }
854901 }
855902 }
856903 } else if ( this . compactFolders ) {
857904 // add direct subfolders and apply compaction
858905 for ( const folder2 of files . keys ( ) ) {
859906 if ( path . dirname ( folder2 ) === folder ) {
907+ if ( ! this . folderHasMatchingFiles ( folder2 , useFilesOutsideTreeRoot ) ) {
908+ continue ;
909+ }
860910 let compactedPath = folder2 ;
861911 // not very efficient, needs a better data structure
862912 outer: while ( true ) {
@@ -891,9 +941,11 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
891941 // add direct subfolders
892942 for ( const folder2 of files . keys ( ) ) {
893943 if ( path . dirname ( folder2 ) === folder ) {
894- const label = path . basename ( folder2 ) ;
895- entries . push ( new FolderElement (
896- label , folder2 , useFilesOutsideTreeRoot ) ) ;
944+ if ( this . folderHasMatchingFiles ( folder2 , useFilesOutsideTreeRoot ) ) {
945+ const label = path . basename ( folder2 ) ;
946+ entries . push ( new FolderElement (
947+ label , folder2 , useFilesOutsideTreeRoot ) ) ;
948+ }
897949 }
898950 }
899951 entries . sort ( ( a , b ) => path . basename ( a . dstAbsPath ) . localeCompare ( path . basename ( b . dstAbsPath ) ) ) ;
@@ -905,8 +957,10 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
905957 // there are no files within treeRoot, therefore, this is guarded
906958 if ( fileEntries ) {
907959 for ( const file of fileEntries ) {
908- const dstRelPath = path . relative ( relPathBase , file . dstAbsPath ) ;
909- entries . push ( new FileElement ( file . srcAbsPath , file . dstAbsPath , dstRelPath , file . status , file . isSubmodule ) ) ;
960+ if ( this . matchesFilter ( file . dstAbsPath , relPathBase ) ) {
961+ const dstRelPath = path . relative ( relPathBase , file . dstAbsPath ) ;
962+ entries . push ( new FileElement ( file . srcAbsPath , file . dstAbsPath , dstRelPath , file . status , file . isSubmodule ) ) ;
963+ }
910964 }
911965 }
912966
@@ -1514,6 +1568,39 @@ export class GitTreeCompareProvider implements TreeDataProvider<Element>, Dispos
15141568 } ) ;
15151569 }
15161570
1571+ async filterFiles ( ) {
1572+ const searchTerm = await window . showInputBox ( {
1573+ prompt : 'Enter text to filter files (leave empty to show all)' ,
1574+ placeHolder : 'Filter by filename or path...' ,
1575+ value : this . searchFilter || ''
1576+ } ) ;
1577+
1578+ if ( searchTerm === undefined ) {
1579+ return ;
1580+ }
1581+
1582+ this . searchFilter = searchTerm . trim ( ) || undefined ;
1583+ this . updateTreeTitle ( ) ;
1584+ this . updateFilterContext ( ) ;
1585+ this . log ( this . searchFilter ? `Filtering files by: ${ this . searchFilter } ` : 'Cleared file filter' ) ;
1586+ this . _onDidChangeTreeData . fire ( ) ;
1587+ }
1588+
1589+ clearFilter ( ) {
1590+ if ( ! this . searchFilter ) {
1591+ return ;
1592+ }
1593+ this . searchFilter = undefined ;
1594+ this . updateTreeTitle ( ) ;
1595+ this . updateFilterContext ( ) ;
1596+ this . log ( 'Cleared file filter' ) ;
1597+ this . _onDidChangeTreeData . fire ( ) ;
1598+ }
1599+
1600+ private updateFilterContext ( ) {
1601+ commands . executeCommand ( 'setContext' , NAMESPACE + '.isFiltered' , ! ! this . searchFilter ) ;
1602+ }
1603+
15171604 async copyPath ( fileEntry : FileElement ) {
15181605 const diffStatus = this . getDiffStatus ( fileEntry ) ;
15191606 if ( ! diffStatus ) {
0 commit comments