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