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
125            // --- Logging ---
126            log_receiver: None,
127            log_lines: VecDeque::with_capacity(MAX_LOG_LINES),
128            log_cached_text: None,
129            log_evicted: false,
130            show_logs,
131
132            // --- Rendering ---
133            transcript_cache: None,
134            visible_lines_cache: None,
135            queued_inputs: Vec::with_capacity(4),
136            queue_overlay_cache: None,
137            queue_overlay_version: 0,
138            active_overlay: None,
139            overlay_queue: VecDeque::new(),
140            last_overlay_list_selection: None,
141            last_overlay_list_was_last: false,
142            header_rows: initial_header_rows,
143            line_revision_counter: 0,
144            first_dirty_line: None,
145            in_tool_code_fence: false,
146
147            // --- Prompt Suggestions ---
148            suggested_prompt_state: SuggestedPromptState::default(),
149
150            // --- Thinking Indicator ---
151            thinking_spinner: ThinkingSpinner::new(),
152            shimmer_state: ShimmerState::new(),
153
154            // --- Reverse Search ---
155            reverse_search_state: reverse_search::ReverseSearchState::new(),
156
157            // --- PTY Session Management ---
158            active_pty_sessions: None,
159
160            // --- Clipboard for yank/paste operations ---
161            clipboard: String::new(),
162            vim_state: VimState::new(vim_mode_enabled),
163
164            // --- Mouse Text Selection ---
165            mouse_selection: MouseSelectionState::new(),
166            mouse_drag_target: MouseDragTarget::None,
167
168            skip_confirmations: false,
169
170            // --- Performance Caching ---
171            header_lines_cache: None,
172            header_height_cache: hashbrown::HashMap::new(),
173            queued_inputs_preview_cache: None,
174
175            // --- Terminal Title ---
176            app_name,
177            workspace_root: None,
178            last_terminal_title: None,
179
180            // --- Streaming State ---
181            is_streaming_final_answer: false,
182        };
183        session.ensure_prompt_style_color();
184        session
185    }
186
187    pub(super) fn clear_thinking_spinner_if_active(&mut self, kind: InlineMessageKind) {
188        // Clear spinner when any substantive agent output arrives
189        if matches!(
190            kind,
191            InlineMessageKind::Agent
192                | InlineMessageKind::Policy
193                | InlineMessageKind::Tool
194                | InlineMessageKind::Error
195        ) && self.thinking_spinner.is_active
196        {
197            self.thinking_spinner.stop();
198            self.needs_redraw = true;
199        }
200    }
201}