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
81        let mut session = Self {
82            // --- Managers (Phase 2) ---
83            input_manager: InputManager::new(),
84            scroll_manager: ScrollManager::new(initial_transcript_rows),
85            user_scrolled: false,
86
87            // --- Message Management ---
88            lines: Vec::with_capacity(64),
89            collapsed_pastes: Vec::new(),
90            styles: SessionStyles::new(theme.clone()),
91            theme,
92            appearance,
93            header_context: InlineHeaderContext::default(),
94            labels: MessageLabels::default(),
95
96            // --- Prompt/Input Display ---
97            prompt_prefix: USER_PREFIX.to_string(),
98            prompt_style: InlineTextStyle::default(),
99            placeholder,
100            placeholder_style: None,
101            input_status_left: None,
102            input_status_right: None,
103            input_compact_mode: false,
104
105            // --- UI State ---
106            slash_palette: SlashPalette::with_commands(slash_commands),
107            navigation_state: ListState::default(), // Kept for backward compatibility
108            input_enabled: true,
109            cursor_visible: true,
110            needs_redraw: true,
111            needs_full_clear: false,
112            transcript_content_changed: true,
113            should_exit: false,
114            scroll_cursor_steady_until: None,
115            last_shimmer_active: false,
116            view_rows: resolved_rows,
117            input_height: Self::input_block_height_for_lines(1),
118            transcript_rows: initial_transcript_rows,
119            transcript_width: 0,
120            transcript_view_top: 0,
121            transcript_area: None,
122            input_area: None,
123            transcript_file_link_targets: Vec::new(),
124            hovered_transcript_file_link: None,
125            last_mouse_position: None,
126
127            // --- Logging ---
128            log_receiver: None,
129            log_lines: VecDeque::with_capacity(MAX_LOG_LINES),
130            log_cached_text: None,
131            log_evicted: false,
132            show_logs,
133
134            // --- Rendering ---
135            transcript_cache: None,
136            visible_lines_cache: None,
137            queued_inputs: Vec::with_capacity(4),
138            queue_overlay_cache: None,
139            queue_overlay_version: 0,
140            active_overlay: None,
141            overlay_queue: VecDeque::new(),
142            header_rows: initial_header_rows,
143            line_revision_counter: 0,
144            first_dirty_line: None,
145            in_tool_code_fence: false,
146
147            // --- Palette Management ---
148            file_palette: None,
149            file_palette_active: false,
150            inline_lists_visible: true,
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            // --- History Picker (Ctrl+R fuzzy search) ---
160            history_picker_state: HistoryPickerState::new(),
161
162            // --- PTY Session Management ---
163            active_pty_sessions: None,
164
165            // --- Clipboard for yank/paste operations ---
166            clipboard: String::new(),
167
168            // --- Mouse Text Selection ---
169            mouse_selection: MouseSelectionState::new(),
170
171            skip_confirmations: false,
172
173            // --- Performance Caching ---
174            header_lines_cache: None,
175            header_height_cache: hashbrown::HashMap::new(),
176            queued_inputs_preview_cache: None,
177
178            // --- Terminal Title ---
179            app_name,
180            workspace_root: None,
181            last_terminal_title: None,
182
183            // --- Streaming State ---
184            is_streaming_final_answer: false,
185        };
186        session.ensure_prompt_style_color();
187        session
188    }
189
190    pub(super) fn clear_thinking_spinner_if_active(&mut self, kind: InlineMessageKind) {
191        // Clear spinner when any substantive agent output arrives
192        if matches!(
193            kind,
194            InlineMessageKind::Agent
195                | InlineMessageKind::Policy
196                | InlineMessageKind::Tool
197                | InlineMessageKind::Error
198        ) && self.thinking_spinner.is_active
199        {
200            self.thinking_spinner.stop();
201            self.needs_redraw = true;
202        }
203    }
204}