From b9e9da435402b0e31fae5e4f15c7fd7b59eab184 Mon Sep 17 00:00:00 2001 From: Drew Galbraith Date: Wed, 11 Mar 2026 16:47:55 +0000 Subject: [PATCH] Truncate tool use. (#9) Truncate tool use to 10 lines by default. Reviewed-on: https://git.tiramisu.one/drew/skate/pulls/9 Co-authored-by: Drew Galbraith Co-committed-by: Drew Galbraith --- src/tui/tool_display.rs | 49 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/tui/tool_display.rs b/src/tui/tool_display.rs index 39af479..0383f78 100644 --- a/src/tui/tool_display.rs +++ b/src/tui/tool_display.rs @@ -8,6 +8,9 @@ use similar::{ChangeTag, TextDiff}; use crate::core::types::ToolDisplay; +/// Maximum number of output lines shown for a tool result before truncation. +const MAX_RESULT_LINES: usize = 10; + /// Format a tool that is currently executing (or awaiting approval). pub fn format_executing(name: &str, display: &ToolDisplay) -> String { match display { @@ -31,9 +34,12 @@ pub fn format_executing(name: &str, display: &ToolDisplay) -> String { } /// Format a tool result after execution completes. +/// +/// Output is truncated to [`MAX_RESULT_LINES`] lines to keep the TUI +/// responsive for commands that produce large output (e.g. verbose test runs). pub fn format_result(name: &str, display: &ToolDisplay, is_error: bool) -> String { let prefix = if is_error { "error" } else { "result" }; - match display { + let raw = match display { ToolDisplay::WriteFile { path, old_content, @@ -54,7 +60,19 @@ pub fn format_result(name: &str, display: &ToolDisplay, is_error: bool) -> Strin ToolDisplay::ListDirectory { path } => format!("ls {path}"), ToolDisplay::ReadFile { path } => format!("read {path}"), ToolDisplay::Generic { summary } => format!("[{name} {prefix}] {summary}"), + }; + truncate_lines(&raw, MAX_RESULT_LINES) +} + +/// Keep at most `max` lines, appending a summary if truncated. +fn truncate_lines(s: &str, max: usize) -> String { + let total = s.lines().count(); + if total <= max { + return s.to_string(); } + let mut out: String = s.lines().take(max).collect::>().join("\n"); + out.push_str(&format!("\n... ({} lines truncated)", total - max)); + out } /// Produce a unified diff between `old` and `new` text. @@ -130,4 +148,33 @@ mod tests { assert!(diff.contains("+line1")); assert!(diff.contains("+line2")); } + + #[test] + fn truncate_lines_under_limit() { + assert_eq!(truncate_lines("a\nb\nc", 5), "a\nb\nc"); + } + + #[test] + fn truncate_lines_over_limit() { + let input: String = (0..300) + .map(|i| format!("line {i}")) + .collect::>() + .join("\n"); + let output = truncate_lines(&input, MAX_RESULT_LINES); + assert_eq!(output.lines().count(), MAX_RESULT_LINES + 1); // +1 for summary + assert!(output.contains("290 lines truncated")); + } + + #[test] + fn format_result_shell_truncates_large_output() { + let big_output = (0..500) + .map(|i| format!("line {i}")) + .collect::>() + .join("\n"); + let display = ToolDisplay::ShellExec { + command: format!("cargo test\n{big_output}"), + }; + let result = format_result("shell_exec", &display, false); + assert!(result.contains("lines truncated")); + } }