Skip to main content

vtcode_ui/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.iter().any(|indicator| lower_content.contains(indicator))
29    }
30
31    pub fn new(theme: InlineTheme, placeholder: Option<String>, view_rows: u16) -> Self {
32        Self::new_with_logs(theme, placeholder, view_rows, true, None, "Agent TUI".to_string())
33    }
34
35    pub(crate) fn new_with_logs(
36        theme: InlineTheme,
37        placeholder: Option<String>,
38        view_rows: u16,
39        show_logs: bool,
40        appearance: Option<AppearanceConfig>,
41        app_name: String,
42    ) -> Self {
43        Self::new_with_options(theme, placeholder, view_rows, show_logs, appearance, app_name, None)
44    }
45
46    pub(crate) fn new_with_bindings(
47        theme: InlineTheme,
48        placeholder: Option<String>,
49        view_rows: u16,
50        show_logs: bool,
51        appearance: Option<AppearanceConfig>,
52        app_name: String,
53        bindings: BindingStore,
54    ) -> Self {
55        Self::new_with_options(theme, placeholder, view_rows, show_logs, appearance, app_name, Some(bindings))
56    }
57
58    fn new_with_options(
59        theme: InlineTheme,
60        placeholder: Option<String>,
61        view_rows: u16,
62        show_logs: bool,
63        appearance: Option<AppearanceConfig>,
64        app_name: String,
65        bindings: Option<BindingStore>,
66    ) -> Self {
67        let resolved_rows = view_rows.max(2);
68        let initial_header_rows = ui::INLINE_HEADER_HEIGHT;
69        let reserved_rows = initial_header_rows + Self::input_block_height_for_lines(1);
70        let initial_transcript_rows = resolved_rows.saturating_sub(reserved_rows).max(1);
71
72        let appearance = appearance.unwrap_or_default();
73        let vim_mode_enabled = appearance.vim_mode;
74
75        let mut session = Self {
76            // --- Managers (Phase 2) ---
77            input_manager: InputManager::new(),
78            scroll_manager: ScrollManager::new(initial_transcript_rows),
79            user_scrolled: false,
80
81            // --- Message Management ---
82            lines: Vec::with_capacity(64),
83            collapsed_pastes: Vec::new(),
84            thinking_runs: ThinkingRunIndex::default(),
85            styles: SessionStyles::new(theme.clone()),
86            theme,
87            appearance,
88            auto_color_scheme: false,
89            header_context: InlineHeaderContext::default(),
90            labels: MessageLabels::default(),
91
92            // --- Prompt/Input Display ---
93            prompt_prefix: USER_PREFIX.to_string(),
94            prompt_style: InlineTextStyle::default(),
95            placeholder,
96            placeholder_style: None,
97            input_status_left: None,
98            footer_context_status: None,
99            footer_context_right: None,
100            footer_context_configured: false,
101            input_status_right: None,
102            copy_notification_until: None,
103            copy_notification_failed: false,
104            copy_notification_chars: 0,
105            drag_auto_scroll: None,
106            input_compact_mode: false,
107
108            // --- UI State ---
109            navigation_state: ListState::default(), // Kept for backward compatibility
110            input_enabled: true,
111            program_status: super::super::program_status::ProgramStatus::default(),
112            activity_state: ActivityState::Idle,
113            image_input_enabled: false,
114            cursor_visible: true,
115            render_state: RenderState::new(),
116            transcript_clear_required: true,
117            evicted_message_count: 0,
118            pending_new_messages: 0,
119            last_change_line_idx: None,
120            should_exit: false,
121            last_interrupt_press: None,
122            last_escape_press: None,
123            scroll_cursor_steady_until: None,
124            last_shimmer_active: false,
125            view_rows: resolved_rows,
126            input_height: Self::input_block_height_for_lines(1),
127            transcript_rows: initial_transcript_rows,
128            transcript_width: 0,
129            transcript_view_top: 0,
130            areas: SessionAreas::default(),
131            sticky_prompt_target: None,
132            sticky_prompt_preview_cache: None,
133            leading_user_prompt_truncated: false,
134            transcript_file_link_targets: Vec::new(),
135            modal_link_targets: Vec::new(),
136            hovered_transcript_file_link: None,
137            last_mouse_position: None,
138            last_link_open: None,
139            pending_link_open: None,
140            held_key_modifiers: KeyModifiers::empty(),
141
142            // --- Logging ---
143            log_receiver: None,
144            log_lines: VecDeque::with_capacity(MAX_LOG_LINES),
145            log_cached_text: None,
146            log_evicted: false,
147            show_logs,
148
149            // --- Rendering ---
150            transcript_cache: None,
151            visible_lines_cache: None,
152            queued_inputs: Vec::with_capacity(4),
153            local_agents: Vec::new(),
154            local_agents_drawer_visible: false,
155            subprocess_entries: Vec::new(),
156            subagent_preview: None,
157            queue_overlay_cache: None,
158            queue_overlay_version: 0,
159            active_overlay: None,
160            overlay_queue: VecDeque::new(),
161            last_overlay_list_selection: None,
162            last_overlay_list_was_last: false,
163            header_rows: initial_header_rows,
164            line_revision_counter: 0,
165            transcript_presentation_revision: 0,
166            first_dirty_line: None,
167            in_tool_code_fence: false,
168
169            // --- Prompt Suggestions ---
170            suggested_prompt_state: SuggestedPromptState::default(),
171            inline_prompt_suggestion: InlinePromptSuggestionState::default(),
172
173            // --- Thinking Indicator ---
174            thinking_spinner: ThinkingSpinner::new(),
175            shimmer_state: ShimmerState::new(),
176            progress: progress::TransientProgress::default(),
177
178            // --- Reverse Search ---
179            reverse_search_state: reverse_search::ReverseSearchState::new(),
180
181            // --- PTY Session Management ---
182            active_pty_sessions: None,
183
184            // --- Background Activity ---
185            background_activity_count: 0,
186            background_finished_count: 0,
187            background_indicator_hits: Vec::new(),
188
189            // --- Keybinding store ---
190            bindings: bindings.unwrap_or_default(),
191
192            // --- Clipboard for yank/paste operations ---
193            clipboard: String::new(),
194            vim_state: VimState::new(vim_mode_enabled),
195
196            // --- Mouse Text Selection ---
197            mouse_selection: MouseSelectionState::new(),
198            mouse_drag_target: MouseDragTarget::None,
199            fullscreen: FullscreenSessionState::default(),
200
201            // --- Performance Caching ---
202            header_lines_cache: None,
203            header_block_title_cache: None,
204            header_height_cache: hashbrown::HashMap::new(),
205            queued_inputs_preview_cache: None,
206            subprocess_entries_preview_cache: None,
207            input_render_cache: None,
208
209            // --- Terminal Title ---
210            app_name,
211            workspace_root: None,
212            terminal_title_items: None,
213            terminal_title_thread_label: None,
214            terminal_title_git_branch: None,
215            terminal_title_task_progress: None,
216            last_terminal_title: None,
217
218            // --- Streaming State ---
219            is_streaming_final_answer: false,
220        };
221        session.ensure_prompt_style_color();
222        session
223    }
224
225    pub(super) fn clear_thinking_spinner_if_active(&mut self, kind: InlineMessageKind) {
226        // Clear spinner when any substantive agent output arrives
227        if matches!(
228            kind,
229            InlineMessageKind::Agent | InlineMessageKind::Policy | InlineMessageKind::Tool | InlineMessageKind::Error
230        ) && self.thinking_spinner.is_active
231        {
232            self.thinking_spinner.stop();
233            self.mark_visual_dirty();
234        }
235    }
236}