1use super::*;
2
3impl Session {
4 pub(super) fn is_error_content(content: &str) -> bool {
5 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.iter().any(|indicator| lower_content.contains(indicator))
29 }
30
31 pub fn new(theme: InlineTheme, placeholder: Option<String>, view_rows: u16) -> Self {
32 Self::new_with_logs(theme, placeholder, view_rows, true, None, "Agent TUI".to_string())
33 }
34
35 pub(crate) fn new_with_logs(
36 theme: InlineTheme,
37 placeholder: Option<String>,
38 view_rows: u16,
39 show_logs: bool,
40 appearance: Option<AppearanceConfig>,
41 app_name: String,
42 ) -> Self {
43 Self::new_with_options(theme, placeholder, view_rows, show_logs, appearance, app_name, None)
44 }
45
46 pub(crate) fn new_with_bindings(
47 theme: InlineTheme,
48 placeholder: Option<String>,
49 view_rows: u16,
50 show_logs: bool,
51 appearance: Option<AppearanceConfig>,
52 app_name: String,
53 bindings: BindingStore,
54 ) -> Self {
55 Self::new_with_options(theme, placeholder, view_rows, show_logs, appearance, app_name, Some(bindings))
56 }
57
58 fn new_with_options(
59 theme: InlineTheme,
60 placeholder: Option<String>,
61 view_rows: u16,
62 show_logs: bool,
63 appearance: Option<AppearanceConfig>,
64 app_name: String,
65 bindings: Option<BindingStore>,
66 ) -> Self {
67 let resolved_rows = view_rows.max(2);
68 let initial_header_rows = ui::INLINE_HEADER_HEIGHT;
69 let reserved_rows = initial_header_rows + Self::input_block_height_for_lines(1);
70 let initial_transcript_rows = resolved_rows.saturating_sub(reserved_rows).max(1);
71
72 let appearance = appearance.unwrap_or_default();
73 let vim_mode_enabled = appearance.vim_mode;
74
75 let mut session = Self {
76 input_manager: InputManager::new(),
78 scroll_manager: ScrollManager::new(initial_transcript_rows),
79 user_scrolled: false,
80
81 lines: Vec::with_capacity(64),
83 collapsed_pastes: Vec::new(),
84 thinking_runs: ThinkingRunIndex::default(),
85 styles: SessionStyles::new(theme.clone()),
86 theme,
87 appearance,
88 auto_color_scheme: false,
89 header_context: InlineHeaderContext::default(),
90 labels: MessageLabels::default(),
91
92 prompt_prefix: USER_PREFIX.to_string(),
94 prompt_style: InlineTextStyle::default(),
95 placeholder,
96 placeholder_style: None,
97 input_status_left: None,
98 footer_context_status: None,
99 footer_context_right: None,
100 footer_context_configured: false,
101 input_status_right: None,
102 copy_notification_until: None,
103 copy_notification_failed: false,
104 copy_notification_chars: 0,
105 drag_auto_scroll: None,
106 input_compact_mode: false,
107
108 navigation_state: ListState::default(), input_enabled: true,
111 program_status: super::super::program_status::ProgramStatus::default(),
112 activity_state: ActivityState::Idle,
113 image_input_enabled: false,
114 cursor_visible: true,
115 render_state: RenderState::new(),
116 transcript_clear_required: true,
117 evicted_message_count: 0,
118 pending_new_messages: 0,
119 last_change_line_idx: None,
120 should_exit: false,
121 last_interrupt_press: None,
122 last_escape_press: None,
123 scroll_cursor_steady_until: None,
124 last_shimmer_active: false,
125 view_rows: resolved_rows,
126 input_height: Self::input_block_height_for_lines(1),
127 transcript_rows: initial_transcript_rows,
128 transcript_width: 0,
129 transcript_view_top: 0,
130 areas: SessionAreas::default(),
131 sticky_prompt_target: None,
132 sticky_prompt_preview_cache: None,
133 leading_user_prompt_truncated: false,
134 transcript_file_link_targets: Vec::new(),
135 modal_link_targets: Vec::new(),
136 hovered_transcript_file_link: None,
137 last_mouse_position: None,
138 last_link_open: None,
139 pending_link_open: None,
140 held_key_modifiers: KeyModifiers::empty(),
141
142 log_receiver: None,
144 log_lines: VecDeque::with_capacity(MAX_LOG_LINES),
145 log_cached_text: None,
146 log_evicted: false,
147 show_logs,
148
149 transcript_cache: None,
151 visible_lines_cache: None,
152 queued_inputs: Vec::with_capacity(4),
153 local_agents: Vec::new(),
154 local_agents_drawer_visible: false,
155 subprocess_entries: Vec::new(),
156 subagent_preview: None,
157 queue_overlay_cache: None,
158 queue_overlay_version: 0,
159 active_overlay: None,
160 overlay_queue: VecDeque::new(),
161 last_overlay_list_selection: None,
162 last_overlay_list_was_last: false,
163 header_rows: initial_header_rows,
164 line_revision_counter: 0,
165 transcript_presentation_revision: 0,
166 first_dirty_line: None,
167 in_tool_code_fence: false,
168
169 suggested_prompt_state: SuggestedPromptState::default(),
171 inline_prompt_suggestion: InlinePromptSuggestionState::default(),
172
173 thinking_spinner: ThinkingSpinner::new(),
175 shimmer_state: ShimmerState::new(),
176 progress: progress::TransientProgress::default(),
177
178 reverse_search_state: reverse_search::ReverseSearchState::new(),
180
181 active_pty_sessions: None,
183
184 background_activity_count: 0,
186 background_finished_count: 0,
187 background_indicator_hits: Vec::new(),
188
189 bindings: bindings.unwrap_or_default(),
191
192 clipboard: String::new(),
194 vim_state: VimState::new(vim_mode_enabled),
195
196 mouse_selection: MouseSelectionState::new(),
198 mouse_drag_target: MouseDragTarget::None,
199 fullscreen: FullscreenSessionState::default(),
200
201 header_lines_cache: None,
203 header_block_title_cache: None,
204 header_height_cache: hashbrown::HashMap::new(),
205 queued_inputs_preview_cache: None,
206 subprocess_entries_preview_cache: None,
207 input_render_cache: None,
208
209 app_name,
211 workspace_root: None,
212 terminal_title_items: None,
213 terminal_title_thread_label: None,
214 terminal_title_git_branch: None,
215 terminal_title_task_progress: None,
216 last_terminal_title: None,
217
218 is_streaming_final_answer: false,
220 };
221 session.ensure_prompt_style_color();
222 session
223 }
224
225 pub(super) fn clear_thinking_spinner_if_active(&mut self, kind: InlineMessageKind) {
226 if matches!(
228 kind,
229 InlineMessageKind::Agent | InlineMessageKind::Policy | InlineMessageKind::Tool | InlineMessageKind::Error
230 ) && self.thinking_spinner.is_active
231 {
232 self.thinking_spinner.stop();
233 self.mark_visual_dirty();
234 }
235 }
236}