@@ -526,44 +526,45 @@ impl App {
526526 match key. code {
527527 KeyCode :: Char ( 'j' ) | KeyCode :: Down => {
528528 ts. key_down ( ) ;
529- None
530529 }
531530 KeyCode :: Char ( 'k' ) | KeyCode :: Up => {
532531 ts. key_up ( ) ;
533- None
534532 }
535533 KeyCode :: Left => {
536534 ts. key_left ( ) ;
537- None
538535 }
539536 KeyCode :: Right => {
540537 ts. key_right ( ) ;
541- None
542538 }
543539 KeyCode :: Enter => {
544- let selected = ts. selected ( ) . to_vec ( ) ;
545- drop ( ts) ; // release borrow before mutating self
546- if let Some ( last) = selected. last ( ) {
547- match last {
548- TreeNodeId :: File ( path) => {
549- self . select_tree_file ( path) ;
550- }
551- TreeNodeId :: Group ( gi) => {
552- self . select_tree_group ( * gi) ;
553- }
554- }
555- }
556- None
540+ ts. toggle_selected ( ) ;
557541 }
558542 KeyCode :: Char ( 'g' ) => {
559543 ts. select_first ( ) ;
560- None
561544 }
562545 KeyCode :: Char ( 'G' ) => {
563546 ts. select_last ( ) ;
564- None
565547 }
566- _ => None ,
548+ _ => return None ,
549+ }
550+
551+ // After any navigation, sync the diff view to the selected tree node
552+ let selected = ts. selected ( ) . to_vec ( ) ;
553+ drop ( ts) ; // release borrow before mutating self
554+ self . apply_tree_selection ( & selected) ;
555+ None
556+ }
557+
558+ /// Update the diff view filter based on the currently selected tree node.
559+ fn apply_tree_selection ( & mut self , selected : & [ TreeNodeId ] ) {
560+ match selected. last ( ) {
561+ Some ( TreeNodeId :: File ( group_idx, path) ) => {
562+ self . select_tree_file ( path, * group_idx) ;
563+ }
564+ Some ( TreeNodeId :: Group ( gi) ) => {
565+ self . select_tree_group ( * gi) ;
566+ }
567+ None => { }
567568 }
568569 }
569570
@@ -625,11 +626,11 @@ impl App {
625626 }
626627 }
627628
628- /// Filter the diff view to the group containing the selected file, and scroll to it .
629- /// If the group is already active, just scroll to the file without toggling off .
630- fn select_tree_file ( & mut self , path : & str ) {
631- let filter = self . hunk_filter_for_file ( path) ;
632- // Always apply the group filter (don't toggle — that's what group headers are for)
629+ /// Filter the diff view to show only the hunks for the selected file within its group .
630+ /// `group_idx` identifies which group the file was selected from (None = flat/ungrouped) .
631+ fn select_tree_file ( & mut self , path : & str , group_idx : Option < usize > ) {
632+ let filter = self . hunk_filter_for_file ( path, group_idx ) ;
633+ // Always apply the filter (don't toggle — that's what group headers are for)
633634 self . tree_filter = Some ( filter) ;
634635 // Rebuild visible items and scroll to the selected file's header
635636 let items = self . visible_items ( ) ;
@@ -652,40 +653,74 @@ impl App {
652653 fn select_tree_group ( & mut self , group_idx : usize ) {
653654 let filter = self . hunk_filter_for_group ( group_idx) ;
654655 if filter. is_empty ( ) {
655- self . tree_state . borrow_mut ( ) . toggle_selected ( ) ;
656656 return ;
657657 }
658- // Toggle: if already filtering to this group, clear it
659- if self . tree_filter . as_ref ( ) == Some ( & filter) {
660- self . tree_filter = None ;
661- } else {
662- self . tree_filter = Some ( filter) ;
663- }
658+ self . tree_filter = Some ( filter) ;
664659 self . ui_state . selected_index = 0 ;
665660 self . ui_state . scroll_offset = 0 ;
666661 }
667662
668- /// Build a HunkFilter for the group containing `path` .
669- /// Falls back to showing just that file (all hunks) if no groups exist .
670- fn hunk_filter_for_file ( & self , path : & str ) -> HunkFilter {
663+ /// Build a HunkFilter for a single file's hunks within a specific group .
664+ /// Only shows the hunks relevant to that group, not the entire group's files .
665+ fn hunk_filter_for_file ( & self , path : & str , group_idx : Option < usize > ) -> HunkFilter {
671666 if let Some ( groups) = & self . semantic_groups {
672- for ( gi, group) in groups. iter ( ) . enumerate ( ) {
673- let has_file = group. changes ( ) . iter ( ) . any ( |c| {
674- c. file == path || path. ends_with ( c. file . as_str ( ) ) || c. file . ends_with ( path)
675- } ) ;
676- if has_file {
677- return self . hunk_filter_for_group ( gi) ;
667+ if let Some ( gi) = group_idx {
668+ if gi >= groups. len ( ) {
669+ // "Other" group — extract only this file's ungrouped hunks
670+ return self . hunk_filter_for_file_in_other ( path) ;
671+ }
672+ if let Some ( group) = groups. get ( gi) {
673+ if let Some ( filter) = self . hunk_filter_for_file_in_group ( path, group) {
674+ return filter;
675+ }
678676 }
679677 }
680- // File is in the "Other" group — collect ungrouped file/hunks
681- return self . hunk_filter_for_other ( ) ;
678+ // Fallback (no group_idx or file not found in specified group):
679+ // search all groups for the first match
680+ for group in groups. iter ( ) {
681+ if let Some ( filter) = self . hunk_filter_for_file_in_group ( path, group) {
682+ return filter;
683+ }
684+ }
685+ return self . hunk_filter_for_file_in_other ( path) ;
682686 }
683- // No semantic groups — filter to just this file (all hunks)
687+ // No semantic groups — show all hunks for this file
684688 let mut filter = HunkFilter :: new ( ) ;
685689 filter. insert ( path. to_string ( ) , HashSet :: new ( ) ) ;
686690 filter
687691 }
688692
693+ /// Build a single-file HunkFilter from a specific group's changes.
694+ fn hunk_filter_for_file_in_group (
695+ & self ,
696+ path : & str ,
697+ group : & crate :: grouper:: SemanticGroup ,
698+ ) -> Option < HunkFilter > {
699+ for change in & group. changes ( ) {
700+ if let Some ( diff_path) = self . resolve_diff_path ( & change. file ) {
701+ if diff_path == path {
702+ let mut filter = HunkFilter :: new ( ) ;
703+ let hunk_set: HashSet < usize > = change. hunks . iter ( ) . copied ( ) . collect ( ) ;
704+ filter. insert ( diff_path, hunk_set) ;
705+ return Some ( filter) ;
706+ }
707+ }
708+ }
709+ None
710+ }
711+
712+ /// Build a single-file HunkFilter from the "Other" (ungrouped) hunks.
713+ fn hunk_filter_for_file_in_other ( & self , path : & str ) -> HunkFilter {
714+ let other = self . hunk_filter_for_other ( ) ;
715+ let mut filter = HunkFilter :: new ( ) ;
716+ if let Some ( hunk_set) = other. get ( path) {
717+ filter. insert ( path. to_string ( ) , hunk_set. clone ( ) ) ;
718+ } else {
719+ filter. insert ( path. to_string ( ) , HashSet :: new ( ) ) ;
720+ }
721+ filter
722+ }
723+
689724 /// Build a HunkFilter for group at `group_idx`.
690725 fn hunk_filter_for_group ( & self , group_idx : usize ) -> HunkFilter {
691726 if let Some ( groups) = & self . semantic_groups {
0 commit comments