use super::*;
pub(crate) fn is_exec_session_tool(tool_name: Option<&str>) -> bool {
tool_name.is_some_and(crate::agent::runloop::unified::is_exec_session_tool_name)
}
#[allow(
clippy::too_many_arguments,
reason = "Intentional compatibility, platform, or test-only suppression."
)]
#[cfg_attr(
feature = "profiling",
tracing::instrument(skip(renderer, content, git_styles, ls_styles, config), level = "debug")
)]
pub(crate) async fn render_stream_section(
renderer: &mut AnsiRenderer,
title: &str,
content: &str,
mode: ToolOutputMode,
tail_limit: usize,
tool_name: Option<&str>,
git_styles: &GitStyles,
ls_styles: &LsStyles,
fallback_style: MessageStyle,
allow_ansi: bool,
disable_spool: bool,
config: Option<&VTCodeConfig>,
) -> Result<()> {
use std::fmt::Write as FmtWrite;
let force_session_body = renderer.session_body_active();
let is_run_command = !force_session_body
&& tool_name.is_some_and(|name| {
tool_intent::is_command_run_tool(name)
|| name == vtcode_core::config::constants::tools::UNIFIED_EXEC
|| name == vtcode_core::config::constants::tools::EXEC_PTY_CMD
});
let is_exec_session = force_session_body || (!is_run_command && is_exec_session_tool(tool_name));
let allow_ansi_for_tool = allow_ansi && !is_run_command;
let apply_line_styles = !is_run_command && !is_exec_session;
let stripped_for_diff = strip_ansi_codes(content);
let is_diff_content = apply_line_styles && looks_like_diff_content(stripped_for_diff.as_ref());
let normalized_content = if allow_ansi_for_tool {
Cow::Borrowed(content)
} else {
stripped_for_diff
};
if is_run_command {
return render_run_command_preview(
renderer,
normalized_content.as_ref(),
tool_name,
fallback_style,
disable_spool,
config,
)
.await;
}
let was_truncated_by_tokens = false;
if !disable_spool
&& let Some(tool) = tool_name
&& let Ok(Some(log_path)) = spool_output_if_needed(normalized_content.as_ref(), tool, config).await
{
if normalized_content.len() > EXTREME_OUTPUT_THRESHOLD_MB {
let mut msg_buffer = String::with_capacity(256);
let _ = write!(
&mut msg_buffer,
"Output too large ({} bytes), spooled to: {}",
normalized_content.len(),
log_path.display()
);
renderer.line(MessageStyle::ToolDetail, &msg_buffer)?;
renderer.line(MessageStyle::ToolDetail, "(Preview skipped due to size)")?;
return Ok(());
}
let head_lines = RUN_COMMAND_HEAD_PREVIEW_LINES;
let tail_lines_count = RUN_COMMAND_TAIL_PREVIEW_LINES;
let preview = excerpt_text_lines(normalized_content.as_ref(), head_lines, tail_lines_count);
let total = preview.total;
let mut msg_buffer = String::with_capacity(256);
if !is_run_command {
let uppercase_title = if title.is_empty() {
Cow::Borrowed("OUTPUT")
} else {
Cow::Owned(title.to_ascii_uppercase())
};
let _ = write!(
&mut msg_buffer,
"[{}] {} bytes, {} lines — spooled to: {}",
uppercase_title.as_ref(),
normalized_content.len(),
total,
log_path.display()
);
} else {
let _ = write!(
&mut msg_buffer,
"{} bytes, {} lines — spooled to: {}",
normalized_content.len(),
total,
log_path.display()
);
}
renderer.line(MessageStyle::ToolDetail, &msg_buffer)?;
for line in preview.head.iter() {
render_preview_line(renderer, line, None, Some(" "), true, fallback_style, None)?;
}
if preview.hidden_count > 0 {
renderer.line(
MessageStyle::ToolDetail,
&hidden_lines_notice(preview.hidden_count, HiddenLinesNoticeKind::CommandPreview),
)?;
}
for line in preview.tail.iter() {
render_preview_line(renderer, line, None, Some(" "), true, fallback_style, None)?;
}
return Ok(());
}
if is_diff_content {
render_diff_content_block(
renderer,
normalized_content.as_ref(),
tool_name,
git_styles,
ls_styles,
fallback_style,
mode,
tail_limit,
)?;
return Ok(());
}
let prefer_full = renderer.prefers_untruncated_output();
let (mut lines_vec, total, mut truncated) =
select_stream_lines_streaming(normalized_content.as_ref(), mode, tail_limit, prefer_full);
if prefer_full && trim_to_tail(&mut lines_vec, INLINE_STREAM_MAX_LINES) {
truncated = true;
}
if is_exec_session && trim_to_tail(&mut lines_vec, EXEC_SESSION_OUTPUT_MAX_LINES) {
truncated = true;
}
let truncated = truncated || was_truncated_by_tokens;
if lines_vec.is_empty() {
return Ok(());
}
let session_body_style = is_exec_session.then(|| fallback_style.style().effects(Effects::DIMMED));
if !is_exec_session && should_render_as_code_block(fallback_style) && !apply_line_styles {
let markdown = build_markdown_code_block(&lines_vec, None, true);
renderer.render_markdown_output(fallback_style, &markdown)?;
} else {
for line in &lines_vec {
if apply_line_styles && let Some(style) = select_line_style(tool_name, line, git_styles, ls_styles) {
render_preview_line(renderer, line, None, None, true, fallback_style, Some(style))?;
} else {
render_preview_line(renderer, line, None, None, true, fallback_style, session_body_style)?;
}
}
}
let hidden = if truncated {
total.saturating_sub(lines_vec.len())
} else {
0
};
if hidden > 0 {
let notice_kind = if was_truncated_by_tokens {
HiddenLinesNoticeKind::TokenBudget
} else if is_exec_session {
HiddenLinesNoticeKind::ExecSessionExpand
} else {
HiddenLinesNoticeKind::Generic
};
let notice = hidden_lines_notice_with(hidden, notice_kind, renderer.supports_inline_ui());
if is_exec_session {
if let Some(anchor) = renderer.take_session_expand_anchor() {
renderer.set_next_tool_output_anchor(anchor);
}
renderer.line_with_override_style(
MessageStyle::ToolDetail,
MessageStyle::ToolDetail.style().effects(Effects::DIMMED),
¬ice,
)?;
} else {
renderer.line(MessageStyle::ToolDetail, ¬ice)?;
}
}
Ok(())
}