@@ -13,12 +13,12 @@ use ratatui::{
13
13
} ;
14
14
use tracing:: { debug, trace} ;
15
15
16
- const PANE_SIZE_RATIO : f32 = 3.0 / 4.0 ;
17
16
const FRAMERATE : Duration = Duration :: from_millis ( 3 ) ;
17
+ const RESIZE_DEBOUNCE_DELAY : Duration = Duration :: from_millis ( 10 ) ;
18
18
19
19
use super :: {
20
20
event:: { CacheResult , OutputLogs , TaskResult } ,
21
- input, AppReceiver , Error , Event , InputOptions , TaskTable , TerminalPane ,
21
+ input, AppReceiver , Debouncer , Error , Event , InputOptions , SizeInfo , TaskTable , TerminalPane ,
22
22
} ;
23
23
use crate :: tui:: {
24
24
task:: { Task , TasksByStatus } ,
@@ -32,9 +32,7 @@ pub enum LayoutSections {
32
32
}
33
33
34
34
pub struct App < W > {
35
- term_cols : u16 ,
36
- pane_rows : u16 ,
37
- pane_cols : u16 ,
35
+ size : SizeInfo ,
38
36
tasks : BTreeMap < String , TerminalOutput < W > > ,
39
37
tasks_by_status : TasksByStatus ,
40
38
focus : LayoutSections ,
@@ -53,15 +51,7 @@ pub enum Direction {
53
51
impl < W > App < W > {
54
52
pub fn new ( rows : u16 , cols : u16 , tasks : Vec < String > ) -> Self {
55
53
debug ! ( "tasks: {tasks:?}" ) ;
56
- let task_width_hint = TaskTable :: width_hint ( tasks. iter ( ) . map ( |s| s. as_str ( ) ) ) ;
57
-
58
- // Want to maximize pane width
59
- let ratio_pane_width = ( f32:: from ( cols) * PANE_SIZE_RATIO ) as u16 ;
60
- let full_task_width = cols. saturating_sub ( task_width_hint) ;
61
- let pane_cols = full_task_width. max ( ratio_pane_width) ;
62
-
63
- // We use 2 rows for pane title and for the interaction info
64
- let rows = rows. saturating_sub ( 2 ) . max ( 1 ) ;
54
+ let size = SizeInfo :: new ( rows, cols, tasks. iter ( ) . map ( |s| s. as_str ( ) ) ) ;
65
55
66
56
// Initializes with the planned tasks
67
57
// and will mutate as tasks change
@@ -79,17 +69,23 @@ impl<W> App<W> {
79
69
let has_user_interacted = false ;
80
70
let selected_task_index: usize = 0 ;
81
71
72
+ let pane_rows = size. pane_rows ( ) ;
73
+ let pane_cols = size. pane_cols ( ) ;
74
+
82
75
Self {
83
- term_cols : cols,
84
- pane_rows : rows,
85
- pane_cols,
76
+ size,
86
77
done : false ,
87
78
focus : LayoutSections :: TaskList ,
88
79
// Check if stdin is a tty that we should read input from
89
80
tty_stdin : atty:: is ( atty:: Stream :: Stdin ) ,
90
81
tasks : tasks_by_status
91
82
. task_names_in_displayed_order ( )
92
- . map ( |task_name| ( task_name. to_owned ( ) , TerminalOutput :: new ( rows, cols, None ) ) )
83
+ . map ( |task_name| {
84
+ (
85
+ task_name. to_owned ( ) ,
86
+ TerminalOutput :: new ( pane_rows, pane_cols, None ) ,
87
+ )
88
+ } )
93
89
. collect ( ) ,
94
90
tasks_by_status,
95
91
scroll : TableState :: default ( ) . with_selected ( selected_task_index) ,
@@ -250,9 +246,9 @@ impl<W> App<W> {
250
246
let highlighted_task = self . active_task ( ) . to_owned ( ) ;
251
247
// Make sure all tasks have a terminal output
252
248
for task in & tasks {
253
- self . tasks
254
- . entry ( task . clone ( ) )
255
- . or_insert_with ( || TerminalOutput :: new ( self . pane_rows , self . pane_cols , None ) ) ;
249
+ self . tasks . entry ( task . clone ( ) ) . or_insert_with ( || {
250
+ TerminalOutput :: new ( self . size . pane_rows ( ) , self . size . pane_cols ( ) , None )
251
+ } ) ;
256
252
}
257
253
// Trim the terminal output to only tasks that exist in new list
258
254
self . tasks . retain ( |name, _| tasks. contains ( name) ) ;
@@ -279,9 +275,9 @@ impl<W> App<W> {
279
275
let highlighted_task = self . active_task ( ) . to_owned ( ) ;
280
276
// Make sure all tasks have a terminal output
281
277
for task in & tasks {
282
- self . tasks
283
- . entry ( task . clone ( ) )
284
- . or_insert_with ( || TerminalOutput :: new ( self . pane_rows , self . pane_cols , None ) ) ;
278
+ self . tasks . entry ( task . clone ( ) ) . or_insert_with ( || {
279
+ TerminalOutput :: new ( self . size . pane_rows ( ) , self . size . pane_cols ( ) , None )
280
+ } ) ;
285
281
}
286
282
287
283
self . tasks_by_status
@@ -320,7 +316,7 @@ impl<W> App<W> {
320
316
}
321
317
322
318
pub fn handle_mouse ( & mut self , mut event : crossterm:: event:: MouseEvent ) -> Result < ( ) , Error > {
323
- let table_width = self . term_cols - self . pane_cols ;
319
+ let table_width = self . size . task_list_width ( ) ;
324
320
debug ! ( "original mouse event: {event:?}, table_width: {table_width}" ) ;
325
321
// Only handle mouse event if it happens inside of pane
326
322
// We give a 1 cell buffer to make it easier to select the first column of a row
@@ -375,6 +371,15 @@ impl<W> App<W> {
375
371
self . scroll . select ( Some ( 0 ) ) ;
376
372
self . selected_task_index = 0 ;
377
373
}
374
+
375
+ pub fn resize ( & mut self , rows : u16 , cols : u16 ) {
376
+ self . size . resize ( rows, cols) ;
377
+ let pane_rows = self . size . pane_rows ( ) ;
378
+ let pane_cols = self . size . pane_cols ( ) ;
379
+ self . tasks . values_mut ( ) . for_each ( |term| {
380
+ term. resize ( pane_rows, pane_cols) ;
381
+ } )
382
+ }
378
383
}
379
384
380
385
impl < W : Write > App < W > {
@@ -409,7 +414,7 @@ impl<W: Write> App<W> {
409
414
#[ tracing:: instrument( skip( self , output) ) ]
410
415
pub fn process_output ( & mut self , task : & str , output : & [ u8 ] ) -> Result < ( ) , Error > {
411
416
let task_output = self . tasks . get_mut ( task) . unwrap ( ) ;
412
- task_output. parser . process ( output) ;
417
+ task_output. process ( output) ;
413
418
Ok ( ( ) )
414
419
}
415
420
}
@@ -442,15 +447,32 @@ fn run_app_inner<B: Backend + std::io::Write>(
442
447
// Render initial state to paint the screen
443
448
terminal. draw ( |f| view ( app, f) ) ?;
444
449
let mut last_render = Instant :: now ( ) ;
450
+ let mut resize_debouncer = Debouncer :: new ( RESIZE_DEBOUNCE_DELAY ) ;
445
451
let mut callback = None ;
446
452
while let Some ( event) = poll ( app. input_options ( ) , & receiver, last_render + FRAMERATE ) {
447
- callback = update ( app, event) ?;
448
- if app. done {
449
- break ;
450
- }
451
- if FRAMERATE <= last_render. elapsed ( ) {
452
- terminal. draw ( |f| view ( app, f) ) ?;
453
- last_render = Instant :: now ( ) ;
453
+ let mut event = Some ( event) ;
454
+ let mut resize_event = None ;
455
+ if matches ! ( event, Some ( Event :: Resize { .. } ) ) {
456
+ resize_event = resize_debouncer. update (
457
+ event
458
+ . take ( )
459
+ . expect ( "we just matched against a present value" ) ,
460
+ ) ;
461
+ }
462
+ if let Some ( resize) = resize_event. take ( ) . or_else ( || resize_debouncer. query ( ) ) {
463
+ // If we got a resize event, make sure to update ratatui backend.
464
+ terminal. autoresize ( ) ?;
465
+ update ( app, resize) ?;
466
+ }
467
+ if let Some ( event) = event {
468
+ callback = update ( app, event) ?;
469
+ if app. done {
470
+ break ;
471
+ }
472
+ if FRAMERATE <= last_render. elapsed ( ) {
473
+ terminal. draw ( |f| view ( app, f) ) ?;
474
+ last_render = Instant :: now ( ) ;
475
+ }
454
476
}
455
477
}
456
478
@@ -595,12 +617,15 @@ fn update(
595
617
Event :: RestartTasks { tasks } => {
596
618
app. update_tasks ( tasks) ;
597
619
}
620
+ Event :: Resize { rows, cols } => {
621
+ app. resize ( rows, cols) ;
622
+ }
598
623
}
599
624
Ok ( None )
600
625
}
601
626
602
627
fn view < W > ( app : & mut App < W > , f : & mut Frame ) {
603
- let cols = app. pane_cols ;
628
+ let cols = app. size . pane_cols ( ) ;
604
629
let horizontal = Layout :: horizontal ( [ Constraint :: Fill ( 1 ) , Constraint :: Length ( cols) ] ) ;
605
630
let [ table, pane] = horizontal. areas ( f. size ( ) ) ;
606
631
@@ -899,4 +924,34 @@ mod test {
899
924
"selected b"
900
925
) ;
901
926
}
927
+
928
+ #[ test]
929
+ fn test_resize ( ) {
930
+ let mut app: App < Vec < u8 > > = App :: new ( 20 , 24 , vec ! [ "a" . to_string( ) , "b" . to_string( ) ] ) ;
931
+ let pane_rows = app. size . pane_rows ( ) ;
932
+ let pane_cols = app. size . pane_cols ( ) ;
933
+ for ( name, task) in app. tasks . iter ( ) {
934
+ let ( rows, cols) = task. size ( ) ;
935
+ assert_eq ! (
936
+ ( rows, cols) ,
937
+ ( pane_rows, pane_cols) ,
938
+ "size mismatch for {name}"
939
+ ) ;
940
+ }
941
+
942
+ app. resize ( 20 , 18 ) ;
943
+ let new_pane_rows = app. size . pane_rows ( ) ;
944
+ let new_pane_cols = app. size . pane_cols ( ) ;
945
+ assert_eq ! ( pane_rows, new_pane_rows) ;
946
+ assert_ne ! ( pane_cols, new_pane_cols) ;
947
+
948
+ for ( name, task) in app. tasks . iter ( ) {
949
+ let ( rows, cols) = task. size ( ) ;
950
+ assert_eq ! (
951
+ ( rows, cols) ,
952
+ ( new_pane_rows, new_pane_cols) ,
953
+ "size mismatch for {name}"
954
+ ) ;
955
+ }
956
+ }
902
957
}
0 commit comments