1pub use vtcode_commons::ui_protocol::{
12 CompactActivityMetadata, InlineHeaderContext, InlineHeaderHighlight, InlineHeaderStatusBadge,
13 InlineHeaderStatusTone, InlineLinkRange, InlineLinkTarget, InlineListItem, InlineListSearchConfig,
14 InlineListSelection, InlineMessageKind, InlineSegment, InlineTextStyle, InlineTheme, LayoutModeOverride,
15 PlanContent, PlanPhase, PlanStep, ReasoningDisplayMode, RewindAction, SecurePromptConfig, SessionSurface,
16 SlashCommandItem, ThinkingBlockState, ToolOutputId, UiMode, WizardModalMode, WizardStep, convert_style,
17 theme_from_color_fields,
18};
19
20pub use vtcode_commons::ui_protocol::KeyboardProtocolSettings;
21
22#[cfg(feature = "tui")]
25pub use vtcode_ui::tui::app::*;
26
27#[cfg(not(feature = "tui"))]
30mod headless {
31 use std::collections::VecDeque;
32 use std::sync::atomic::{AtomicU64, Ordering};
33 use std::sync::{Arc, Mutex};
34
35 use hashbrown::HashMap;
36 use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
37
38 use super::{
39 CompactActivityMetadata, InlineListItem, InlineListSearchConfig, InlineListSelection, InlineMessageKind,
40 InlineSegment, SecurePromptConfig, ToolOutputId,
41 };
42
43 use crate::ui::theme::ThemeStyles;
44 pub use vtcode_ui::tui::app::{ContentPart, SubmittedInput};
45
46 #[derive(Clone, Debug, PartialEq, Eq)]
48 pub enum InlineEvent {
49 Submit(SubmittedInput),
50 WebmcpSubmit(SubmittedInput),
52 QueueSubmit(SubmittedInput),
53 Steer(SubmittedInput),
54 ProcessLatestQueued,
55 EditQueue,
56 Cancel,
57 Exit,
58 Interrupt,
59 Pause,
60 Resume,
61 BackgroundOperation,
62 ScrollLineUp,
63 ScrollLineDown,
64 ScrollPageUp,
65 ScrollPageDown,
66 JumpToLastChange,
67 OpenFileInEditor(String),
68 OpenUrl(String),
69 LaunchEditor,
70 ForceCancelPtySession,
71 RequestInlinePromptSuggestion(String),
72 HistoryPrevious,
73 HistoryNext,
74 }
75
76 #[derive(Clone, Debug)]
78 pub enum InlineCommand {
79 AppendLine {
80 kind: InlineMessageKind,
81 segments: Vec<InlineSegment>,
82 },
83 AppendPastedMessage {
84 kind: InlineMessageKind,
85 text: String,
86 line_count: usize,
87 },
88 Inline {
89 kind: InlineMessageKind,
90 segment: InlineSegment,
91 },
92 ReplaceLast {
93 count: usize,
94 kind: InlineMessageKind,
95 lines: Vec<Vec<InlineSegment>>,
96 },
97 RecordToolOutput {
98 id: ToolOutputId,
99 lines: Vec<String>,
100 },
101 AppendToolOutputLine {
102 id: ToolOutputId,
103 kind: InlineMessageKind,
104 segments: Vec<InlineSegment>,
105 },
106 AppendCompactActivity(CompactActivityMetadata),
107 RecordDiffReview(vtcode_commons::ui_protocol::DiffReviewAnchor),
108 ReplaceCompactActivity(CompactActivityMetadata),
109 CollapsePtyBlock(CompactActivityMetadata),
110 SetKeyBindings {
111 bindings: HashMap<String, Vec<String>>,
112 },
113 SetImageInputEnabled(bool),
114 RestoreInputDraft(SubmittedInput),
115 ForceRedraw,
116 Shutdown,
117 ClearScreen,
118 CloseModal,
119 SetReasoningStage(Option<String>),
120 }
121
122 #[derive(Clone, Debug)]
124 pub struct InlineHandle {
125 sender: Option<UnboundedSender<InlineCommand>>,
126 deferred_events: Arc<Mutex<VecDeque<InlineEvent>>>,
127 next_tool_output_id: Arc<AtomicU64>,
128 }
129
130 const MAX_DEFERRED_EVENTS: usize = 32;
131
132 impl InlineHandle {
133 pub fn new_for_tests(sender: UnboundedSender<InlineCommand>) -> Self {
134 Self {
135 sender: Some(sender),
136 deferred_events: Arc::new(Mutex::new(VecDeque::new())),
137 next_tool_output_id: Arc::new(AtomicU64::new(0)),
138 }
139 }
140
141 pub fn defer_event(&self, event: InlineEvent) -> anyhow::Result<()> {
142 let mut deferred_events = self
143 .deferred_events
144 .lock()
145 .map_err(|error| anyhow::anyhow!("deferred input queue poisoned: {error}"))?;
146 if deferred_events.len() >= MAX_DEFERRED_EVENTS {
147 return Err(anyhow::anyhow!("deferred input queue is full"));
148 }
149 deferred_events.push_back(event);
150 Ok(())
151 }
152
153 fn take_deferred_event(&self) -> Option<InlineEvent> {
154 self.deferred_events.lock().ok()?.pop_front()
155 }
156
157 pub fn has_deferred_event(&self) -> bool {
158 self.deferred_events.lock().is_ok_and(|events| !events.is_empty())
159 }
160
161 fn send_command(&self, command: InlineCommand) {
162 if let Some(sender) = &self.sender {
163 let _ = sender.send(command);
164 }
165 }
166
167 pub fn append_line(&self, kind: InlineMessageKind, segments: Vec<InlineSegment>) {
168 self.send_command(InlineCommand::AppendLine { kind, segments });
169 }
170 pub fn append_pasted_message(&self, kind: InlineMessageKind, text: String, line_count: usize) {
171 self.send_command(InlineCommand::AppendPastedMessage { kind, text, line_count });
172 }
173 pub fn inline(&self, kind: InlineMessageKind, segment: InlineSegment) {
174 self.send_command(InlineCommand::Inline { kind, segment });
175 }
176 pub fn replace_last(&self, count: usize, kind: InlineMessageKind, lines: Vec<Vec<InlineSegment>>) {
177 self.send_command(InlineCommand::ReplaceLast { count, kind, lines });
178 }
179 pub fn record_tool_output(&self, lines: Vec<String>) -> ToolOutputId {
180 let id = self.next_tool_output_id.fetch_add(1, Ordering::Relaxed);
181 self.send_command(InlineCommand::RecordToolOutput { id, lines });
182 id
183 }
184 pub fn append_tool_output_line(&self, id: ToolOutputId, kind: InlineMessageKind, segments: Vec<InlineSegment>) {
185 self.send_command(InlineCommand::AppendToolOutputLine { id, kind, segments });
186 }
187 pub fn append_compact_activity(&self, activity: CompactActivityMetadata) {
188 self.send_command(InlineCommand::AppendCompactActivity(activity));
189 }
190 pub fn record_diff_review(&self, anchor: vtcode_commons::ui_protocol::DiffReviewAnchor) {
191 self.send_command(InlineCommand::RecordDiffReview(anchor));
194 }
195 pub fn replace_compact_activity(&self, activity: CompactActivityMetadata) {
196 self.send_command(InlineCommand::ReplaceCompactActivity(activity));
197 }
198 pub fn collapse_pty_block(&self, activity: CompactActivityMetadata) {
199 self.send_command(InlineCommand::CollapsePtyBlock(activity));
200 }
201 pub fn set_key_bindings(&self, bindings: HashMap<String, Vec<String>>) {
202 self.send_command(InlineCommand::SetKeyBindings { bindings });
203 }
204 pub fn force_redraw(&self) {
205 self.send_command(InlineCommand::ForceRedraw);
206 }
207 pub fn shutdown(&self) {
208 self.send_command(InlineCommand::Shutdown);
209 }
210 pub fn clear_screen(&self) {
211 self.send_command(InlineCommand::ClearScreen);
212 }
213 pub fn show_modal(&self, _title: String, _lines: Vec<String>, _secure_prompt: Option<SecurePromptConfig>) {}
214 pub fn show_list_modal(
215 &self,
216 _title: String,
217 _lines: Vec<String>,
218 _items: Vec<InlineListItem>,
219 _selected: Option<InlineListSelection>,
220 _search: Option<InlineListSearchConfig>,
221 ) {
222 }
223 pub fn show_list_modal_with_footer(
224 &self,
225 _title: String,
226 _lines: Vec<String>,
227 _items: Vec<InlineListItem>,
228 _selected: Option<InlineListSelection>,
229 _search: Option<InlineListSearchConfig>,
230 _footer_hint: Option<String>,
231 ) {
232 }
233 pub fn show_list_modal_with_status(
234 &self,
235 _title: String,
236 _lines: Vec<String>,
237 _items: Vec<InlineListItem>,
238 _selected: Option<InlineListSelection>,
239 _search: Option<InlineListSearchConfig>,
240 _footer_hint: Option<String>,
241 _status: Option<vtcode_commons::ui_protocol::InlineStatus>,
242 ) {
243 }
244 pub fn close_modal(&self) {
245 self.send_command(InlineCommand::CloseModal);
246 }
247 pub fn restore_input_draft(&self, input: SubmittedInput) {
248 self.send_command(InlineCommand::RestoreInputDraft(input));
249 }
250 pub fn set_image_input_enabled(&self, enabled: bool) {
251 self.send_command(InlineCommand::SetImageInputEnabled(enabled));
252 }
253 pub fn set_reasoning_stage(&self, stage: Option<String>) {
254 self.send_command(InlineCommand::SetReasoningStage(stage));
255 }
256 }
257
258 pub struct InlineSession {
260 pub handle: InlineHandle,
261 pub events: UnboundedReceiver<InlineEvent>,
262 }
263
264 impl InlineSession {
265 pub async fn next_event(&mut self) -> Option<InlineEvent> {
266 if let Some(event) = self.handle.take_deferred_event() {
267 return Some(event);
268 }
269 self.events.recv().await
270 }
271
272 pub async fn wait_for_exit(&mut self, _timeout: std::time::Duration) -> bool {
275 true
276 }
277
278 pub fn clone_inline_handle(&self) -> InlineHandle {
279 self.handle.clone()
280 }
281 }
282
283 #[derive(Debug, Clone, Default)]
285 pub struct SessionAppearanceConfig {
286 pub theme: String,
287 pub ui_mode: super::UiMode,
288 pub show_sidebar: bool,
289 pub min_content_width: u16,
290 pub min_navigation_width: u16,
291 pub navigation_width_percent: u8,
292 pub transcript_bottom_padding: u16,
298 pub dim_completed_todos: bool,
299 pub message_block_spacing: u8,
300 pub layout_mode: super::LayoutModeOverride,
301 pub reasoning_display_mode: super::ReasoningDisplayMode,
302 pub reasoning_visible_default: bool,
303 pub thinking_display: super::ThinkingBlockState,
304 pub vim_mode: bool,
305 pub screen_reader_mode: bool,
306 pub reduce_motion_mode: bool,
307 pub reduce_motion_keep_progress_animation: bool,
308 pub show_transcript_review_hints: bool,
309 pub show_transcript_review_shortcut_guide: bool,
310 pub show_transcript_review_close_button: bool,
311 pub diff_preview_mode: vtcode_commons::ui_protocol::DiffPreviewMode,
312 pub customization: (),
313 }
314
315 pub fn theme_from_styles(styles: &ThemeStyles) -> super::InlineTheme {
317 super::theme_from_color_fields(
318 styles.foreground,
319 styles.background,
320 styles.primary,
321 styles.secondary,
322 styles.tool,
323 styles.tool_detail,
324 styles.pty_output,
325 styles.error,
326 styles.warning,
327 )
328 }
329}
330
331#[cfg(not(feature = "tui"))]
332pub use headless::*;