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.request_transcript_clear();
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.request_transcript_clear();
64            }
65            InlineCommand::Inline { kind, segment } => {
66                self.clear_thinking_spinner_if_active(kind);
67                self.append_inline(kind, segment);
68                self.request_transcript_clear();
69            }
70            InlineCommand::ReplaceLast {
71                count,
72                kind,
73                lines,
74                link_ranges,
75            } => {
76                self.clear_thinking_spinner_if_active(kind);
77                self.replace_last(count, kind, lines, link_ranges);
78                self.request_transcript_clear();
79            }
80            InlineCommand::SetPrompt { prefix, style } => {
81                self.prompt_prefix = prefix;
82                self.prompt_style = style;
83                self.ensure_prompt_style_color();
84            }
85            InlineCommand::SetPlaceholder { hint, style } => {
86                self.placeholder = hint;
87                self.placeholder_style = style;
88            }
89            InlineCommand::SetMessageLabels { agent, user } => {
90                self.labels.agent = agent.filter(|label| !label.is_empty());
91                self.labels.user = user.filter(|label| !label.is_empty());
92                self.invalidate_transcript_cache();
93                self.invalidate_scroll_metrics();
94            }
95            InlineCommand::SetHeaderContext { context } => {
96                let mut next_context = *context;
97                next_context.editing_mode = self.header_context.editing_mode;
98                next_context.autonomous_mode = self.header_context.autonomous_mode;
99                next_context.reasoning_stage = self.header_context.reasoning_stage.clone();
100                next_context.primary_agent = self.header_context.primary_agent.clone();
101                self.header_context = next_context;
102                self.invalidate_header_cache();
103            }
104            InlineCommand::SetInputStatus { left, right } => {
105                self.input_status_left = left;
106                self.input_status_right = right;
107                if self.thinking_spinner.is_active {
108                    self.thinking_spinner.stop();
109                }
110                self.needs_redraw = true;
111            }
112            InlineCommand::SetTerminalTitleItems { items } => {
113                self.terminal_title_items = items;
114                self.needs_redraw = true;
115            }
116            InlineCommand::SetTerminalTitleThreadLabel { label } => {
117                self.terminal_title_thread_label = label.filter(|value| !value.trim().is_empty());
118                self.needs_redraw = true;
119            }
120            InlineCommand::SetTerminalTitleGitBranch { branch } => {
121                self.terminal_title_git_branch = branch.filter(|value| !value.trim().is_empty());
122                self.needs_redraw = true;
123            }
124            InlineCommand::SetTheme { theme } => {
125                let previous_theme = self.theme.clone();
126                self.theme = theme.clone();
127                self.styles.set_theme(theme);
128                self.retint_lines_for_theme_change(&previous_theme);
129                self.ensure_prompt_style_color();
130                self.invalidate_transcript_cache();
131            }
132            InlineCommand::SetAppearance { appearance } => {
133                self.appearance = appearance;
134                self.invalidate_header_cache();
135                self.invalidate_transcript_cache();
136                self.invalidate_scroll_metrics();
137            }
138            InlineCommand::SetVimModeEnabled(enabled) => {
139                self.vim_state.set_enabled(enabled);
140                self.needs_redraw = true;
141            }
142            InlineCommand::SetQueuedInputs { entries } => {
143                self.set_queued_inputs_entries(entries);
144                self.mark_dirty();
145            }
146            InlineCommand::SetSubprocessEntries { entries } => {
147                self.subprocess_entries = entries;
148                self.invalidate_sidebar_cache();
149            }
150            InlineCommand::SetSubagentPreview { text } => {
151                self.subagent_preview = text.filter(|value| !value.trim().is_empty());
152                self.invalidate_sidebar_cache();
153            }
154            InlineCommand::SetPrimaryAgent { name } => {
155                self.header_context.primary_agent = name.filter(|value| !value.trim().is_empty());
156                self.invalidate_header_cache();
157            }
158            InlineCommand::SetCursorVisible(value) => {
159                self.cursor_visible = value;
160            }
161            InlineCommand::SetInputEnabled(value) => {
162                self.input_enabled = value;
163            }
164            InlineCommand::SetInput(content) => {
165                // Check if the content appears to be an error message
166                // If it looks like an error, redirect to transcript instead
167                if Self::is_error_content(&content) {
168                    // Add error to transcript instead of input field
169                    crate::utils::transcript::display_error(&content);
170                } else {
171                    self.clear_suggested_prompt_state();
172                    self.clear_inline_prompt_suggestion();
173                    self.input_manager.set_content(content);
174                    self.input_compact_mode = self.input_compact_placeholder().is_some();
175                    self.scroll_manager.set_offset(0);
176                }
177            }
178            InlineCommand::ApplySuggestedPrompt(content) => {
179                self.apply_suggested_prompt(content);
180                self.scroll_manager.set_offset(0);
181            }
182            InlineCommand::SetInlinePromptSuggestion {
183                suggestion,
184                llm_generated,
185            } => {
186                self.set_inline_prompt_suggestion(suggestion, llm_generated);
187            }
188            InlineCommand::ClearInlinePromptSuggestion => {
189                self.clear_inline_prompt_suggestion();
190            }
191            InlineCommand::ClearInput => {
192                command::clear_input(self);
193            }
194            InlineCommand::ForceRedraw => {
195                self.mark_dirty();
196            }
197            InlineCommand::ShowOverlay { request } => {
198                self.clear_inline_prompt_suggestion();
199                self.show_overlay(*request);
200            }
201            InlineCommand::CloseOverlay => {
202                self.close_overlay();
203            }
204            InlineCommand::ClearScreen => {
205                self.clear_screen();
206            }
207            InlineCommand::SuspendEventLoop
208            | InlineCommand::ResumeEventLoop
209            | InlineCommand::ClearInputQueue
210            | InlineCommand::StopEventStream
211            | InlineCommand::StartEventStream => {
212                // Handled by drive_terminal
213            }
214            InlineCommand::SetEditingMode(mode) => {
215                self.clear_inline_prompt_suggestion();
216                self.header_context.editing_mode = mode;
217                self.invalidate_header_cache();
218            }
219            InlineCommand::SetAutonomousMode(enabled) => {
220                self.header_context.autonomous_mode = enabled;
221                self.invalidate_header_cache();
222            }
223            InlineCommand::SetSkipConfirmations(skip) => {
224                self.skip_confirmations = skip;
225                if skip {
226                    self.close_overlay();
227                }
228            }
229            InlineCommand::Shutdown => {
230                self.request_exit();
231            }
232            InlineCommand::SetReasoningStage(stage) => {
233                self.header_context.reasoning_stage = stage;
234                self.invalidate_header_cache();
235            }
236        }
237        self.needs_redraw = true;
238    }
239}