@@ -39,6 +39,8 @@ use crate::{
3939 confirm:: { Confirm , ConfirmMessage } ,
4040 cursor:: { Cursor , is_selectable_in_mode} ,
4141 details:: { Details , DetailsMessage , DetailsVisibility , RenderNextChunkResult } ,
42+ event_polling:: { CrosstermEventPolling , EventPolling , NoopEventPolling } ,
43+ fps:: FpsCounter ,
4244 graph_extension:: { ExtensionDirection , extend_connector_spans} ,
4345 highlight:: { Highlights , with_highlight} ,
4446 key_bind:: { KeyBinds , confirm_key_binds, default_key_binds} ,
@@ -63,6 +65,8 @@ use super::{
6365mod confirm;
6466mod cursor;
6567mod details;
68+ mod event_polling;
69+ mod fps;
6670mod graph_extension;
6771mod highlight;
6872mod key_bind;
@@ -134,44 +138,6 @@ pub(super) async fn render_tui(
134138 Ok ( app. status_lines )
135139}
136140
137- /// Trait for abstracting event polling so we can hardcode events in tests.
138- trait EventPolling {
139- type Error : std:: error:: Error + Send + Sync + ' static ;
140-
141- fn poll ( self , timeout : Duration ) -> Result < impl IntoIterator < Item = Event > , Self :: Error > ;
142- }
143-
144- /// An [`EventPolling`] implementation that polls events for real using crossterm.
145- #[ derive( Copy , Clone ) ]
146- struct CrosstermEventPolling ;
147-
148- impl EventPolling for CrosstermEventPolling {
149- type Error = std:: io:: Error ;
150-
151- fn poll ( self , timeout : Duration ) -> Result < impl IntoIterator < Item = Event > , Self :: Error > {
152- if event:: poll ( timeout) ? {
153- Ok ( Some ( event:: read ( ) ?) )
154- } else {
155- Ok ( None )
156- }
157- }
158- }
159-
160- /// An [`EventPolling`] implementation that never yields events.
161- ///
162- /// This is used for non-interactive runs where touching terminal input can stop the process when
163- /// profilers launch the target in a background process group.
164- #[ derive( Copy , Clone ) ]
165- struct NoopEventPolling ;
166-
167- impl EventPolling for NoopEventPolling {
168- type Error = std:: io:: Error ;
169-
170- fn poll ( self , _timeout : Duration ) -> Result < impl IntoIterator < Item = Event > , Self :: Error > {
171- Ok ( None )
172- }
173- }
174-
175141#[ expect( clippy:: too_many_arguments) ]
176142async fn render_loop < T , E > (
177143 app : & mut App ,
@@ -242,7 +208,11 @@ where
242208 mode,
243209 )
244210 . await ?;
211+
245212 render ( app, terminal_guard) ?;
213+
214+ app. fps . frame_finished ( ) ;
215+
246216 Ok ( ( ) )
247217}
248218
@@ -332,6 +302,10 @@ where
332302 app. should_render = true ;
333303 }
334304
305+ if app. fps . update ( ) {
306+ app. should_render = true ;
307+ }
308+
335309 Ok ( ( ) )
336310}
337311
@@ -341,6 +315,7 @@ where
341315 anyhow:: Error : From < <T :: Backend as Backend >:: Error > ,
342316{
343317 if std:: mem:: take ( & mut app. should_render ) {
318+ let _span = tracing:: trace_span!( "render" ) . entered ( ) ;
344319 terminal_guard. terminal_mut ( ) . draw ( |frame| {
345320 app. renders += 1 ;
346321 app. render ( frame)
@@ -370,6 +345,7 @@ struct App {
370345 options : TuiLaunchOptions ,
371346 delayed_messages : Vec < Message > ,
372347 incoming_out_of_band_messages : Vec < Rc < Receiver < Message > > > ,
348+ fps : FpsCounter ,
373349}
374350
375351impl App {
@@ -407,6 +383,7 @@ impl App {
407383 highlight : Default :: default ( ) ,
408384 delayed_messages : Default :: default ( ) ,
409385 incoming_out_of_band_messages : Default :: default ( ) ,
386+ fps : FpsCounter :: new ( ) ,
410387 confirm : None ,
411388 details,
412389 options,
@@ -1756,10 +1733,6 @@ impl App {
17561733 }
17571734
17581735 fn handle_enter_command_mode ( & mut self ) {
1759- if !matches ! ( self . mode, Mode :: Normal ) {
1760- return ;
1761- }
1762-
17631736 let mut textarea = TextArea :: default ( ) ;
17641737 textarea. set_cursor_line_style ( Style :: default ( ) ) ;
17651738 textarea. move_cursor ( CursorMove :: End ) ;
@@ -1855,7 +1828,6 @@ impl App {
18551828 Some ( * commit_id)
18561829 }
18571830
1858- #[ tracing:: instrument( level = Level :: TRACE , skip_all) ]
18591831 fn render ( & self , frame : & mut Frame ) {
18601832 let content_layout =
18611833 Layout :: vertical ( [ Constraint :: Min ( 1 ) , Constraint :: Length ( 1 ) ] ) . split ( frame. area ( ) ) ;
@@ -2514,20 +2486,23 @@ impl App {
25142486 }
25152487
25162488 fn render_debug ( & self , area : Rect , frame : & mut Frame ) {
2517- let renders = once ( ListItem :: new ( "Renders" ) . black ( ) . on_blue ( ) )
2518- . chain ( once ( ListItem :: new ( format ! ( "{}" , self . renders) ) ) ) ;
2489+ let renders = once ( ListItem :: new ( "FPS" ) . black ( ) . on_blue ( ) ) . chain ( once ( ListItem :: new (
2490+ format ! ( "{} FPS ({} renders)" , self . fps. fps( ) , self . renders) ,
2491+ ) ) ) ;
25192492
25202493 let details_selection = format ! ( "{:#?}" , self . details. selection( ) ) ;
25212494 let details_selection = once ( ListItem :: new ( "Details selection" ) . black ( ) . on_blue ( ) ) . chain (
25222495 details_selection
25232496 . lines ( )
2497+ . take ( 100 )
25242498 . map ( |line| ListItem :: new ( line. to_owned ( ) ) ) ,
25252499 ) ;
25262500
25272501 let status_selection = format ! ( "{:#?}" , self . cursor. selected_line( & self . status_lines) ) ;
25282502 let status_selection = once ( ListItem :: new ( "Status selection" ) . black ( ) . on_blue ( ) ) . chain (
25292503 status_selection
25302504 . lines ( )
2505+ . take ( 100 )
25312506 . map ( |line| ListItem :: new ( line. to_owned ( ) ) ) ,
25322507 ) ;
25332508
0 commit comments