- Whitespace stripping: When displaying read/write tool output, leading whitespace is being stripped via
trim_start()inrender_read_output(line 1435 of messages.rs) - No syntax highlighting: The read/write output is displayed with plain styling (
theme.code_style()) without language-aware syntax highlighting
In render_read_output at line 1434-1438:
// Current (strips whitespace):
let content = if let Some(idx) = line.find('|') {
line[idx + 1..].trim_start().to_string() // <-- removes indentation!
} else {
line.to_string()
};Change to:
// Fixed (preserves whitespace after the tab separator):
let content = if let Some(idx) = line.find('|') {
// Skip the '|' and the single tab that follows it, but preserve the rest
let after_pipe = &line[idx + 1..];
if after_pipe.starts_with('\t') {
after_pipe[1..].to_string()
} else {
after_pipe.to_string()
}
} else {
line.to_string()
};Add a new function to get language from file path:
/// Get the language/extension from a file path for syntax highlighting.
pub fn language_from_path(path: &str) -> &str {
std::path::Path::new(path)
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or("")
}- Extract file path from tool input
- Determine language from file extension
- Build the content string (without line numbers)
- Apply syntax highlighting using
highlight_code - Render the highlighted lines with the tool border prefix
fn render_read_output(
&self,
lines: &mut Vec<Line<'static>>,
output_lines: &[&str],
tool: &DisplayToolCall,
theme: &Theme,
) {
let max_lines = if tool.expanded { 100 } else { 10 };
let total = output_lines.len();
// Extract file path from input to determine language
let input: serde_json::Value = tool
.input
.as_deref()
.and_then(|s| serde_json::from_str(s).ok())
.unwrap_or(serde_json::Value::Null);
let file_path = input.get("filePath").and_then(|v| v.as_str()).unwrap_or("");
let language = crate::widgets::syntax::language_from_path(file_path);
// Extract content lines (stripping line number prefix but preserving whitespace)
let content_lines: Vec<String> = output_lines
.iter()
.take(max_lines)
.map(|line| {
if let Some(idx) = line.find('|') {
let after_pipe = &line[idx + 1..];
if after_pipe.starts_with('\t') {
after_pipe[1..].to_string()
} else {
after_pipe.to_string()
}
} else {
line.to_string()
}
})
.collect();
// Apply syntax highlighting if language is detected
if !language.is_empty() {
let code = content_lines.join("\n");
let highlighted = crate::widgets::syntax::highlight_code(&code, language, theme);
for line in highlighted {
let mut new_line = vec![Span::styled(" │ ", theme.tool_border_style())];
new_line.extend(line.spans);
lines.push(Line::from(new_line));
}
} else {
// Fallback to plain code style
for content in content_lines {
let truncated = if content.len() > 70 && !tool.expanded {
format!("{}...", &content[..67])
} else {
content
};
lines.push(Line::from(vec![
Span::styled(" │ ", theme.tool_border_style()),
Span::styled(truncated, theme.code_style()),
]));
}
}
// Show "more lines" indicator
if total > max_lines {
lines.push(Line::from(vec![
Span::styled(" │ ", theme.tool_border_style()),
Span::styled(
format!("... {} more lines", total - max_lines),
theme.dim_style(),
),
]));
}
self.render_expand_hint(lines, tool, total, theme);
}Apply the same syntax highlighting to write tool preview output.
-
crates/wonopcode-tui/src/widgets/syntax.rs- Add
language_from_path()function
- Add
-
crates/wonopcode-tui/src/widgets/messages.rs- Fix whitespace stripping in
render_read_output - Add syntax highlighting to
render_read_output - Add syntax highlighting to
render_write_output
- Fix whitespace stripping in
After implementation:
- Read a file with indentation (e.g., a Rust or Python file) and verify indentation is preserved
- Read files of different types (.rs, .py, .js, .json, .yaml) and verify syntax highlighting is applied
- Write a file and verify the preview shows syntax highlighting