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