steer_tui/tui/widgets/formatters/
replace.rs

1use super::ToolFormatter;
2use crate::tui::theme::{Component, Theme};
3use ratatui::{
4    style::{Color, Modifier, Style},
5    text::{Line, Span},
6};
7use serde_json::Value;
8use steer_core::app::conversation::ToolResult;
9use steer_tools::tools::replace::ReplaceParams;
10
11pub struct ReplaceFormatter;
12
13impl ToolFormatter for ReplaceFormatter {
14    fn compact(
15        &self,
16        params: &Value,
17        _result: &Option<ToolResult>,
18        _wrap_width: usize,
19        theme: &Theme,
20    ) -> Vec<Line<'static>> {
21        let mut lines = Vec::new();
22
23        let Ok(params) = serde_json::from_value::<ReplaceParams>(params.clone()) else {
24            return vec![Line::from(Span::styled(
25                "Invalid replace params",
26                theme.style(Component::ErrorText),
27            ))];
28        };
29
30        let line_count = params.content.lines().count();
31
32        lines.push(Line::from(vec![
33            Span::styled(format!("{} ", params.file_path), Style::default()),
34            Span::styled(
35                format!("({line_count} lines)"),
36                theme
37                    .style(Component::DimText)
38                    .add_modifier(Modifier::ITALIC),
39            ),
40        ]));
41
42        lines
43    }
44
45    fn detailed(
46        &self,
47        params: &Value,
48        result: &Option<ToolResult>,
49        wrap_width: usize,
50        theme: &Theme,
51    ) -> Vec<Line<'static>> {
52        let mut lines = Vec::new();
53
54        let Ok(params) = serde_json::from_value::<ReplaceParams>(params.clone()) else {
55            return vec![Line::from(Span::styled(
56                "Invalid replace params",
57                theme.style(Component::ErrorText),
58            ))];
59        };
60
61        lines.push(Line::from(Span::styled(
62            format!("Replacing {}", params.file_path),
63            Style::default()
64                .fg(Color::Yellow)
65                .add_modifier(Modifier::BOLD),
66        )));
67
68        if result.is_none() {
69            // Show preview of new content
70            lines.push(Line::from(Span::styled(
71                format!("+++ {} (Full New Content)", params.file_path),
72                theme.style(Component::ToolSuccess),
73            )));
74
75            const MAX_PREVIEW_LINES: usize = 15;
76            for (idx, line) in params.content.lines().enumerate() {
77                if idx >= MAX_PREVIEW_LINES {
78                    lines.push(Line::from(Span::styled(
79                        format!(
80                            "... ({} more lines)",
81                            params.content.lines().count() - MAX_PREVIEW_LINES
82                        ),
83                        theme
84                            .style(Component::DimText)
85                            .add_modifier(Modifier::ITALIC),
86                    )));
87                    break;
88                }
89                for wrapped_line in textwrap::wrap(line, wrap_width) {
90                    lines.push(Line::from(Span::styled(
91                        format!("+ {wrapped_line}"),
92                        theme.style(Component::ToolSuccess),
93                    )));
94                }
95            }
96        }
97
98        // Show error if result is an error
99        if let Some(ToolResult::Error(error)) = result {
100            lines.push(Line::from(Span::styled(
101                error.to_string(),
102                theme.style(Component::ErrorText),
103            )));
104        }
105
106        lines
107    }
108}