Skip to main content

vtcode_tui/core_tui/session/
impl_input.rs

1use super::*;
2
3impl Session {
4    pub fn cursor(&self) -> usize {
5        self.input_manager.cursor()
6    }
7
8    pub fn set_input(&mut self, text: impl Into<String>) {
9        self.input_manager.set_content(text.into());
10        self.input_compact_mode = self.input_compact_placeholder().is_some();
11        self.check_file_reference_trigger();
12        self.mark_dirty();
13    }
14
15    pub fn set_cursor(&mut self, pos: usize) {
16        self.input_manager.set_cursor(pos);
17        self.mark_dirty();
18    }
19
20    pub fn process_key(&mut self, key: KeyEvent) -> Option<InlineEvent> {
21        events::process_key(self, key)
22    }
23
24    pub fn handle_command(&mut self, command: InlineCommand) {
25        // Track streaming state: set when agent starts responding
26        if matches!(
27            &command,
28            InlineCommand::AppendLine { kind: InlineMessageKind::Agent, segments }
29                if !segments.is_empty()
30        ) || matches!(
31            &command,
32            InlineCommand::AppendPastedMessage { kind: InlineMessageKind::Agent, text, .. }
33                if !text.is_empty()
34        ) || matches!(
35            &command,
36            InlineCommand::Inline { kind: InlineMessageKind::Agent, segment }
37                if !segment.text.is_empty()
38        ) {
39            self.is_streaming_final_answer = true;
40        }
41
42        // Clear streaming state on turn completion (status cleared)
43        if let InlineCommand::SetInputStatus { left, right } = &command
44            && self.is_streaming_final_answer
45            && left.is_none()
46            && right.is_none()
47        {
48            self.is_streaming_final_answer = false;
49        }
50
51        match command {
52            InlineCommand::AppendLine { kind, segments } => {
53                self.clear_thinking_spinner_if_active(kind);
54                self.push_line(kind, segments);
55                self.transcript_content_changed = true;
56            }
57            InlineCommand::AppendPastedMessage {
58                kind,
59                text,
60                line_count,
61            } => {
62                self.clear_thinking_spinner_if_active(kind);
63                self.append_pasted_message(kind, text, line_count);
64                self.transcript_content_changed = true;
65            }
66            InlineCommand::Inline { kind, segment } => {
67                self.clear_thinking_spinner_if_active(kind);
68                self.append_inline(kind, segment);
69                self.transcript_content_changed = true;
70            }
71            InlineCommand::ReplaceLast { count, kind, lines } => {
72                self.clear_thinking_spinner_if_active(kind);
73                self.replace_last(count, kind, lines);
74                self.transcript_content_changed = true;
75            }
76            InlineCommand::SetPrompt { prefix, style } => {
77                self.prompt_prefix = prefix;
78                self.prompt_style = style;
79                self.ensure_prompt_style_color();
80            }
81            InlineCommand::SetPlaceholder { hint, style } => {
82                self.placeholder = hint;
83                self.placeholder_style = style;
84            }
85            InlineCommand::SetMessageLabels { agent, user } => {
86                self.labels.agent = agent.filter(|label| !label.is_empty());
87                self.labels.user = user.filter(|label| !label.is_empty());
88                self.invalidate_scroll_metrics();
89            }
90            InlineCommand::SetHeaderContext { context } => {
91                self.header_context = context;
92                self.needs_redraw = true;
93            }
94            InlineCommand::SetInputStatus { left, right } => {
95                self.input_status_left = left;
96                self.input_status_right = right;
97                if self.thinking_spinner.is_active {
98                    self.thinking_spinner.stop();
99                }
100                self.needs_redraw = true;
101            }
102            InlineCommand::SetTheme { theme } => {
103                let previous_theme = self.theme.clone();
104                self.theme = theme.clone();
105                self.styles.set_theme(theme);
106                self.retint_lines_for_theme_change(&previous_theme);
107                self.ensure_prompt_style_color();
108                self.invalidate_transcript_cache();
109            }
110            InlineCommand::SetAppearance { appearance } => {
111                self.appearance = appearance;
112                self.invalidate_transcript_cache();
113                self.invalidate_scroll_metrics();
114            }
115            InlineCommand::SetQueuedInputs { entries } => {
116                self.set_queued_inputs_entries(entries);
117                self.mark_dirty();
118            }
119            InlineCommand::SetCursorVisible(value) => {
120                self.cursor_visible = value;
121            }
122            InlineCommand::SetInputEnabled(value) => {
123                self.input_enabled = value;
124                slash::update_slash_suggestions(self);
125            }
126            InlineCommand::SetInput(content) => {
127                // Check if the content appears to be an error message
128                // If it looks like an error, redirect to transcript instead
129                if Self::is_error_content(&content) {
130                    // Add error to transcript instead of input field
131                    crate::utils::transcript::display_error(&content);
132                } else {
133                    self.input_manager.set_content(content);
134                    self.input_compact_mode = self.input_compact_placeholder().is_some();
135                    self.scroll_manager.set_offset(0);
136                    slash::update_slash_suggestions(self);
137                    self.check_file_reference_trigger();
138                }
139            }
140            InlineCommand::ClearInput => {
141                command::clear_input(self);
142            }
143            InlineCommand::ForceRedraw => {
144                self.mark_dirty();
145            }
146            InlineCommand::ShowModal {
147                title,
148                lines,
149                secure_prompt,
150            } => {
151                self.show_modal(title, lines, secure_prompt);
152            }
153            InlineCommand::ShowListModal {
154                title,
155                lines,
156                items,
157                selected,
158                search,
159            } => {
160                self.show_list_modal(title, lines, items, selected, search);
161            }
162            InlineCommand::ShowWizardModal {
163                title,
164                steps,
165                current_step,
166                search,
167                mode,
168            } => {
169                self.show_wizard_modal(title, steps, current_step, search, mode);
170            }
171            InlineCommand::CloseModal => {
172                self.close_modal();
173            }
174            InlineCommand::LoadFilePalette { files, workspace } => {
175                self.load_file_palette(files, workspace);
176            }
177            InlineCommand::OpenHistoryPicker => {
178                events::open_history_picker(self);
179            }
180            InlineCommand::ClearScreen => {
181                self.clear_screen();
182            }
183            InlineCommand::SuspendEventLoop
184            | InlineCommand::ResumeEventLoop
185            | InlineCommand::ClearInputQueue => {
186                // Handled by drive_terminal
187            }
188            InlineCommand::SetEditingMode(mode) => {
189                self.header_context.editing_mode = mode;
190                self.needs_redraw = true;
191            }
192            InlineCommand::SetAutonomousMode(enabled) => {
193                self.header_context.autonomous_mode = enabled;
194                self.needs_redraw = true;
195            }
196            InlineCommand::ShowPlanConfirmation { plan } => {
197                command::show_plan_confirmation_modal(self, *plan);
198            }
199            InlineCommand::ShowDiffPreview {
200                file_path,
201                before,
202                after,
203                hunks,
204                current_hunk,
205            } => {
206                command::show_diff_preview(self, file_path, before, after, hunks, current_hunk);
207            }
208            InlineCommand::SetSkipConfirmations(skip) => {
209                self.skip_confirmations = skip;
210                if skip {
211                    self.close_modal();
212                }
213            }
214            InlineCommand::Shutdown => {
215                self.request_exit();
216            }
217            InlineCommand::SetReasoningStage(stage) => {
218                self.header_context.reasoning_stage = stage;
219                self.invalidate_header_cache();
220            }
221        }
222        self.needs_redraw = true;
223    }
224}