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            header_rows: initial_header_rows,
146            line_revision_counter: 0,
147            first_dirty_line: None,
148            in_tool_code_fence: false,
149
150            // --- Palette Management ---
151            file_palette: None,
152            file_palette_active: false,
153            inline_lists_visible: true,
154            show_task_panel: false,
155            task_panel_lines: Vec::new(),
156            suggested_prompt_state: SuggestedPromptState::default(),
157
158            // --- Thinking Indicator ---
159            thinking_spinner: ThinkingSpinner::new(),
160            shimmer_state: ShimmerState::new(),
161
162            // --- Reverse Search ---
163            reverse_search_state: reverse_search::ReverseSearchState::new(),
164
165            // --- History Picker (Ctrl+R fuzzy search) ---
166            history_picker_state: HistoryPickerState::new(),
167
168            // --- PTY Session Management ---
169            active_pty_sessions: None,
170
171            // --- Clipboard for yank/paste operations ---
172            clipboard: String::new(),
173            vim_state: VimState::new(vim_mode_enabled),
174
175            // --- Mouse Text Selection ---
176            mouse_selection: MouseSelectionState::new(),
177            mouse_drag_target: MouseDragTarget::None,
178
179            skip_confirmations: false,
180
181            // --- Performance Caching ---
182            header_lines_cache: None,
183            header_height_cache: hashbrown::HashMap::new(),
184            queued_inputs_preview_cache: None,
185
186            // --- Terminal Title ---
187            app_name,
188            workspace_root: None,
189            last_terminal_title: None,
190
191            // --- Streaming State ---
192            is_streaming_final_answer: false,
193        };
194        session.ensure_prompt_style_color();
195        session
196    }
197
198    pub(super) fn clear_thinking_spinner_if_active(&mut self, kind: InlineMessageKind) {
199        // Clear spinner when any substantive agent output arrives
200        if matches!(
201            kind,
202            InlineMessageKind::Agent
203                | InlineMessageKind::Policy
204                | InlineMessageKind::Tool
205                | InlineMessageKind::Error
206        ) && self.thinking_spinner.is_active
207        {
208            self.thinking_spinner.stop();
209            self.needs_redraw = true;
210        }
211    }
212}