@@ -99,12 +99,34 @@ public bool UseAmend
9999 }
100100 }
101101
102+ public string UnstagedFilter
103+ {
104+ get => _unstagedFilter ;
105+ set
106+ {
107+ if ( SetProperty ( ref _unstagedFilter , value ) )
108+ {
109+ if ( _isLoadingData )
110+ return ;
111+
112+ VisibleUnstaged = GetVisibleUnstagedChanges ( ) ;
113+ SelectedUnstaged = [ ] ;
114+ }
115+ }
116+ }
117+
102118 public List < Models . Change > Unstaged
103119 {
104120 get => _unstaged ;
105121 private set => SetProperty ( ref _unstaged , value ) ;
106122 }
107123
124+ public List < Models . Change > VisibleUnstaged
125+ {
126+ get => _visibleUnstaged ;
127+ private set => SetProperty ( ref _visibleUnstaged , value ) ;
128+ }
129+
108130 public List < Models . Change > Staged
109131 {
110132 get => _staged ;
@@ -191,8 +213,9 @@ public void Cleanup()
191213 _selectedStaged . Clear ( ) ;
192214 OnPropertyChanged ( nameof ( SelectedStaged ) ) ;
193215
216+ _visibleUnstaged . Clear ( ) ;
194217 _unstaged . Clear ( ) ;
195- OnPropertyChanged ( nameof ( Unstaged ) ) ;
218+ OnPropertyChanged ( nameof ( VisibleUnstaged ) ) ;
196219
197220 _staged . Clear ( ) ;
198221 OnPropertyChanged ( nameof ( Staged ) ) ;
@@ -249,20 +272,26 @@ public void SetData(List<Models.Change> changes)
249272 }
250273
251274 var unstaged = new List < Models . Change > ( ) ;
252- var selectedUnstaged = new List < Models . Change > ( ) ;
253275 var hasConflict = false ;
254276 foreach ( var c in changes )
255277 {
256278 if ( c . WorkTree != Models . ChangeState . None )
257279 {
258280 unstaged . Add ( c ) ;
259281 hasConflict |= c . IsConflit ;
260-
261- if ( lastSelectedUnstaged . Contains ( c . Path ) )
262- selectedUnstaged . Add ( c ) ;
263282 }
264283 }
265284
285+ _unstaged = unstaged ;
286+
287+ var visibleUnstaged = GetVisibleUnstagedChanges ( ) ;
288+ var selectedUnstaged = new List < Models . Change > ( ) ;
289+ foreach ( var c in visibleUnstaged )
290+ {
291+ if ( lastSelectedUnstaged . Contains ( c . Path ) )
292+ selectedUnstaged . Add ( c ) ;
293+ }
294+
266295 var staged = GetStagedChanges ( ) ;
267296 var selectedStaged = new List < Models . Change > ( ) ;
268297 foreach ( var c in staged )
@@ -275,7 +304,7 @@ public void SetData(List<Models.Change> changes)
275304 {
276305 _isLoadingData = true ;
277306 HasUnsolvedConflicts = hasConflict ;
278- Unstaged = unstaged ;
307+ VisibleUnstaged = visibleUnstaged ;
279308 Staged = staged ;
280309 SelectedUnstaged = selectedUnstaged ;
281310 SelectedStaged = selectedStaged ;
@@ -336,46 +365,7 @@ public void StageSelected(Models.Change next)
336365
337366 public void StageAll ( )
338367 {
339- StageChanges ( _unstaged , null ) ;
340- }
341-
342- public async void StageChanges ( List < Models . Change > changes , Models . Change next )
343- {
344- if ( _unstaged . Count == 0 || changes . Count == 0 )
345- return ;
346-
347- // Use `_selectedUnstaged` instead of `SelectedUnstaged` to avoid UI refresh.
348- _selectedUnstaged = next != null ? [ next ] : [ ] ;
349-
350- IsStaging = true ;
351- _repo . SetWatcherEnabled ( false ) ;
352- if ( changes . Count == _unstaged . Count )
353- {
354- await Task . Run ( ( ) => new Commands . Add ( _repo . FullPath , _repo . IncludeUntracked ) . Exec ( ) ) ;
355- }
356- else if ( Native . OS . GitVersion >= Models . GitVersions . ADD_WITH_PATHSPECFILE )
357- {
358- var paths = new List < string > ( ) ;
359- foreach ( var c in changes )
360- paths . Add ( c . Path ) ;
361-
362- var tmpFile = Path . GetTempFileName ( ) ;
363- File . WriteAllLines ( tmpFile , paths ) ;
364- await Task . Run ( ( ) => new Commands . Add ( _repo . FullPath , tmpFile ) . Exec ( ) ) ;
365- File . Delete ( tmpFile ) ;
366- }
367- else
368- {
369- for ( int i = 0 ; i < changes . Count ; i += 10 )
370- {
371- var count = Math . Min ( 10 , changes . Count - i ) ;
372- var step = changes . GetRange ( i , count ) ;
373- await Task . Run ( ( ) => new Commands . Add ( _repo . FullPath , step ) . Exec ( ) ) ;
374- }
375- }
376- _repo . MarkWorkingCopyDirtyManually ( ) ;
377- _repo . SetWatcherEnabled ( true ) ;
378- IsStaging = false ;
368+ StageChanges ( _visibleUnstaged , null ) ;
379369 }
380370
381371 public void UnstageSelected ( Models . Change next )
@@ -388,44 +378,17 @@ public void UnstageAll()
388378 UnstageChanges ( _staged , null ) ;
389379 }
390380
391- public async void UnstageChanges ( List < Models . Change > changes , Models . Change next )
392- {
393- if ( _staged . Count == 0 || changes . Count == 0 )
394- return ;
395-
396- // Use `_selectedStaged` instead of `SelectedStaged` to avoid UI refresh.
397- _selectedStaged = next != null ? [ next ] : [ ] ;
398-
399- IsUnstaging = true ;
400- _repo . SetWatcherEnabled ( false ) ;
401- if ( _useAmend )
402- {
403- await Task . Run ( ( ) => new Commands . UnstageChangesForAmend ( _repo . FullPath , changes ) . Exec ( ) ) ;
404- }
405- else if ( changes . Count == _staged . Count )
406- {
407- await Task . Run ( ( ) => new Commands . Reset ( _repo . FullPath ) . Exec ( ) ) ;
408- }
409- else
410- {
411- for ( int i = 0 ; i < changes . Count ; i += 10 )
412- {
413- var count = Math . Min ( 10 , changes . Count - i ) ;
414- var step = changes . GetRange ( i , count ) ;
415- await Task . Run ( ( ) => new Commands . Reset ( _repo . FullPath , step ) . Exec ( ) ) ;
416- }
417- }
418- _repo . MarkWorkingCopyDirtyManually ( ) ;
419- _repo . SetWatcherEnabled ( true ) ;
420- IsUnstaging = false ;
421- }
422-
423381 public void Discard ( List < Models . Change > changes )
424382 {
425383 if ( _repo . CanCreatePopup ( ) )
426384 _repo . ShowPopup ( new Discard ( _repo , changes ) ) ;
427385 }
428386
387+ public void ClearUnstagedFilter ( )
388+ {
389+ UnstagedFilter = string . Empty ;
390+ }
391+
429392 public async void UseTheirs ( List < Models . Change > changes )
430393 {
431394 var files = new List < string > ( ) ;
@@ -1496,6 +1459,22 @@ public ContextMenu CreateContextForOpenAI()
14961459 }
14971460 }
14981461
1462+ private List < Models . Change > GetVisibleUnstagedChanges ( )
1463+ {
1464+ if ( string . IsNullOrEmpty ( _unstagedFilter ) )
1465+ return _unstaged ;
1466+
1467+ var visible = new List < Models . Change > ( ) ;
1468+
1469+ foreach ( var c in _unstaged )
1470+ {
1471+ if ( c . Path . Contains ( _unstagedFilter , StringComparison . OrdinalIgnoreCase ) )
1472+ visible . Add ( c ) ;
1473+ }
1474+
1475+ return visible ;
1476+ }
1477+
14991478 private List < Models . Change > GetStagedChanges ( )
15001479 {
15011480 if ( _useAmend )
@@ -1511,6 +1490,77 @@ public ContextMenu CreateContextForOpenAI()
15111490 return rs ;
15121491 }
15131492
1493+ private async void StageChanges ( List < Models . Change > changes , Models . Change next )
1494+ {
1495+ if ( changes . Count == 0 )
1496+ return ;
1497+
1498+ // Use `_selectedUnstaged` instead of `SelectedUnstaged` to avoid UI refresh.
1499+ _selectedUnstaged = next != null ? [ next ] : [ ] ;
1500+
1501+ IsStaging = true ;
1502+ _repo . SetWatcherEnabled ( false ) ;
1503+ if ( changes . Count == _unstaged . Count )
1504+ {
1505+ await Task . Run ( ( ) => new Commands . Add ( _repo . FullPath , _repo . IncludeUntracked ) . Exec ( ) ) ;
1506+ }
1507+ else if ( Native . OS . GitVersion >= Models . GitVersions . ADD_WITH_PATHSPECFILE )
1508+ {
1509+ var paths = new List < string > ( ) ;
1510+ foreach ( var c in changes )
1511+ paths . Add ( c . Path ) ;
1512+
1513+ var tmpFile = Path . GetTempFileName ( ) ;
1514+ File . WriteAllLines ( tmpFile , paths ) ;
1515+ await Task . Run ( ( ) => new Commands . Add ( _repo . FullPath , tmpFile ) . Exec ( ) ) ;
1516+ File . Delete ( tmpFile ) ;
1517+ }
1518+ else
1519+ {
1520+ for ( int i = 0 ; i < changes . Count ; i += 10 )
1521+ {
1522+ var count = Math . Min ( 10 , changes . Count - i ) ;
1523+ var step = changes . GetRange ( i , count ) ;
1524+ await Task . Run ( ( ) => new Commands . Add ( _repo . FullPath , step ) . Exec ( ) ) ;
1525+ }
1526+ }
1527+ _repo . MarkWorkingCopyDirtyManually ( ) ;
1528+ _repo . SetWatcherEnabled ( true ) ;
1529+ IsStaging = false ;
1530+ }
1531+
1532+ private async void UnstageChanges ( List < Models . Change > changes , Models . Change next )
1533+ {
1534+ if ( changes . Count == 0 )
1535+ return ;
1536+
1537+ // Use `_selectedStaged` instead of `SelectedStaged` to avoid UI refresh.
1538+ _selectedStaged = next != null ? [ next ] : [ ] ;
1539+
1540+ IsUnstaging = true ;
1541+ _repo . SetWatcherEnabled ( false ) ;
1542+ if ( _useAmend )
1543+ {
1544+ await Task . Run ( ( ) => new Commands . UnstageChangesForAmend ( _repo . FullPath , changes ) . Exec ( ) ) ;
1545+ }
1546+ else if ( changes . Count == _staged . Count )
1547+ {
1548+ await Task . Run ( ( ) => new Commands . Reset ( _repo . FullPath ) . Exec ( ) ) ;
1549+ }
1550+ else
1551+ {
1552+ for ( int i = 0 ; i < changes . Count ; i += 10 )
1553+ {
1554+ var count = Math . Min ( 10 , changes . Count - i ) ;
1555+ var step = changes . GetRange ( i , count ) ;
1556+ await Task . Run ( ( ) => new Commands . Reset ( _repo . FullPath , step ) . Exec ( ) ) ;
1557+ }
1558+ }
1559+ _repo . MarkWorkingCopyDirtyManually ( ) ;
1560+ _repo . SetWatcherEnabled ( true ) ;
1561+ IsUnstaging = false ;
1562+ }
1563+
15141564 private void SetDetail ( Models . Change change , bool isUnstaged )
15151565 {
15161566 if ( _isLoadingData )
@@ -1609,11 +1659,13 @@ private bool IsChanged(List<Models.Change> old, List<Models.Change> cur)
16091659 private bool _hasRemotes = false ;
16101660 private List < Models . Change > _cached = [ ] ;
16111661 private List < Models . Change > _unstaged = [ ] ;
1662+ private List < Models . Change > _visibleUnstaged = [ ] ;
16121663 private List < Models . Change > _staged = [ ] ;
16131664 private List < Models . Change > _selectedUnstaged = [ ] ;
16141665 private List < Models . Change > _selectedStaged = [ ] ;
16151666 private int _count = 0 ;
16161667 private object _detailContext = null ;
1668+ private string _unstagedFilter = string . Empty ;
16171669 private string _commitMessage = string . Empty ;
16181670
16191671 private bool _hasUnsolvedConflicts = false ;
0 commit comments