vtcode_tui/core_tui/session/
impl_init.rs1use super::*;
2
3impl Session {
4 pub(super) fn is_error_content(content: &str) -> bool {
5 let lower_content = content.to_lowercase();
7 let error_indicators = [
8 "error:",
9 "error ",
10 "error\n",
11 "failed",
12 "failure",
13 "exception",
14 "invalid",
15 "not found",
16 "couldn't",
17 "can't",
18 "cannot",
19 "denied",
20 "forbidden",
21 "unauthorized",
22 "timeout",
23 "connection refused",
24 "no such",
25 "does not exist",
26 ];
27
28 error_indicators
29 .iter()
30 .any(|indicator| lower_content.contains(indicator))
31 }
32
33 pub fn new(theme: InlineTheme, placeholder: Option<String>, view_rows: u16) -> Self {
34 Self::new_with_logs(
35 theme,
36 placeholder,
37 view_rows,
38 true,
39 None,
40 "Agent TUI".to_string(),
41 )
42 }
43
44 pub fn new_with_logs(
45 theme: InlineTheme,
46 placeholder: Option<String>,
47 view_rows: u16,
48 show_logs: bool,
49 appearance: Option<AppearanceConfig>,
50 app_name: String,
51 ) -> Self {
52 Self::new_with_options(
53 theme,
54 placeholder,
55 view_rows,
56 show_logs,
57 appearance,
58 app_name,
59 None,
60 )
61 }
62
63 pub fn new_with_bindings(
64 theme: InlineTheme,
65 placeholder: Option<String>,
66 view_rows: u16,
67 show_logs: bool,
68 appearance: Option<AppearanceConfig>,
69 app_name: String,
70 bindings: BindingStore,
71 ) -> Self {
72 Self::new_with_options(
73 theme,
74 placeholder,
75 view_rows,
76 show_logs,
77 appearance,
78 app_name,
79 Some(bindings),
80 )
81 }
82
83 fn new_with_options(
84 theme: InlineTheme,
85 placeholder: Option<String>,
86 view_rows: u16,
87 show_logs: bool,
88 appearance: Option<AppearanceConfig>,
89 app_name: String,
90 bindings: Option<BindingStore>,
91 ) -> Self {
92 let resolved_rows = view_rows.max(2);
93 let initial_header_rows = ui::INLINE_HEADER_HEIGHT;
94 let reserved_rows = initial_header_rows + Self::input_block_height_for_lines(1);
95 let initial_transcript_rows = resolved_rows.saturating_sub(reserved_rows).max(1);
96
97 let appearance = appearance.unwrap_or_default();
98 let vim_mode_enabled = appearance.vim_mode;
99
100 let mut session = Self {
101 input_manager: InputManager::new(),
103 scroll_manager: ScrollManager::new(initial_transcript_rows),
104 user_scrolled: false,
105
106 lines: Vec::with_capacity(64),
108 collapsed_pastes: Vec::new(),
109 styles: SessionStyles::new(theme.clone()),
110 theme,
111 appearance,
112 header_context: InlineHeaderContext::default(),
113 labels: MessageLabels::default(),
114
115 prompt_prefix: USER_PREFIX.to_string(),
117 prompt_style: InlineTextStyle::default(),
118 placeholder,
119 placeholder_style: None,
120 input_status_left: None,
121 input_status_right: None,
122 copy_notification_until: None,
123 input_compact_mode: false,
124
125 navigation_state: ListState::default(), input_enabled: true,
128 cursor_visible: true,
129 needs_redraw: true,
130 needs_full_clear: false,
131 transcript_clear_required: true,
132 should_exit: false,
133 scroll_cursor_steady_until: None,
134 last_shimmer_active: false,
135 view_rows: resolved_rows,
136 input_height: Self::input_block_height_for_lines(1),
137 transcript_rows: initial_transcript_rows,
138 transcript_width: 0,
139 transcript_view_top: 0,
140 transcript_area: None,
141 input_area: None,
142 bottom_panel_area: None,
143 modal_list_area: None,
144 modal_text_areas: Vec::new(),
145 transcript_file_link_targets: Vec::new(),
146 modal_link_targets: Vec::new(),
147 hovered_transcript_file_link: None,
148 last_mouse_position: None,
149 last_link_open: None,
150 pending_link_open: None,
151 held_key_modifiers: KeyModifiers::empty(),
152
153 log_receiver: None,
155 log_lines: VecDeque::with_capacity(MAX_LOG_LINES),
156 log_cached_text: None,
157 log_evicted: false,
158 show_logs,
159
160 transcript_cache: None,
162 visible_lines_cache: None,
163 queued_inputs: Vec::with_capacity(4),
164 local_agents: Vec::new(),
165 local_agents_drawer_visible: false,
166 subprocess_entries: Vec::new(),
167 subagent_preview: None,
168 queue_overlay_cache: None,
169 queue_overlay_version: 0,
170 active_overlay: None,
171 overlay_queue: VecDeque::new(),
172 last_overlay_list_selection: None,
173 last_overlay_list_was_last: false,
174 header_rows: initial_header_rows,
175 line_revision_counter: 0,
176 first_dirty_line: None,
177 in_tool_code_fence: false,
178
179 suggested_prompt_state: SuggestedPromptState::default(),
181 inline_prompt_suggestion: InlinePromptSuggestionState::default(),
182
183 thinking_spinner: ThinkingSpinner::new(),
185 shimmer_state: ShimmerState::new(),
186
187 reverse_search_state: reverse_search::ReverseSearchState::new(),
189
190 active_pty_sessions: None,
192
193 bindings: bindings.unwrap_or_default(),
195
196 clipboard: String::new(),
198 vim_state: VimState::new(vim_mode_enabled),
199
200 mouse_selection: MouseSelectionState::new(),
202 mouse_drag_target: MouseDragTarget::None,
203 fullscreen: FullscreenSessionState::default(),
204
205 skip_confirmations: false,
206
207 header_lines_cache: None,
209 header_height_cache: hashbrown::HashMap::new(),
210 queued_inputs_preview_cache: None,
211 subprocess_entries_preview_cache: None,
212
213 app_name,
215 workspace_root: None,
216 terminal_title_items: None,
217 terminal_title_thread_label: None,
218 terminal_title_git_branch: None,
219 terminal_title_task_progress: None,
220 last_terminal_title: None,
221
222 is_streaming_final_answer: false,
224 };
225 session.ensure_prompt_style_color();
226 session
227 }
228
229 pub(super) fn clear_thinking_spinner_if_active(&mut self, kind: InlineMessageKind) {
230 if matches!(
232 kind,
233 InlineMessageKind::Agent
234 | InlineMessageKind::Policy
235 | InlineMessageKind::Tool
236 | InlineMessageKind::Error
237 ) && self.thinking_spinner.is_active
238 {
239 self.thinking_spinner.stop();
240 self.needs_redraw = true;
241 }
242 }
243}