UI: Show consecutive read_file calls as continuation lines
When the LLM reads the same file multiple times in sequence (scrolling
through a large file), instead of showing each as a separate line:
● read_file | path [0..2000] | 50 lines | 100 ◉ 5ms
● read_file | path [2000..4000] | 50 lines | 100 ◉ 5ms
● read_file | path [4000..6000] | 50 lines | 100 ◉ 5ms
Now shows a cleaner continuation format:
● read_file | path [0..2000] | 50 lines | 100 ◉ 5ms
└─ reading further [2000..4000] | 50 lines | 100 ◉ 5ms
└─ reading further [4000..6000] | 50 lines | 100 ◉ 5ms
This makes it visually clear that the agent is scrolling through
a single file rather than reading multiple different files.
Implementation:
- Added last_read_file_path field to ConsoleUiWriter
- Detect when consecutive read_file calls target the same file
- Print continuation format for subsequent reads
- Reset tracking when:
- A different tool is executed (shell, write_file, etc.)
- A different file is read
- Text is output between tool calls
This commit is contained in:
@@ -20,6 +20,8 @@ pub struct ConsoleUiWriter {
|
||||
last_output_was_text: std::sync::Mutex<bool>,
|
||||
/// Track if the last output was a tool call (for spacing between tool calls and text)
|
||||
last_output_was_tool: std::sync::Mutex<bool>,
|
||||
/// Track the last read_file path for continuation display
|
||||
last_read_file_path: std::sync::Mutex<Option<String>>,
|
||||
}
|
||||
|
||||
/// ANSI color code for duration display based on elapsed time.
|
||||
@@ -73,6 +75,7 @@ impl ConsoleUiWriter {
|
||||
markdown_formatter: Mutex::new(None),
|
||||
last_output_was_text: std::sync::Mutex::new(false),
|
||||
last_output_was_tool: std::sync::Mutex::new(false),
|
||||
last_read_file_path: std::sync::Mutex::new(None),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -322,6 +325,8 @@ impl UiWriter for ConsoleUiWriter {
|
||||
// Handle file operation tools and other compact tools
|
||||
let is_compact_tool = matches!(tool_name, "read_file" | "write_file" | "str_replace" | "remember" | "take_screenshot" | "code_coverage" | "rehydrate");
|
||||
if !is_compact_tool {
|
||||
// Reset continuation tracking for non-compact tools
|
||||
*self.last_read_file_path.lock().unwrap() = None;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -343,6 +348,10 @@ impl UiWriter for ConsoleUiWriter {
|
||||
.map(|(_, v)| v.as_str())
|
||||
.unwrap_or("");
|
||||
|
||||
// Check if this is a continuation of reading the same file
|
||||
let mut last_read_path = self.last_read_file_path.lock().unwrap();
|
||||
let is_continuation = tool_name == "read_file" && !file_path.is_empty() && last_read_path.as_deref() == Some(file_path);
|
||||
|
||||
// For tools without file_path, get other relevant args
|
||||
let display_arg = if file_path.is_empty() {
|
||||
// For remember, take_screenshot, etc. - no path to show
|
||||
@@ -387,33 +396,42 @@ impl UiWriter for ConsoleUiWriter {
|
||||
// Color for tool name
|
||||
let tool_color = if is_agent_mode { "\x1b[38;5;250m" } else { "\x1b[32m" };
|
||||
|
||||
// Print compact single line - different format for tools with/without path
|
||||
if display_arg.is_empty() {
|
||||
// Tools without file path: " ● tool_name | summary | tokens ◉ time"
|
||||
// Print compact single line
|
||||
if is_continuation {
|
||||
// Continuation line for consecutive read_file on same file:
|
||||
// " └─ reading further [range] | summary | tokens ◉ time"
|
||||
println!(
|
||||
" \x1b[2m●\x1b[0m {}{} \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m",
|
||||
tool_color,
|
||||
tool_name,
|
||||
summary,
|
||||
tokens_delta,
|
||||
duration_str
|
||||
);
|
||||
} else {
|
||||
// Tools with file path: " ● tool_name | path [range] | summary | tokens ◉ time"
|
||||
println!(
|
||||
" \x1b[2m●\x1b[0m {}{} \x1b[2m|\x1b[0m \x1b[35m{}{}\x1b[0m \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m",
|
||||
tool_color,
|
||||
tool_name,
|
||||
display_arg,
|
||||
" \x1b[2m└─ reading further\x1b[0m\x1b[35m{}\x1b[0m \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m",
|
||||
range_suffix,
|
||||
summary,
|
||||
tokens_delta,
|
||||
duration_str
|
||||
);
|
||||
} else if display_arg.is_empty() {
|
||||
// Tools without file path: " ● tool_name | summary | tokens ◉ time"
|
||||
println!(
|
||||
" \x1b[2m●\x1b[0m {}{} \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m",
|
||||
tool_color, tool_name, summary, tokens_delta, duration_str
|
||||
);
|
||||
} else {
|
||||
// Tools with file path: " ● tool_name | path [range] | summary | tokens ◉ time"
|
||||
println!(
|
||||
" \x1b[2m●\x1b[0m {}{} \x1b[2m|\x1b[0m \x1b[35m{}{}\x1b[0m \x1b[2m| {}\x1b[0m \x1b[2m| {} ◉ {}\x1b[0m",
|
||||
tool_color, tool_name, display_arg, range_suffix, summary, tokens_delta, duration_str
|
||||
);
|
||||
}
|
||||
|
||||
// Update last_read_file_path for continuation tracking
|
||||
if tool_name == "read_file" && !file_path.is_empty() {
|
||||
*last_read_path = Some(file_path.to_string());
|
||||
} else {
|
||||
// Reset for non-read_file tools
|
||||
*last_read_path = None;
|
||||
}
|
||||
|
||||
// Clear the stored tool info
|
||||
drop(args); // Release the lock before clearing
|
||||
drop(last_read_path); // Release this lock too
|
||||
self.clear_tool_state();
|
||||
|
||||
true
|
||||
@@ -422,6 +440,14 @@ impl UiWriter for ConsoleUiWriter {
|
||||
fn print_tool_timing(&self, duration_str: &str, tokens_delta: u32, context_percentage: f32) {
|
||||
let color_code = duration_color(duration_str);
|
||||
|
||||
// Reset read_file continuation tracking for non-read_file tools
|
||||
// (read_file tools handle this in print_tool_compact)
|
||||
if let Some(tool_name) = self.current_tool_name.lock().unwrap().as_ref() {
|
||||
if tool_name != "read_file" {
|
||||
*self.last_read_file_path.lock().unwrap() = None;
|
||||
}
|
||||
}
|
||||
|
||||
// Add blank line before footer for research tool (its output is a full report)
|
||||
if let Some(tool_name) = self.current_tool_name.lock().unwrap().as_ref() {
|
||||
if tool_name == "research" {
|
||||
@@ -477,6 +503,8 @@ impl UiWriter for ConsoleUiWriter {
|
||||
// Track that we just output text (only if non-empty)
|
||||
if !content.trim().is_empty() {
|
||||
*self.last_output_was_text.lock().unwrap() = true;
|
||||
// Reset read_file continuation tracking when text is output between tool calls
|
||||
*self.last_read_file_path.lock().unwrap() = None;
|
||||
}
|
||||
let _ = io::stdout().flush();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user