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: crate::ui::tui::session::reverse_search::ReverseSearchState::new(
154            ),
155
156            // --- History Picker (Ctrl+R fuzzy search) ---
157            history_picker_state: crate::ui::tui::session::history_picker::HistoryPickerState::new(
158            ),
159
160            // --- PTY Session Management ---
161            active_pty_sessions: None,
162
163            // --- Clipboard for yank/paste operations ---
164            clipboard: String::new(),
165
166            // --- Mouse Text Selection ---
167            mouse_selection: crate::ui::tui::session::mouse_selection::MouseSelectionState::new(),
168
169            // --- Diff Preview Modal ---
170            diff_preview: None,
171
172            skip_confirmations: false,
173
174            // --- Performance Caching ---
175            header_lines_cache: None,
176            header_height_cache: std::collections::HashMap::new(),
177            queued_inputs_preview_cache: None,
178
179            // --- Terminal Title ---
180            app_name,
181            workspace_root: None,
182            last_terminal_title: None,
183        };
184        session.ensure_prompt_style_color();
185        session
186    }
187
188    pub(super) fn clear_thinking_spinner_if_active(&mut self, kind: InlineMessageKind) {
189        // Clear spinner when any substantive agent output arrives
190        if matches!(
191            kind,
192            InlineMessageKind::Agent
193                | InlineMessageKind::Policy
194                | InlineMessageKind::Tool
195                | InlineMessageKind::Error
196        ) && self.thinking_spinner.is_active
197        {
198            self.thinking_spinner.stop();
199            self.needs_redraw = true;
200        }
201    }
202}