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        )
60    }
61
62    fn new_with_options(
63        theme: InlineTheme,
64        placeholder: Option<String>,
65        view_rows: u16,
66        show_logs: bool,
67        appearance: Option<AppearanceConfig>,
68        app_name: String,
69    ) -> Self {
70        let resolved_rows = view_rows.max(2);
71        let initial_header_rows = ui::INLINE_HEADER_HEIGHT;
72        let reserved_rows = initial_header_rows + Self::input_block_height_for_lines(1);
73        let initial_transcript_rows = resolved_rows.saturating_sub(reserved_rows).max(1);
74
75        let appearance = appearance.unwrap_or_default();
76        let vim_mode_enabled = appearance.vim_mode;
77
78        let mut session = Self {
79            // --- Managers (Phase 2) ---
80            input_manager: InputManager::new(),
81            scroll_manager: ScrollManager::new(initial_transcript_rows),
82            user_scrolled: false,
83
84            // --- Message Management ---
85            lines: Vec::with_capacity(64),
86            collapsed_pastes: Vec::new(),
87            styles: SessionStyles::new(theme.clone()),
88            theme,
89            appearance,
90            header_context: InlineHeaderContext::default(),
91            labels: MessageLabels::default(),
92
93            // --- Prompt/Input Display ---
94            prompt_prefix: USER_PREFIX.to_string(),
95            prompt_style: InlineTextStyle::default(),
96            placeholder,
97            placeholder_style: None,
98            input_status_left: None,
99            input_status_right: None,
100            input_compact_mode: false,
101
102            // --- UI State ---
103            navigation_state: ListState::default(), // Kept for backward compatibility
104            input_enabled: true,
105            cursor_visible: true,
106            needs_redraw: true,
107            needs_full_clear: false,
108            transcript_content_changed: true,
109            should_exit: false,
110            scroll_cursor_steady_until: None,
111            last_shimmer_active: false,
112            view_rows: resolved_rows,
113            input_height: Self::input_block_height_for_lines(1),
114            transcript_rows: initial_transcript_rows,
115            transcript_width: 0,
116            transcript_view_top: 0,
117            transcript_area: None,
118            input_area: None,
119            bottom_panel_area: None,
120            modal_list_area: None,
121            transcript_file_link_targets: Vec::new(),
122            hovered_transcript_file_link: None,
123            last_mouse_position: None,
124            held_key_modifiers: KeyModifiers::empty(),
125
126            // --- Logging ---
127            log_receiver: None,
128            log_lines: VecDeque::with_capacity(MAX_LOG_LINES),
129            log_cached_text: None,
130            log_evicted: false,
131            show_logs,
132
133            // --- Rendering ---
134            transcript_cache: None,
135            visible_lines_cache: None,
136            queued_inputs: Vec::with_capacity(4),
137            queue_overlay_cache: None,
138            queue_overlay_version: 0,
139            active_overlay: None,
140            overlay_queue: VecDeque::new(),
141            last_overlay_list_selection: None,
142            last_overlay_list_was_last: false,
143            header_rows: initial_header_rows,
144            line_revision_counter: 0,
145            first_dirty_line: None,
146            in_tool_code_fence: false,
147
148            // --- Prompt Suggestions ---
149            suggested_prompt_state: SuggestedPromptState::default(),
150            inline_prompt_suggestion: InlinePromptSuggestionState::default(),
151
152            // --- Thinking Indicator ---
153            thinking_spinner: ThinkingSpinner::new(),
154            shimmer_state: ShimmerState::new(),
155
156            // --- Reverse Search ---
157            reverse_search_state: reverse_search::ReverseSearchState::new(),
158
159            // --- PTY Session Management ---
160            active_pty_sessions: None,
161
162            // --- Clipboard for yank/paste operations ---
163            clipboard: String::new(),
164            vim_state: VimState::new(vim_mode_enabled),
165
166            // --- Mouse Text Selection ---
167            mouse_selection: MouseSelectionState::new(),
168            mouse_drag_target: MouseDragTarget::None,
169
170            skip_confirmations: false,
171
172            // --- Performance Caching ---
173            header_lines_cache: None,
174            header_height_cache: hashbrown::HashMap::new(),
175            queued_inputs_preview_cache: None,
176
177            // --- Terminal Title ---
178            app_name,
179            workspace_root: None,
180            last_terminal_title: None,
181
182            // --- Streaming State ---
183            is_streaming_final_answer: false,
184        };
185        session.ensure_prompt_style_color();
186        session
187    }
188
189    pub(super) fn clear_thinking_spinner_if_active(&mut self, kind: InlineMessageKind) {
190        // Clear spinner when any substantive agent output arrives
191        if matches!(
192            kind,
193            InlineMessageKind::Agent
194                | InlineMessageKind::Policy
195                | InlineMessageKind::Tool
196                | InlineMessageKind::Error
197        ) && self.thinking_spinner.is_active
198        {
199            self.thinking_spinner.stop();
200            self.needs_redraw = true;
201        }
202    }
203}