@@ -29,7 +29,7 @@ use warpui_core::{
2929use crate :: agent_block_sections:: render_fallback_tool_call_section;
3030use crate :: editor_view:: { TuiEditorView , TuiEditorViewEvent } ;
3131use crate :: keybindings:: { TUI_BINDING_GROUP , is_tui_owned_binding} ;
32- use crate :: terminal_block:: TerminalBlockElement ;
32+ use crate :: terminal_block:: { TerminalBlockElement , block_content_rows } ;
3333use crate :: terminal_use:: user_controls_running_command;
3434use crate :: tool_call_labels:: {
3535 CommandBlockState , ResolvedCommandBlock , styled_tool_call_label_spans, tool_call_display_state,
@@ -75,6 +75,12 @@ pub(crate) fn init(app: &mut AppContext) {
7575 ] ) ;
7676 app. register_tui_binding_validator :: < TuiShellCommandView > ( is_tui_owned_binding) ;
7777}
78+ #[ derive( Clone , Copy , Debug , Eq , PartialEq ) ]
79+ pub ( super ) struct ShellCommandContentExtent {
80+ rendered_height : usize ,
81+ command_rows : usize ,
82+ output_rows : usize ,
83+ }
7884
7985struct ShellCommandViewState {
8086 collapsed : bool ,
@@ -120,7 +126,7 @@ pub(super) struct TuiShellCommandView {
120126 permission_prompt : ViewHandle < TuiPermissionPrompt > ,
121127 command_was_edited : bool ,
122128 state : ShellCommandViewState ,
123- command_running : Cell < bool > ,
129+ last_measured_content_extent : Cell < Option < ShellCommandContentExtent > > ,
124130 header_mouse_state : MouseStateHandle ,
125131 cli_subagent_view : Option < ViewHandle < TuiCLISubagentView > > ,
126132}
@@ -196,7 +202,7 @@ impl TuiShellCommandView {
196202 permission_prompt,
197203 command_was_edited : false ,
198204 state : ShellCommandViewState :: new_collapsed ( ) ,
199- command_running : Cell :: new ( false ) ,
205+ last_measured_content_extent : Cell :: new ( None ) ,
200206 header_mouse_state : MouseStateHandle :: default ( ) ,
201207 cli_subagent_view : None ,
202208 }
@@ -343,9 +349,31 @@ impl TuiShellCommandView {
343349 }
344350 } ) ;
345351 }
346- /// Whether expanded command output can still grow between layout events.
347- pub ( super ) fn needs_continuous_height_measurement ( & self ) -> bool {
348- !self . state . is_collapsed ( ) && self . command_running . get ( )
352+ /// Returns the live row extent when command output can change this view's height.
353+ pub ( super ) fn dynamic_content_extent ( & self ) -> Option < ShellCommandContentExtent > {
354+ let model = self . terminal_model . lock ( ) ;
355+ let block = model. block_list ( ) . block_for_ai_action_id ( & self . action . id ) ?;
356+ let expanded = !self . state . is_collapsed ( ) || user_controls_running_command ( block) ;
357+ ( expanded && !block. finished ( ) ) . then ( || ShellCommandContentExtent {
358+ rendered_height : block_content_rows ( block) . len ( ) ,
359+ command_rows : usize:: from ( !block. should_hide_command_grid ( ) )
360+ . saturating_mul ( block. prompt_and_command_grid ( ) . len_displayed ( ) ) ,
361+ output_rows : usize:: from ( !block. should_hide_output_grid ( ) )
362+ . saturating_mul ( block. output_grid ( ) . len_displayed ( ) ) ,
363+ } )
364+ }
365+ /// Whether running command output has changed height since the last layout.
366+ pub ( super ) fn needs_height_measurement ( & self ) -> bool {
367+ self . dynamic_content_extent ( )
368+ . is_some_and ( |extent| self . last_measured_content_extent . get ( ) != Some ( extent) )
369+ }
370+
371+ /// Records the row extent represented by the latest agent-block layout.
372+ pub ( super ) fn record_content_extent_measurement (
373+ & self ,
374+ extent : Option < ShellCommandContentExtent > ,
375+ ) {
376+ self . last_measured_content_extent . set ( extent) ;
349377 }
350378 pub ( super ) fn is_expanded ( & self ) -> bool {
351379 !self . state . is_collapsed ( )
@@ -437,11 +465,9 @@ impl TuiView for TuiShellCommandView {
437465 . as_ref ( app)
438466 . get_action_status ( & self . action . id ) ;
439467 if matches ! ( status, Some ( AIActionStatus :: Blocked ) ) {
440- self . command_running . set ( false ) ;
441468 return self . render_blocked ( app) ;
442469 }
443470 let Some ( block) = self . resolved_block ( status. as_ref ( ) ) else {
444- self . command_running . set ( false ) ;
445471 return render_fallback_tool_call_section (
446472 & self . action ,
447473 status. as_ref ( ) ,
@@ -450,8 +476,6 @@ impl TuiView for TuiShellCommandView {
450476 app,
451477 ) ;
452478 } ;
453- self . command_running
454- . set ( matches ! ( block. details. state, CommandBlockState :: Running ) ) ;
455479
456480 let builder = TuiUiBuilder :: from_app ( app) ;
457481 let display_state =
0 commit comments