Skip to main content

vtcode_tui/core_tui/session/
impl_init.rs

1use super::*;
2
3impl Session {
4    pub(super) fn is_error_content(content: &str) -> bool {
5        // Check if message contains common error indicators
6        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            // --- Managers (Phase 2) ---
102            input_manager: InputManager::new(),
103            scroll_manager: ScrollManager::new(initial_transcript_rows),
104            user_scrolled: false,
105
106            // --- Message Management ---
107            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/Input Display ---
116            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            // --- UI State ---
126            navigation_state: ListState::default(), // Kept for backward compatibility
127            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            // --- Logging ---
154            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            // --- Rendering ---
161            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            // --- Prompt Suggestions ---
180            suggested_prompt_state: SuggestedPromptState::default(),
181            inline_prompt_suggestion: InlinePromptSuggestionState::default(),
182
183            // --- Thinking Indicator ---
184            thinking_spinner: ThinkingSpinner::new(),
185            shimmer_state: ShimmerState::new(),
186
187            // --- Reverse Search ---
188            reverse_search_state: reverse_search::ReverseSearchState::new(),
189
190            // --- PTY Session Management ---
191            active_pty_sessions: None,
192
193            // --- Keybinding store ---
194            bindings: bindings.unwrap_or_default(),
195
196            // --- Clipboard for yank/paste operations ---
197            clipboard: String::new(),
198            vim_state: VimState::new(vim_mode_enabled),
199
200            // --- Mouse Text Selection ---
201            mouse_selection: MouseSelectionState::new(),
202            mouse_drag_target: MouseDragTarget::None,
203            fullscreen: FullscreenSessionState::default(),
204
205            skip_confirmations: false,
206
207            // --- Performance Caching ---
208            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            // --- Terminal Title ---
214            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            // --- Streaming State ---
223            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        // Clear spinner when any substantive agent output arrives
231        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}