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            inline_lists_visible: true,
148
149            // --- Thinking Indicator ---
150            thinking_spinner: ThinkingSpinner::new(),
151            shimmer_state: ShimmerState::new(),
152
153            // --- Reverse Search ---
154            reverse_search_state: reverse_search::ReverseSearchState::new(),
155
156            // --- History Picker (Ctrl+R fuzzy search) ---
157            history_picker_state: HistoryPickerState::new(),
158
159            // --- PTY Session Management ---
160            active_pty_sessions: None,
161
162            // --- Clipboard for yank/paste operations ---
163            clipboard: String::new(),
164
165            // --- Mouse Text Selection ---
166            mouse_selection: MouseSelectionState::new(),
167
168            // --- Diff Preview Modal ---
169            diff_preview: None,
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}