Skip to content

Commit 4caf3b4

Browse files
committed
feat: compact tool stdout rendering
1 parent 9ddde17 commit 4caf3b4

File tree

1 file changed

+41
-39
lines changed

1 file changed

+41
-39
lines changed

src/agent/runloop/tool_output.rs

Lines changed: 41 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use anstyle::{AnsiColor, Color, Style};
22
use anyhow::{Context, Result};
33
use serde_json::Value;
4+
use std::collections::{HashMap, VecDeque};
45
use vtcode_core::config::ToolOutputMode;
56
use vtcode_core::config::constants::{defaults, tools};
67
use vtcode_core::config::loader::VTCodeConfig;
@@ -30,16 +31,17 @@ pub(crate) fn render_tool_output(
3031
}
3132
}
3233

34+
let output_mode = vt_config
35+
.map(|cfg| cfg.ui.tool_output_mode)
36+
.unwrap_or(ToolOutputMode::Compact);
37+
let tail_limit = resolve_stdout_tail_limit(vt_config);
38+
3339
if tool_name == Some(tools::CURL) {
34-
render_curl_result(renderer, val)?;
40+
render_curl_result(renderer, val, output_mode, tail_limit)?;
3541
}
3642

3743
let git_styles = GitStyles::new();
3844
let ls_styles = LsStyles::from_env();
39-
let output_mode = vt_config
40-
.map(|cfg| cfg.ui.tool_output_mode)
41-
.unwrap_or(ToolOutputMode::Compact);
42-
let tail_limit = resolve_stdout_tail_limit(vt_config);
4345

4446
if let Some(stdout) = val.get("stdout").and_then(|value| value.as_str()) {
4547
render_stream_section(
@@ -467,21 +469,24 @@ fn resolve_stdout_tail_limit(config: Option<&VTCodeConfig>) -> usize {
467469
}
468470

469471
fn tail_lines<'a>(text: &'a str, limit: usize) -> (Vec<&'a str>, usize) {
472+
if text.is_empty() {
473+
return (Vec::new(), 0);
474+
}
470475
if limit == 0 {
471476
return (Vec::new(), text.lines().count());
472477
}
473478

474-
let mut lines: Vec<&str> = text.lines().collect();
475-
let total = lines.len();
476-
if total == 0 {
477-
return (Vec::new(), 0);
478-
}
479-
if total <= limit {
480-
return (lines, total);
479+
let mut ring = VecDeque::with_capacity(limit);
480+
let mut total = 0;
481+
for line in text.lines() {
482+
total += 1;
483+
if ring.len() == limit {
484+
ring.pop_front();
485+
}
486+
ring.push_back(line);
481487
}
482-
let start = total.saturating_sub(limit);
483-
let tail = lines.split_off(start);
484-
(tail, total)
488+
489+
(ring.into_iter().collect(), total)
485490
}
486491

487492
fn select_stream_lines<'a>(
@@ -558,8 +563,12 @@ fn render_stream_section(
558563
Ok(())
559564
}
560565

561-
fn render_curl_result(renderer: &mut AnsiRenderer, val: &Value) -> Result<()> {
562-
const PREVIEW_LINES: usize = 5;
566+
fn render_curl_result(
567+
renderer: &mut AnsiRenderer,
568+
val: &Value,
569+
mode: ToolOutputMode,
570+
tail_limit: usize,
571+
) -> Result<()> {
563572
const PREVIEW_LINE_MAX: usize = 120;
564573
const NOTICE_MAX: usize = 160;
565574

@@ -624,34 +633,29 @@ fn render_curl_result(renderer: &mut AnsiRenderer, val: &Value) -> Result<()> {
624633
if let Some(body) = val.get("body").and_then(|value| value.as_str())
625634
&& !body.trim().is_empty()
626635
{
627-
let mut lines = body.lines();
628-
let mut preview: Vec<String> = Vec::new();
636+
let prefer_full = renderer.prefers_untruncated_output();
637+
let (lines, total, truncated) = select_stream_lines(body, mode, tail_limit, prefer_full);
638+
let tail_len = lines.len();
629639

630-
for _ in 0..PREVIEW_LINES {
631-
if let Some(line) = lines.next() {
632-
let trimmed = line.trim_end();
633-
if trimmed.is_empty() {
634-
continue;
635-
}
636-
preview.push(truncate_text(trimmed, PREVIEW_LINE_MAX));
637-
} else {
638-
break;
640+
if tail_len > 0 {
641+
if truncated {
642+
renderer.line(
643+
MessageStyle::Info,
644+
&format!(" ... showing last {}/{} body lines", tail_len, total),
645+
)?;
639646
}
640-
}
641647

642-
if !preview.is_empty() {
643648
renderer.line(
644649
MessageStyle::Tool,
645-
&format!("[curl] body preview ({} lines)", preview.len()),
650+
&format!("[curl] body tail ({} lines)", tail_len),
646651
)?;
647-
for line in preview {
648-
renderer.line(MessageStyle::Output, &format!(" {line}"))?;
649-
}
650652

651-
if lines.next().is_some() {
653+
for line in lines {
654+
let trimmed = line.trim_end();
655+
652656
renderer.line(
653-
MessageStyle::Info,
654-
&format!(" … truncated after {PREVIEW_LINES} lines"),
657+
MessageStyle::Output,
658+
&format!(" {}", truncate_text(trimmed, PREVIEW_LINE_MAX)),
655659
)?;
656660
}
657661
}
@@ -689,8 +693,6 @@ impl GitStyles {
689693
}
690694
}
691695

692-
use std::collections::HashMap;
693-
694696
struct LsStyles {
695697
classes: HashMap<String, Style>,
696698
suffixes: Vec<(String, Style)>,

0 commit comments

Comments
 (0)