1+ use std:: fmt;
12use std:: io:: IsTerminal ;
23use std:: io:: Result ;
34use std:: io:: Stdout ;
45use std:: io:: stdout;
6+ use std:: panic;
57use std:: pin:: Pin ;
68use std:: sync:: Arc ;
79use std:: sync:: atomic:: AtomicBool ;
8- #[ cfg( unix) ]
9- use std:: sync:: atomic:: AtomicU8 ;
10- #[ cfg( unix) ]
11- use std:: sync:: atomic:: AtomicU16 ;
1210use std:: sync:: atomic:: Ordering ;
1311use std:: time:: Duration ;
1412use std:: time:: Instant ;
1513
1614use crossterm:: Command ;
1715use crossterm:: SynchronizedUpdate ;
18- #[ cfg( unix) ]
19- use crossterm:: cursor:: MoveTo ;
2016use crossterm:: event:: DisableBracketedPaste ;
2117use crossterm:: event:: DisableFocusChange ;
2218use crossterm:: event:: EnableBracketedPaste ;
2319use crossterm:: event:: EnableFocusChange ;
2420use crossterm:: event:: Event ;
25- #[ cfg( unix) ]
26- use crossterm:: event:: KeyCode ;
2721use crossterm:: event:: KeyEvent ;
2822use crossterm:: event:: KeyboardEnhancementFlags ;
2923use crossterm:: event:: PopKeyboardEnhancementFlags ;
@@ -38,20 +32,22 @@ use ratatui::crossterm::terminal::disable_raw_mode;
3832use ratatui:: crossterm:: terminal:: enable_raw_mode;
3933use ratatui:: layout:: Offset ;
4034use ratatui:: text:: Line ;
35+ use tokio:: select;
36+ use tokio_stream:: Stream ;
4137
4238use crate :: custom_terminal;
4339use crate :: custom_terminal:: Terminal as CustomTerminal ;
4440#[ cfg( unix) ]
45- use crate :: key_hint;
46- use tokio:: select;
47- use tokio_stream:: Stream ;
41+ use crate :: tui:: job_control:: SUSPEND_KEY ;
42+ #[ cfg( unix) ]
43+ use crate :: tui:: job_control:: SuspendContext ;
44+
45+ #[ cfg( unix) ]
46+ mod job_control;
4847
4948/// A type alias for the terminal type used in this application
5049pub type Terminal = CustomTerminal < CrosstermBackend < Stdout > > ;
5150
52- #[ cfg( unix) ]
53- const SUSPEND_KEY : key_hint:: KeyBinding = key_hint:: ctrl ( KeyCode :: Char ( 'z' ) ) ;
54-
5551pub fn set_modes ( ) -> Result < ( ) > {
5652 execute ! ( stdout( ) , EnableBracketedPaste ) ?;
5753
@@ -79,12 +75,12 @@ pub fn set_modes() -> Result<()> {
7975struct EnableAlternateScroll ;
8076
8177impl Command for EnableAlternateScroll {
82- fn write_ansi ( & self , f : & mut impl std :: fmt:: Write ) -> std :: fmt:: Result {
78+ fn write_ansi ( & self , f : & mut impl fmt:: Write ) -> fmt:: Result {
8379 write ! ( f, "\x1b [?1007h" )
8480 }
8581
8682 #[ cfg( windows) ]
87- fn execute_winapi ( & self ) -> std :: io :: Result < ( ) > {
83+ fn execute_winapi ( & self ) -> Result < ( ) > {
8884 Err ( std:: io:: Error :: other (
8985 "tried to execute EnableAlternateScroll using WinAPI; use ANSI instead" ,
9086 ) )
@@ -100,12 +96,12 @@ impl Command for EnableAlternateScroll {
10096struct DisableAlternateScroll ;
10197
10298impl Command for DisableAlternateScroll {
103- fn write_ansi ( & self , f : & mut impl std :: fmt:: Write ) -> std :: fmt:: Result {
99+ fn write_ansi ( & self , f : & mut impl fmt:: Write ) -> fmt:: Result {
104100 write ! ( f, "\x1b [?1007l" )
105101 }
106102
107103 #[ cfg( windows) ]
108- fn execute_winapi ( & self ) -> std :: io :: Result < ( ) > {
104+ fn execute_winapi ( & self ) -> Result < ( ) > {
109105 Err ( std:: io:: Error :: other (
110106 "tried to execute DisableAlternateScroll using WinAPI; use ANSI instead" ,
111107 ) )
@@ -144,8 +140,8 @@ pub fn init() -> Result<Terminal> {
144140}
145141
146142fn set_panic_hook ( ) {
147- let hook = std :: panic:: take_hook ( ) ;
148- std :: panic:: set_hook ( Box :: new ( move |panic_info| {
143+ let hook = panic:: take_hook ( ) ;
144+ panic:: set_hook ( Box :: new ( move |panic_info| {
149145 let _ = restore ( ) ; // ignore any errors as we are already failing
150146 hook ( panic_info) ;
151147 } ) ) ;
@@ -165,40 +161,14 @@ pub struct Tui {
165161 pending_history_lines : Vec < Line < ' static > > ,
166162 alt_saved_viewport : Option < ratatui:: layout:: Rect > ,
167163 #[ cfg( unix) ]
168- resume_pending : Arc < AtomicU8 > , // Stores a ResumeAction
169- #[ cfg( unix) ]
170- suspend_cursor_y : Arc < AtomicU16 > , // Bottom line of inline viewport
164+ suspend_context : SuspendContext ,
171165 // True when overlay alt-screen UI is active
172166 alt_screen_active : Arc < AtomicBool > ,
173167 // True when terminal/tab is focused; updated internally from crossterm events
174168 terminal_focused : Arc < AtomicBool > ,
175169 enhanced_keys_supported : bool ,
176170}
177171
178- #[ cfg( unix) ]
179- #[ derive( Copy , Clone , Debug , Eq , PartialEq ) ]
180- #[ repr( u8 ) ]
181- enum ResumeAction {
182- None = 0 ,
183- RealignInline = 1 ,
184- RestoreAlt = 2 ,
185- }
186-
187- #[ cfg( unix) ]
188- enum PreparedResumeAction {
189- RestoreAltScreen ,
190- RealignViewport ( ratatui:: layout:: Rect ) ,
191- }
192-
193- #[ cfg( unix) ]
194- fn take_resume_action ( pending : & AtomicU8 ) -> ResumeAction {
195- match pending. swap ( ResumeAction :: None as u8 , Ordering :: Relaxed ) {
196- 1 => ResumeAction :: RealignInline ,
197- 2 => ResumeAction :: RestoreAlt ,
198- _ => ResumeAction :: None ,
199- }
200- }
201-
202172#[ derive( Clone , Debug ) ]
203173pub struct FrameRequester {
204174 frame_schedule_tx : tokio:: sync:: mpsc:: UnboundedSender < Instant > ,
@@ -244,9 +214,7 @@ impl Tui {
244214 pending_history_lines : vec ! [ ] ,
245215 alt_saved_viewport : None ,
246216 #[ cfg( unix) ]
247- resume_pending : Arc :: new ( AtomicU8 :: new ( 0 ) ) ,
248- #[ cfg( unix) ]
249- suspend_cursor_y : Arc :: new ( AtomicU16 :: new ( 0 ) ) ,
217+ suspend_context : SuspendContext :: new ( ) ,
250218 alt_screen_active : Arc :: new ( AtomicBool :: new ( false ) ) ,
251219 terminal_focused : Arc :: new ( AtomicBool :: new ( true ) ) ,
252220 enhanced_keys_supported,
@@ -282,37 +250,20 @@ impl Tui {
282250
283251 // State for tracking how we should resume from ^Z suspend.
284252 #[ cfg( unix) ]
285- let resume_pending = self . resume_pending . clone ( ) ;
253+ let suspend_context = self . suspend_context . clone ( ) ;
286254 #[ cfg( unix) ]
287255 let alt_screen_active = self . alt_screen_active . clone ( ) ;
288- #[ cfg( unix) ]
289- let suspend_cursor_y = self . suspend_cursor_y . clone ( ) ;
290-
291- #[ cfg( unix) ]
292- let suspend = move || {
293- if alt_screen_active. load ( Ordering :: Relaxed ) {
294- // Disable alternate scroll when suspending from alt-screen
295- let _ = execute ! ( stdout( ) , DisableAlternateScroll ) ;
296- let _ = execute ! ( stdout( ) , LeaveAlternateScreen ) ;
297- resume_pending. store ( ResumeAction :: RestoreAlt as u8 , Ordering :: Relaxed ) ;
298- } else {
299- resume_pending. store ( ResumeAction :: RealignInline as u8 , Ordering :: Relaxed ) ;
300- }
301- let y = suspend_cursor_y. load ( Ordering :: Relaxed ) ;
302- let _ = execute ! ( stdout( ) , MoveTo ( 0 , y) , crossterm:: cursor:: Show ) ;
303- let _ = Tui :: suspend ( ) ;
304- } ;
305256
306257 let terminal_focused = self . terminal_focused . clone ( ) ;
307258 let event_stream = async_stream:: stream! {
308259 loop {
309260 select! {
310261 Some ( Ok ( event) ) = crossterm_events. next( ) => {
311262 match event {
312- crossterm :: event :: Event :: Key ( key_event) => {
263+ Event :: Key ( key_event) => {
313264 #[ cfg( unix) ]
314265 if SUSPEND_KEY . is_press( key_event) {
315- suspend( ) ;
266+ let _ = suspend_context . suspend( & alt_screen_active ) ;
316267 // We continue here after resume.
317268 yield TuiEvent :: Draw ;
318269 continue ;
@@ -356,67 +307,6 @@ impl Tui {
356307 Box :: pin ( event_stream)
357308 }
358309
359- #[ cfg( unix) ]
360- fn suspend ( ) -> Result < ( ) > {
361- restore ( ) ?;
362- unsafe { libc:: kill ( 0 , libc:: SIGTSTP ) } ;
363- set_modes ( ) ?;
364- Ok ( ( ) )
365- }
366-
367- /// When resuming from ^Z suspend, we want to put things back the way they were before suspend.
368- /// We capture the action in an object so we can pass it into the event stream, since the relevant
369- #[ cfg( unix) ]
370- fn prepare_resume_action (
371- & mut self ,
372- action : ResumeAction ,
373- ) -> Result < Option < PreparedResumeAction > > {
374- match action {
375- ResumeAction :: RealignInline => {
376- let cursor_pos = self
377- . terminal
378- . get_cursor_position ( )
379- . unwrap_or ( self . terminal . last_known_cursor_pos ) ;
380- Ok ( Some ( PreparedResumeAction :: RealignViewport (
381- ratatui:: layout:: Rect :: new ( 0 , cursor_pos. y , 0 , 0 ) ,
382- ) ) )
383- }
384- ResumeAction :: RestoreAlt => {
385- if let Ok ( ratatui:: layout:: Position { y, .. } ) = self . terminal . get_cursor_position ( )
386- && let Some ( saved) = self . alt_saved_viewport . as_mut ( )
387- {
388- saved. y = y;
389- }
390- Ok ( Some ( PreparedResumeAction :: RestoreAltScreen ) )
391- }
392- ResumeAction :: None => Ok ( None ) ,
393- }
394- }
395-
396- #[ cfg( unix) ]
397- fn apply_prepared_resume_action ( & mut self , prepared : PreparedResumeAction ) -> Result < ( ) > {
398- match prepared {
399- PreparedResumeAction :: RealignViewport ( area) => {
400- self . terminal . set_viewport_area ( area) ;
401- }
402- PreparedResumeAction :: RestoreAltScreen => {
403- execute ! ( self . terminal. backend_mut( ) , EnterAlternateScreen ) ?;
404- // Enable "alternate scroll" so terminals may translate wheel to arrows
405- execute ! ( self . terminal. backend_mut( ) , EnableAlternateScroll ) ?;
406- if let Ok ( size) = self . terminal . size ( ) {
407- self . terminal . set_viewport_area ( ratatui:: layout:: Rect :: new (
408- 0 ,
409- 0 ,
410- size. width ,
411- size. height ,
412- ) ) ;
413- self . terminal . clear ( ) ?;
414- }
415- }
416- }
417- Ok ( ( ) )
418- }
419-
420310 /// Enter alternate screen and expand the viewport to full terminal size, saving the current
421311 /// inline viewport for restoration when leaving.
422312 pub fn enter_alt_screen ( & mut self ) -> Result < ( ) > {
@@ -462,8 +352,9 @@ impl Tui {
462352 // If we are resuming from ^Z, we need to prepare the resume action now so we can apply it
463353 // in the synchronized update.
464354 #[ cfg( unix) ]
465- let mut prepared_resume =
466- self . prepare_resume_action ( take_resume_action ( & self . resume_pending ) ) ?;
355+ let mut prepared_resume = self
356+ . suspend_context
357+ . prepare_resume_action ( & mut self . terminal , & mut self . alt_saved_viewport ) ;
467358
468359 // Precompute any viewport updates that need a cursor-position query before entering
469360 // the synchronized update, to avoid racing with the event reader.
@@ -490,12 +381,10 @@ impl Tui {
490381 }
491382 }
492383
493- std :: io :: stdout ( ) . sync_update ( |_| {
384+ stdout ( ) . sync_update ( |_| {
494385 #[ cfg( unix) ]
495- {
496- if let Some ( prepared) = prepared_resume. take ( ) {
497- self . apply_prepared_resume_action ( prepared) ?;
498- }
386+ if let Some ( prepared) = prepared_resume. take ( ) {
387+ prepared. apply ( & mut self . terminal ) ?;
499388 }
500389 let terminal = & mut self . terminal ;
501390 if let Some ( new_area) = pending_viewport_area. take ( ) {
@@ -539,8 +428,7 @@ impl Tui {
539428 } else {
540429 area. bottom ( ) . saturating_sub ( 1 )
541430 } ;
542- self . suspend_cursor_y
543- . store ( inline_area_bottom, Ordering :: Relaxed ) ;
431+ self . suspend_context . set_cursor_y ( inline_area_bottom) ;
544432 }
545433
546434 terminal. draw ( |frame| {
@@ -600,12 +488,12 @@ fn spawn_frame_scheduler(
600488pub struct PostNotification ( pub String ) ;
601489
602490impl Command for PostNotification {
603- fn write_ansi ( & self , f : & mut impl std :: fmt:: Write ) -> std :: fmt:: Result {
491+ fn write_ansi ( & self , f : & mut impl fmt:: Write ) -> fmt:: Result {
604492 write ! ( f, "\x1b ]9;{}\x07 " , self . 0 )
605493 }
606494
607495 #[ cfg( windows) ]
608- fn execute_winapi ( & self ) -> std :: io :: Result < ( ) > {
496+ fn execute_winapi ( & self ) -> Result < ( ) > {
609497 Err ( std:: io:: Error :: other (
610498 "tried to execute PostNotification using WinAPI; use ANSI instead" ,
611499 ) )
0 commit comments