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