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
151            // --- Thinking Indicator ---
152            thinking_spinner: ThinkingSpinner::new(),
153            shimmer_state: ShimmerState::new(),
154
155            // --- Reverse Search ---
156            reverse_search_state: reverse_search::ReverseSearchState::new(),
157
158            // --- PTY Session Management ---
159            active_pty_sessions: None,
160
161            // --- Clipboard for yank/paste operations ---
162            clipboard: String::new(),
163            vim_state: VimState::new(vim_mode_enabled),
164
165            // --- Mouse Text Selection ---
166            mouse_selection: MouseSelectionState::new(),
167            mouse_drag_target: MouseDragTarget::None,
168
169            skip_confirmations: false,
170
171            // --- Performance Caching ---
172            header_lines_cache: None,
173            header_height_cache: hashbrown::HashMap::new(),
174            queued_inputs_preview_cache: None,
175
176            // --- Terminal Title ---
177            app_name,
178            workspace_root: None,
179            last_terminal_title: None,
180
181            // --- Streaming State ---
182            is_streaming_final_answer: false,
183        };
184        session.ensure_prompt_style_color();
185        session
186    }
187
188    pub(super) fn clear_thinking_spinner_if_active(&mut self, kind: InlineMessageKind) {
189        // Clear spinner when any substantive agent output arrives
190        if matches!(
191            kind,
192            InlineMessageKind::Agent
193                | InlineMessageKind::Policy
194                | InlineMessageKind::Tool
195                | InlineMessageKind::Error
196        ) && self.thinking_spinner.is_active
197        {
198            self.thinking_spinner.stop();
199            self.needs_redraw = true;
200        }
201    }
202}