1use std::{collections::VecDeque, sync::Arc, time::Instant};
2
3#[cfg(test)]
4use anstyle::Color as AnsiColorEnum;
5use anstyle::RgbColor;
6use ratatui::crossterm::event::{
7 Event as CrosstermEvent, KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind,
8};
9
10use ratatui::{
11 Frame,
12 layout::{Constraint, Layout, Rect},
13 text::{Line, Span, Text},
14 widgets::{Clear, ListState, Widget},
15};
16use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
17
18use super::{
19 style::{measure_text_width, ratatui_color_from_ansi, ratatui_style_from_inline},
20 types::{
21 ActivityState, InlineCommand, InlineEvent, InlineHeaderContext, InlineListSelection, InlineMessageKind,
22 InlineTextStyle, InlineTheme, OverlayRequest,
23 },
24};
25use crate::tui::config::constants::ui;
26use crate::tui::core_tui::types::LocalAgentEntry;
27use crate::tui::options::FullscreenInteractionSettings;
28use crate::tui::ui::tui::widgets::SessionWidget;
29
30mod frame_layout;
31mod header;
32mod impl_events;
33mod impl_init;
34mod impl_input;
35mod impl_layout;
36mod impl_logs;
37mod impl_render;
38mod impl_scroll;
39mod impl_style;
40pub(crate) mod inline_list;
41mod input;
42pub(crate) mod input_manager;
43mod layout_state;
44pub(crate) mod list_navigator;
45pub(crate) mod list_panel;
46mod message;
47pub mod modal;
48pub mod mouse_selection;
49mod navigation;
50mod progress;
51mod queue;
52pub(crate) mod render;
53mod render_state;
54mod scroll;
55pub mod styling;
56pub(crate) mod text_utils;
57mod textarea_bridge;
58mod transcript;
59pub(crate) mod utils;
60pub(crate) mod wrapping;
61
62mod command;
64mod editing;
65
66pub mod action;
67pub(crate) mod clipboard_image;
68pub mod config;
69mod driver;
70mod events;
71pub(crate) mod message_renderer;
72mod messages;
73pub(crate) mod mode_switch_guard;
74mod reflow;
75pub(crate) mod reverse_search;
76mod spinner;
77mod state;
78mod sticky_prompt;
79pub mod terminal_capabilities;
80pub(crate) mod terminal_title;
81#[cfg(test)]
82mod tests;
83mod tool_renderer;
84mod transcript_links;
85mod vim;
86
87use self::input_manager::InputManager;
88use self::layout_state::SessionAreas;
89pub(crate) use self::message::TranscriptLine;
90use self::message::{MessageLabels, MessageLine};
91use self::modal::{ModalState, WizardModalState};
92
93pub use self::action::{Action, BindingStore, parse_key_binding};
94use self::config::AppearanceConfig;
95pub(crate) use self::input::status_requires_shimmer;
96use self::mouse_selection::{DragAutoScroll, DragAutoScrollDirection, MouseSelectionState};
97use self::queue::QueueOverlay;
98use self::render_state::RenderState;
99use self::scroll::ScrollManager;
100pub(crate) use self::spinner::pulse_spinner_frame_for_phase;
101use self::spinner::{ShimmerState, ThinkingSpinner};
102use self::styling::SessionStyles;
103use self::transcript::TranscriptReflowCache;
104use self::transcript_links::TranscriptFileLinkTarget;
105pub(crate) use self::transcript_links::TranscriptLinkClickAction;
106use self::vim::VimState;
107#[cfg(test)]
108use super::types::InlineHeaderHighlight;
109use crate::tui::ui::tui::log::{LogEntry, highlight_log_entry};
111
112const USER_PREFIX: &str = "";
113const PLACEHOLDER_COLOR: RgbColor = RgbColor(ui::PLACEHOLDER_R, ui::PLACEHOLDER_G, ui::PLACEHOLDER_B);
114const MAX_LOG_LINES: usize = 256;
115const MAX_LOG_DRAIN_PER_TICK: usize = 256;
116
117#[derive(Clone, Debug)]
118struct CollapsedPaste {
119 line_index: usize,
120 full_text: String,
121}
122
123#[derive(Clone, Debug, Default)]
124pub(crate) struct SuggestedPromptState {
125 active: bool,
126}
127
128#[derive(Clone, Copy, Debug, PartialEq, Eq)]
129pub(crate) enum InlinePromptSuggestionSource {
130 Llm,
131 Local,
132}
133
134#[derive(Clone, Debug, Default, PartialEq, Eq)]
135pub(crate) struct InlinePromptSuggestionState {
136 suggestion: Option<String>,
137 source: Option<InlinePromptSuggestionSource>,
138}
139
140pub(crate) enum ActiveOverlay {
141 Modal(Box<ModalState>),
142 Wizard(Box<WizardModalState>),
143}
144
145impl ActiveOverlay {
146 fn as_modal(&self) -> Option<&ModalState> {
147 match self {
148 Self::Modal(state) => Some(state),
149 Self::Wizard(_) => None,
150 }
151 }
152
153 fn as_modal_mut(&mut self) -> Option<&mut ModalState> {
154 match self {
155 Self::Modal(state) => Some(state),
156 Self::Wizard(_) => None,
157 }
158 }
159
160 fn as_wizard(&self) -> Option<&WizardModalState> {
161 match self {
162 Self::Wizard(state) => Some(state),
163 Self::Modal(_) => None,
164 }
165 }
166
167 fn as_wizard_mut(&mut self) -> Option<&mut WizardModalState> {
168 match self {
169 Self::Wizard(state) => Some(state),
170 Self::Modal(_) => None,
171 }
172 }
173
174 fn restore_input(&self) -> bool {
175 match self {
176 Self::Modal(state) => state.restore_input,
177 Self::Wizard(_) => true,
178 }
179 }
180
181 fn restore_cursor(&self) -> bool {
182 match self {
183 Self::Modal(state) => state.restore_cursor,
184 Self::Wizard(_) => true,
185 }
186 }
187}
188
189#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
190pub(crate) enum MouseDragTarget {
191 #[default]
192 None,
193 Transcript,
194 ModalText,
195 Input,
196}
197
198#[derive(Clone, Debug, Default, PartialEq, Eq)]
199pub(crate) struct FullscreenSessionState {
200 pub(crate) active: bool,
201 pub(crate) interaction: FullscreenInteractionSettings,
202}
203
204pub struct Session {
205 pub(crate) input_manager: InputManager,
208 pub(crate) scroll_manager: ScrollManager,
210 user_scrolled: bool,
211
212 pub(crate) lines: Vec<MessageLine>,
214 collapsed_pastes: Vec<CollapsedPaste>,
215 thinking_runs: ThinkingRunIndex,
219 pub(crate) theme: InlineTheme,
220 pub(crate) styles: SessionStyles,
221 pub(crate) appearance: AppearanceConfig,
222 pub(crate) auto_color_scheme: bool,
225 header_context: InlineHeaderContext,
226 pub(crate) header_rows: u16,
227 pub(crate) labels: MessageLabels,
228
229 prompt_prefix: String,
231 prompt_style: InlineTextStyle,
232 placeholder: Option<String>,
233 placeholder_style: Option<InlineTextStyle>,
234 pub(crate) input_status_left: Option<String>,
235 footer_context_status: Option<String>,
236 footer_context_right: Option<String>,
237 footer_context_configured: bool,
238 pub(crate) input_status_right: Option<String>,
239 copy_notification_until: Option<Instant>,
241 copy_notification_failed: bool,
242 copy_notification_chars: usize,
243 input_compact_mode: bool,
244
245 navigation_state: ListState,
247 input_enabled: bool,
248 pub(crate) program_status: super::program_status::ProgramStatus,
249 pub(crate) activity_state: ActivityState,
250 image_input_enabled: bool,
251 cursor_visible: bool,
252 render_state: RenderState,
253 pub(crate) transcript_clear_required: bool,
255 pub(crate) evicted_message_count: usize,
257 pub(crate) pending_new_messages: usize,
260 pub(crate) last_change_line_idx: Option<usize>,
263 should_exit: bool,
264 pub(crate) last_interrupt_press: Option<Instant>,
266 pub(crate) last_escape_press: Option<Instant>,
274 scroll_cursor_steady_until: Option<Instant>,
275 last_shimmer_active: bool,
276 view_rows: u16,
277 pub(crate) input_height: u16,
278 pub(crate) transcript_rows: u16,
279 pub(crate) transcript_width: u16,
280 pub(crate) transcript_view_top: usize,
281 areas: SessionAreas,
282 sticky_prompt_target: Option<sticky_prompt::StickyPromptTarget>,
283 sticky_prompt_preview_cache: Option<sticky_prompt::StickyPromptPreviewCache>,
284 leading_user_prompt_truncated: bool,
285 transcript_file_link_targets: Vec<TranscriptFileLinkTarget>,
286 modal_link_targets: Vec<TranscriptFileLinkTarget>,
287 hovered_transcript_file_link: Option<usize>,
288 last_mouse_position: Option<(u16, u16)>,
289 last_link_open: Option<(String, Instant)>,
290 pending_link_open: Option<String>,
291 held_key_modifiers: KeyModifiers,
292
293 log_receiver: Option<UnboundedReceiver<LogEntry>>,
295 log_lines: VecDeque<Arc<Text<'static>>>,
296 log_cached_text: Option<Arc<Text<'static>>>,
297 log_evicted: bool,
298 pub(crate) show_logs: bool,
299
300 transcript_cache: Option<TranscriptReflowCache>,
302 visible_lines_cache: Option<(usize, u16, usize, Arc<Vec<TranscriptLine>>)>,
305 pub(crate) queued_inputs: Vec<String>,
306 pub(crate) local_agents: Vec<LocalAgentEntry>,
307 local_agents_drawer_visible: bool,
308 subprocess_entries: Vec<String>,
309 subagent_preview: Option<String>,
310 queue_overlay_cache: Option<QueueOverlay>,
311 queue_overlay_version: u64,
312 active_overlay: Option<ActiveOverlay>,
313 overlay_queue: VecDeque<OverlayRequest>,
314 last_overlay_list_selection: Option<InlineListSelection>,
315 last_overlay_list_was_last: bool,
316 line_revision_counter: u64,
317 pub(crate) transcript_presentation_revision: u64,
318 first_dirty_line: Option<usize>,
320 in_tool_code_fence: bool,
321
322 suggested_prompt_state: SuggestedPromptState,
324 inline_prompt_suggestion: InlinePromptSuggestionState,
325
326 pub(crate) thinking_spinner: ThinkingSpinner,
328 pub(crate) shimmer_state: ShimmerState,
329 pub(crate) progress: progress::TransientProgress,
330
331 pub(crate) reverse_search_state: reverse_search::ReverseSearchState,
333
334 pub(crate) active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
336
337 pub(crate) background_activity_count: usize,
343 pub(crate) background_finished_count: usize,
345 pub(crate) background_indicator_hits: Vec<Rect>,
348
349 bindings: BindingStore,
351
352 clipboard: String,
354 vim_state: VimState,
355
356 pub(crate) mouse_selection: MouseSelectionState,
358 pub(crate) mouse_drag_target: MouseDragTarget,
359 pub(crate) drag_auto_scroll: Option<DragAutoScroll>,
360 pub(crate) fullscreen: FullscreenSessionState,
361
362 header_lines_cache: Option<Arc<Vec<Line<'static>>>>,
364 header_block_title_cache: Option<Line<'static>>,
365 header_height_cache: hashbrown::HashMap<u16, u16>,
366 pub(crate) queued_inputs_preview_cache: Option<Vec<String>>,
367 subprocess_entries_preview_cache: Option<Vec<String>>,
368 input_render_cache: Option<(u16, u16, u64, usize, bool, bool, input::InputRender)>,
371
372 app_name: String,
375 workspace_root: Option<std::path::PathBuf>,
377 terminal_title_items: Option<Vec<String>>,
379 terminal_title_thread_label: Option<String>,
381 terminal_title_git_branch: Option<String>,
383 terminal_title_task_progress: Option<String>,
385 last_terminal_title: Option<String>,
387
388 is_streaming_final_answer: bool,
393}
394
395#[derive(Debug, Default, Clone)]
402pub(crate) struct ThinkingRunIndex {
403 collapsed: std::collections::HashMap<usize, bool>,
406 active_start: Option<usize>,
410}
411
412impl ThinkingRunIndex {
413 fn is_collapsed(&self, start: usize, default: bool) -> bool {
416 self.collapsed.get(&start).copied().unwrap_or(default)
417 }
418
419 fn set_collapsed(&mut self, start: usize, collapsed: bool) {
421 self.collapsed.insert(start, collapsed);
422 }
423
424 fn begin_run(&mut self, start: usize) {
426 self.active_start = Some(start);
427 }
428
429 fn end_run(&mut self) {
431 self.active_start = None;
432 }
433
434 fn active_start(&self) -> Option<usize> {
436 self.active_start
437 }
438
439 fn clear(&mut self) {
441 self.collapsed.clear();
442 self.active_start = None;
443 }
444}