@@ -47,7 +47,8 @@ pub enum Actions {
4747 Filter ( String ) ,
4848 UpdateIgnoreListKeys ( bool ) ,
4949 EditCurrentService ,
50- ServiceAction ( ServiceAction )
50+ ServiceAction ( ServiceAction ) ,
51+ ShowHelp ,
5152}
5253
5354pub enum AppEvent {
@@ -84,6 +85,7 @@ pub struct App {
8485 event_rx : Receiver < AppEvent > ,
8586 event_tx : Sender < AppEvent > ,
8687 selected_tab_index : usize ,
88+ show_help : bool ,
8789}
8890
8991impl App {
@@ -108,6 +110,7 @@ impl App {
108110 event_rx,
109111 event_tx,
110112 selected_tab_index : 0 ,
113+ show_help : false ,
111114 }
112115 }
113116
@@ -161,18 +164,36 @@ fn spawn_key_event_listener(&self) {
161164 match self . event_rx . recv ( ) ? {
162165 AppEvent :: Key ( key) => match self . status {
163166 Status :: Log => {
164- self . on_key_event ( key) ;
165- log. on_key_event ( key)
167+ if self . show_help {
168+ self . show_help = false ;
169+ } else {
170+ self . on_key_event ( key) ;
171+ log. on_key_event ( key) ;
172+ }
166173 }
167174 Status :: List => {
168- self . on_key_event ( key) ;
169- self . on_key_horizontal_event ( key, filter. input_mode == InputMode :: Editing ) ;
170- table_service. on_key_event ( key) ;
171- filter. on_key_event ( key) ;
175+ if self . show_help {
176+ self . show_help = false ;
177+ // Ensure the table is active and can receive key events
178+ table_service. set_ignore_key_events ( false ) ;
179+ // If no item is selected and list is not empty, select first item
180+ if table_service. table_state . selected ( ) . is_none ( ) && !table_service. is_filtered_list_empty ( ) {
181+ table_service. set_selected_index ( 0 ) ;
182+ }
183+ } else {
184+ self . on_key_event ( key) ;
185+ self . on_key_horizontal_event ( key, filter. input_mode == InputMode :: Editing ) ;
186+ table_service. on_key_event ( key) ;
187+ filter. on_key_event ( key) ;
188+ }
172189 }
173190 Status :: Details => {
174- self . on_key_event ( key) ;
175- details. on_key_event ( key) ;
191+ if self . show_help {
192+ self . show_help = false ;
193+ } else {
194+ self . on_key_event ( key) ;
195+ details. on_key_event ( key) ;
196+ }
176197 }
177198 } ,
178199 AppEvent :: Action ( Actions :: ServiceAction ( action) ) => {
@@ -229,6 +250,9 @@ fn spawn_key_event_listener(&self) {
229250 AppEvent :: Error ( error_msg) => {
230251 self . error_popup ( & mut terminal, error_msg) ?;
231252 }
253+ AppEvent :: Action ( Actions :: ShowHelp ) => {
254+ self . show_help = !self . show_help ;
255+ }
232256 }
233257 }
234258
@@ -296,6 +320,67 @@ fn spawn_key_event_listener(&self) {
296320 Ok ( ( ) )
297321 }
298322
323+ fn draw_help_popup ( & self , frame : & mut Frame , area : Rect ) {
324+ let popup_width = std:: cmp:: min ( 80 , area. width . saturating_sub ( 4 ) ) ;
325+ let popup_height = std:: cmp:: min ( 25 , area. height . saturating_sub ( 4 ) ) ;
326+
327+ let popup_x = ( area. width . saturating_sub ( popup_width) ) / 2 ;
328+ let popup_y = ( area. height . saturating_sub ( popup_height) ) / 2 ;
329+
330+ let popup_area = Rect :: new (
331+ area. x + popup_x,
332+ area. y + popup_y,
333+ popup_width,
334+ popup_height,
335+ ) ;
336+
337+ frame. render_widget ( Clear , popup_area) ;
338+
339+ let text = vec ! [
340+ Line :: from( vec![ Span :: styled(
341+ "SYSTEMD MANAGER TUI - HELP" ,
342+ Style :: default ( ) . fg( Color :: Cyan ) . add_modifier( Modifier :: BOLD ) ,
343+ ) ] ) ,
344+ Line :: from( "" ) ,
345+ Line :: from( vec![ Span :: styled( "Navigation:" , Style :: default ( ) . fg( Color :: Yellow ) . add_modifier( Modifier :: BOLD ) ) ] ) ,
346+ Line :: from( " ↑/k - Move up ↓/j - Move down" ) ,
347+ Line :: from( " ←/h - Previous tab →/l - Next tab" ) ,
348+ Line :: from( " PageUp/PageDown - Jump 10 items" ) ,
349+ Line :: from( "" ) ,
350+ Line :: from( vec![ Span :: styled( "Service Control:" , Style :: default ( ) . fg( Color :: Yellow ) . add_modifier( Modifier :: BOLD ) ) ] ) ,
351+ Line :: from( " s - Start service x - Stop service" ) ,
352+ Line :: from( " r - Restart service" ) ,
353+ Line :: from( " e - Enable service d - Disable service" ) ,
354+ Line :: from( " m - Mask/Unmask service" ) ,
355+ Line :: from( "" ) ,
356+ Line :: from( vec![ Span :: styled( "View & Filter:" , Style :: default ( ) . fg( Color :: Yellow ) . add_modifier( Modifier :: BOLD ) ) ] ) ,
357+ Line :: from( " f - Toggle all/services filter" ) ,
358+ Line :: from( " a - Cycle filter (all→active→inactive→failed)" ) ,
359+ Line :: from( " u - Refresh service list" ) ,
360+ Line :: from( "" ) ,
361+ Line :: from( vec![ Span :: styled( "Information:" , Style :: default ( ) . fg( Color :: Yellow ) . add_modifier( Modifier :: BOLD ) ) ] ) ,
362+ Line :: from( " v - View service logs" ) ,
363+ Line :: from( " c - View unit file details" ) ,
364+ Line :: from( "" ) ,
365+ Line :: from( vec![ Span :: styled(
366+ "Press ? or any key to close" ,
367+ Style :: default ( ) . fg( Color :: Gray ) ,
368+ ) ] ) ,
369+ ] ;
370+
371+ let help_block = Paragraph :: new ( text)
372+ . block (
373+ Block :: default ( )
374+ . borders ( Borders :: ALL )
375+ . border_style ( Style :: default ( ) . fg ( Color :: Cyan ) )
376+ . title ( "Help" ) ,
377+ )
378+ . alignment ( Alignment :: Left )
379+ . wrap ( ratatui:: widgets:: Wrap { trim : true } ) ;
380+
381+ frame. render_widget ( help_block, popup_area) ;
382+ }
383+
299384 fn error_popup ( & self , terminal : & mut DefaultTerminal , error_msg : String ) -> Result < ( ) > {
300385 let user_friendly_message = get_user_friendly_error ( & error_msg) ;
301386
@@ -403,14 +488,45 @@ fn spawn_key_event_listener(&self) {
403488 ] )
404489 . areas ( area) ;
405490
406- let tabs = Tabs :: new ( vec ! [ "System units" , "Session units" ] )
491+ let filter_state = table. get_active_filter_state ( ) ;
492+
493+ let system_tab = if self . selected_tab_index == 0 {
494+ Line :: from ( vec ! [
495+ Span :: raw( "System units" ) ,
496+ Span :: styled(
497+ format!( " (Filter: {})" , filter_state. as_str( ) ) ,
498+ Style :: default ( ) . fg( Color :: Gray )
499+ )
500+ ] )
501+ } else {
502+ Line :: from ( "System units" )
503+ } ;
504+
505+ let session_tab = if self . selected_tab_index == 1 {
506+ Line :: from ( vec ! [
507+ Span :: raw( "Session units" ) ,
508+ Span :: styled(
509+ format!( " (Filter: {})" , filter_state. as_str( ) ) ,
510+ Style :: default ( ) . fg( Color :: Gray )
511+ )
512+ ] )
513+ } else {
514+ Line :: from ( "Session units" )
515+ } ;
516+
517+ let tabs = Tabs :: new ( vec ! [ system_tab, session_tab] )
407518 . select ( self . selected_tab_index )
408519 . highlight_style ( Style :: default ( ) . fg ( Color :: Yellow ) ) ;
409520
410521 frame. render_widget ( tabs, tabs_box) ;
411522 filter. draw ( frame, filter_box) ;
412523 table. render ( frame, list_box) ;
413524 self . draw_shortcuts ( frame, help_area_box, table. shortcuts ( ) ) ;
525+
526+ // Show help popup if needed
527+ if self . show_help {
528+ self . draw_help_popup ( frame, area) ;
529+ }
414530 } ) ?;
415531
416532 Ok ( ( ) )
0 commit comments