vtcode_core/utils/ansi/
compact.rs1use super::*;
4
5use crate::utils::transcript;
6
7use anyhow::Result;
8use vtcode_commons::formatting::{RAN_COMMAND_CONTINUATION_WIDTH, RAN_COMMAND_FIRST_WIDTH};
9use vtcode_commons::tool_types::CompactStr;
10use vtcode_commons::ui_protocol::{CompactActivityMetadata, ToolOutputId};
11
12impl AnsiRenderer {
13 pub fn flush_compact_command_group(&mut self) {
18 self.compact_command_group = None;
19 }
20
21 fn next_compact_activity(
22 &mut self,
23 command: String,
24 hidden_line_count: usize,
25 suffix: Option<String>,
26 review_anchor: Option<ToolOutputId>,
27 ) -> (CompactActivityMetadata, bool) {
28 if let Some(group) = &mut self.compact_command_group {
29 group.command_count = group.command_count.saturating_add(1);
30 group.command = None;
31 group.hidden_line_count = group.hidden_line_count.saturating_add(hidden_line_count);
32 group.suffix = None;
33 if let Some(review_anchor) = review_anchor {
34 if group.review_anchor.is_none() {
35 group.review_anchor = Some(review_anchor);
36 }
37 if !group.review_anchors.contains(&review_anchor) {
38 group.review_anchors.push(review_anchor);
39 }
40 }
41 return (group.clone(), true);
42 }
43
44 let group_id = self.next_compact_group_id;
45 self.next_compact_group_id = self.next_compact_group_id.wrapping_add(1);
46 let activity = CompactActivityMetadata {
47 group_id,
48 command_count: 1,
49 command: Some(command.into()),
50 hidden_line_count,
51 suffix: suffix.map(CompactStr::from),
52 review_anchor,
53 review_anchors: review_anchor.into_iter().collect(),
54 };
55 self.compact_command_group = Some(activity.clone());
56 (activity, false)
57 }
58
59 pub fn render_compact_command_activity(
62 &mut self,
63 command: impl Into<String>,
64 hidden_line_count: usize,
65 suffix: Option<String>,
66 review_anchor: Option<ToolOutputId>,
67 ) -> Result<()> {
68 let command = command.into();
69 if !self.supports_inline_ui() {
70 let wrapped = vtcode_commons::formatting::wrap_shell_command_with_continuations(
75 &command,
76 RAN_COMMAND_FIRST_WIDTH,
77 RAN_COMMAND_CONTINUATION_WIDTH,
78 );
79 if wrapped.is_empty() {
80 return self.line(MessageStyle::Info, "• Ran command");
81 }
82 self.line(MessageStyle::Info, &format!("• Ran {}", wrapped[0]))?;
83 for segment in wrapped.iter().skip(1) {
84 self.line(MessageStyle::Info, &format!(" │ {segment}"))?;
85 }
86 return Ok(());
87 }
88
89 let (activity, replaces_previous) =
90 self.next_compact_activity(command, hidden_line_count, suffix, review_anchor);
91 let text = activity.display_text();
92 if let Some(sink) = &self.sink {
93 if replaces_previous {
94 sink.handle.replace_compact_activity(activity);
95 transcript::replace_last(1, std::slice::from_ref(&text));
96 } else {
97 sink.handle.append_compact_activity(activity);
98 transcript::append(&text);
99 }
100 }
101 self.last_line_was_empty = false;
102 Ok(())
103 }
104
105 pub fn collapse_pty_block_to_compact_activity(
108 &mut self,
109 command: impl Into<String>,
110 hidden_line_count: usize,
111 suffix: Option<String>,
112 review_anchor: Option<ToolOutputId>,
113 ) -> Result<()> {
114 if !self.supports_inline_ui() {
115 return Ok(());
116 }
117
118 let (activity, replaces_previous) =
119 self.next_compact_activity(command.into(), hidden_line_count, suffix, review_anchor);
120 let text = activity.display_text();
121 if let Some(sink) = &self.sink {
122 sink.handle.collapse_pty_block(activity);
123 if replaces_previous {
124 transcript::replace_last(1, std::slice::from_ref(&text));
125 } else {
126 transcript::append(&text);
127 }
128 }
129 self.last_line_was_empty = false;
130 Ok(())
131 }
132}