@@ -15,7 +15,10 @@ use ratatui::{
1515
1616use super :: {
1717 Action , EventSource , Focus , Selection , StatusMessage , WorktreeEntry ,
18- dialog:: { CreateDialog , CreateDialogFocus , Dialog , MergeDialog , MergeDialogFocus } ,
18+ dialog:: {
19+ CreateDialog , CreateDialogFocus , Dialog , MergeDialog , MergeDialogFocus , RemoveDialog ,
20+ RemoveDialogFocus ,
21+ } ,
1922 view:: { DetailData , DialogView , Snapshot } ,
2023} ;
2124
7477
7578 pub fn run < F , G > ( mut self , mut on_remove : F , mut on_create : G ) -> Result < Option < Selection > >
7679 where
77- F : FnMut ( & str ) -> Result < ( ) > ,
80+ F : FnMut ( & str , bool ) -> Result < ( ) > ,
7881 G : FnMut ( & str , Option < & str > ) -> Result < ( ) > ,
7982 {
8083 self . terminal
9699 on_create : & mut G ,
97100 ) -> Result < Option < Selection > >
98101 where
99- F : FnMut ( & str ) -> Result < ( ) > ,
102+ F : FnMut ( & str , bool ) -> Result < ( ) > ,
100103 G : FnMut ( & str , Option < & str > ) -> Result < ( ) > ,
101104 {
102105 let mut state = ListState :: default ( ) ;
@@ -123,15 +126,15 @@ where
123126 on_create : & mut G ,
124127 ) -> Result < LoopControl >
125128 where
126- F : FnMut ( & str ) -> Result < ( ) > ,
129+ F : FnMut ( & str , bool ) -> Result < ( ) > ,
127130 G : FnMut ( & str , Option < & str > ) -> Result < ( ) > ,
128131 {
129132 if let Some ( dialog) = self . dialog . clone ( ) {
130133 match dialog {
131- Dialog :: ConfirmRemove { index } => {
134+ Dialog :: Remove ( _ ) => {
132135 if let Event :: Key ( key) = event {
133136 if key. kind == KeyEventKind :: Press {
134- self . handle_confirm ( index , key. code , state, on_remove) ?;
137+ self . handle_remove_dialog_key ( key, state, on_remove) ?;
135138 }
136139 }
137140 return Ok ( LoopControl :: Continue ) ;
@@ -280,7 +283,7 @@ where
280283 }
281284 Action :: Remove => {
282285 if let Some ( index) = self . selected {
283- self . dialog = Some ( Dialog :: ConfirmRemove { index } ) ;
286+ self . dialog = Some ( Dialog :: Remove ( RemoveDialog :: new ( index) ) ) ;
284287 } else {
285288 self . status =
286289 Some ( StatusMessage :: info ( "No worktree selected to remove." ) ) ;
@@ -319,57 +322,123 @@ where
319322 Ok ( LoopControl :: Continue )
320323 }
321324
322- fn handle_confirm < F > (
325+ fn handle_remove_dialog_key < F > (
323326 & mut self ,
324- index : usize ,
325- code : KeyCode ,
327+ key : KeyEvent ,
326328 state : & mut ListState ,
327329 on_remove : & mut F ,
328330 ) -> Result < ( ) >
329331 where
330- F : FnMut ( & str ) -> Result < ( ) > ,
332+ F : FnMut ( & str , bool ) -> Result < ( ) > ,
331333 {
332- match code {
333- KeyCode :: Char ( 'y' ) | KeyCode :: Char ( 'Y' ) | KeyCode :: Enter => {
334- if let Some ( entry) = self . worktrees . get ( index) . cloned ( ) {
335- match on_remove ( & entry. name ) {
336- Ok ( ( ) ) => {
337- self . worktrees . remove ( index) ;
338- let removal_dir = entry
339- . path
340- . parent ( )
341- . map ( |parent| parent. display ( ) . to_string ( ) )
342- . unwrap_or_else ( || entry. path . display ( ) . to_string ( ) ) ;
343- let message = format ! (
344- "Removed worktree `{}` from `{}`." ,
345- entry. name, removal_dir
346- ) ;
347- self . selected = None ;
348- self . focus = Focus :: Worktrees ;
349- self . sync_selection ( state) ;
350- self . status = None ;
351- self . dialog = Some ( Dialog :: Info { message } ) ;
352- return Ok ( ( ) ) ;
353- }
354- Err ( err) => {
355- self . status = Some ( StatusMessage :: error ( format ! (
356- "Failed to remove `{}`: {err}" ,
357- entry. name
358- ) ) ) ;
359- self . dialog = None ;
360- return Ok ( ( ) ) ;
361- }
362- }
363- }
364- self . dialog = None ;
365- }
334+ let dialog_state = self . dialog . take ( ) ;
335+ let Some ( Dialog :: Remove ( mut dialog) ) = dialog_state else {
336+ self . dialog = dialog_state;
337+ return Ok ( ( ) ) ;
338+ } ;
339+
340+ let mut reinstate = true ;
341+
342+ match key. code {
366343 KeyCode :: Esc | KeyCode :: Char ( 'n' ) | KeyCode :: Char ( 'N' ) => {
344+ reinstate = false ;
367345 self . status = Some ( StatusMessage :: info ( "Removal cancelled." ) ) ;
368- self . dialog = None ;
369346 }
347+ KeyCode :: Char ( 'y' ) | KeyCode :: Char ( 'Y' ) => {
348+ reinstate = false ;
349+ self . perform_remove ( dialog. index , dialog. remove_local_branch ( ) , state, on_remove) ?;
350+ }
351+ KeyCode :: Tab => dialog. focus_next ( ) ,
352+ KeyCode :: BackTab => dialog. focus_prev ( ) ,
353+ KeyCode :: Up | KeyCode :: Char ( 'k' ) => match dialog. focus {
354+ RemoveDialogFocus :: Options => dialog. move_option ( -1 ) ,
355+ RemoveDialogFocus :: Buttons => dialog. focus = RemoveDialogFocus :: Options ,
356+ } ,
357+ KeyCode :: Down | KeyCode :: Char ( 'j' ) => match dialog. focus {
358+ RemoveDialogFocus :: Options => dialog. move_option ( 1 ) ,
359+ RemoveDialogFocus :: Buttons => { }
360+ } ,
361+ KeyCode :: Left => {
362+ if dialog. focus == RemoveDialogFocus :: Buttons {
363+ dialog. move_button ( -1 ) ;
364+ }
365+ }
366+ KeyCode :: Right => {
367+ if dialog. focus == RemoveDialogFocus :: Buttons {
368+ dialog. move_button ( 1 ) ;
369+ }
370+ }
371+ KeyCode :: Char ( ' ' ) => {
372+ if dialog. focus == RemoveDialogFocus :: Options {
373+ dialog. toggle_selected_option ( ) ;
374+ }
375+ }
376+ KeyCode :: Enter => match dialog. focus {
377+ RemoveDialogFocus :: Options => dialog. toggle_selected_option ( ) ,
378+ RemoveDialogFocus :: Buttons => {
379+ if dialog. buttons_selected == 0 {
380+ reinstate = false ;
381+ self . status = Some ( StatusMessage :: info ( "Removal cancelled." ) ) ;
382+ } else {
383+ reinstate = false ;
384+ self . perform_remove (
385+ dialog. index ,
386+ dialog. remove_local_branch ( ) ,
387+ state,
388+ on_remove,
389+ ) ?;
390+ }
391+ }
392+ } ,
370393 _ => { }
371394 }
372395
396+ if reinstate {
397+ self . dialog = Some ( Dialog :: Remove ( dialog) ) ;
398+ }
399+
400+ Ok ( ( ) )
401+ }
402+
403+ fn perform_remove < F > (
404+ & mut self ,
405+ index : usize ,
406+ remove_local_branch : bool ,
407+ state : & mut ListState ,
408+ on_remove : & mut F ,
409+ ) -> Result < ( ) >
410+ where
411+ F : FnMut ( & str , bool ) -> Result < ( ) > ,
412+ {
413+ if let Some ( entry) = self . worktrees . get ( index) . cloned ( ) {
414+ match on_remove ( & entry. name , remove_local_branch) {
415+ Ok ( ( ) ) => {
416+ self . worktrees . remove ( index) ;
417+ let removal_dir = entry
418+ . path
419+ . parent ( )
420+ . map ( |parent| parent. display ( ) . to_string ( ) )
421+ . unwrap_or_else ( || entry. path . display ( ) . to_string ( ) ) ;
422+ let message =
423+ format ! ( "Removed worktree `{}` from `{}`." , entry. name, removal_dir) ;
424+ self . selected = None ;
425+ self . focus = Focus :: Worktrees ;
426+ self . sync_selection ( state) ;
427+ self . status = None ;
428+ self . dialog = Some ( Dialog :: Info { message } ) ;
429+ }
430+ Err ( err) => {
431+ self . status = Some ( StatusMessage :: error ( format ! (
432+ "Failed to remove `{}`: {err}" ,
433+ entry. name
434+ ) ) ) ;
435+ self . dialog = None ;
436+ }
437+ }
438+ } else {
439+ self . dialog = None ;
440+ }
441+
373442 Ok ( ( ) )
374443 }
375444
@@ -769,11 +838,12 @@ where
769838 let detail = self . current_entry ( ) . map ( build_detail_data) ;
770839
771840 let dialog = match self . dialog . clone ( ) {
772- Some ( Dialog :: ConfirmRemove { index } ) => {
841+ Some ( Dialog :: Remove ( dialog ) ) => {
773842 self . worktrees
774- . get ( index)
775- . map ( |entry| DialogView :: ConfirmRemove {
843+ . get ( dialog . index )
844+ . map ( |entry| DialogView :: Remove {
776845 name : entry. name . clone ( ) ,
846+ dialog : dialog. into ( ) ,
777847 } )
778848 }
779849 Some ( Dialog :: Info { message } ) => Some ( DialogView :: Info { message } ) ,
0 commit comments