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,
8 MouseEventKind,
9};
10
11use ratatui::{
12 Frame,
13 layout::{Constraint, Layout, Rect},
14 text::{Line, Span, Text},
15 widgets::{Clear, ListState, Widget},
16};
17use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
18
19use super::{
20 style::{measure_text_width, ratatui_color_from_ansi, ratatui_style_from_inline},
21 types::{
22 InlineCommand, InlineEvent, InlineHeaderContext, InlineListSelection, InlineMessageKind,
23 InlineTextStyle, InlineTheme, OverlayRequest,
24 },
25};
26use crate::config::constants::ui;
27use crate::core_tui::types::LocalAgentEntry;
28use crate::options::FullscreenInteractionSettings;
29use crate::ui::tui::widgets::SessionWidget;
30
31mod frame_layout;
32mod header;
33mod impl_events;
34mod impl_init;
35mod impl_input;
36mod impl_layout;
37mod impl_logs;
38mod impl_render;
39mod impl_scroll;
40mod impl_style;
41pub(crate) mod inline_list;
42mod input;
43pub(crate) mod input_manager;
44pub(crate) mod list_navigator;
45pub(crate) mod list_panel;
46mod message;
47pub mod modal;
48pub mod mouse_selection;
49mod navigation;
50mod queue;
51pub mod render;
52mod scroll;
53pub mod styling;
54mod text_utils;
55mod textarea_bridge;
56mod transcript;
57pub mod utils;
58pub mod wrapping;
59
60mod command;
62mod editing;
63
64pub mod action;
65pub mod config;
66mod driver;
67mod events;
68pub(crate) mod message_renderer;
69mod messages;
70mod reflow;
71pub(crate) mod reverse_search;
72mod spinner;
73mod state;
74pub mod terminal_capabilities;
75mod terminal_title;
76#[cfg(test)]
77mod tests;
78mod tool_renderer;
79mod transcript_links;
80mod vim;
81
82use self::input_manager::InputManager;
83pub(crate) use self::message::TranscriptLine;
84use self::message::{MessageLabels, MessageLine};
85use self::modal::{ModalState, WizardModalState};
86
87pub use self::action::{Action, BindingStore, parse_key_binding};
88use self::config::AppearanceConfig;
89pub(crate) use self::input::status_requires_shimmer;
90use self::mouse_selection::MouseSelectionState;
91use self::queue::QueueOverlay;
92use self::scroll::ScrollManager;
93pub(crate) use self::spinner::pulse_spinner_frame_for_phase;
94use self::spinner::{ShimmerState, ThinkingSpinner};
95use self::styling::SessionStyles;
96use self::transcript::TranscriptReflowCache;
97use self::transcript_links::TranscriptFileLinkTarget;
98pub(crate) use self::transcript_links::TranscriptLinkClickAction;
99use self::vim::VimState;
100#[cfg(test)]
101use super::types::InlineHeaderHighlight;
102use crate::ui::tui::log::{LogEntry, highlight_log_entry};
104
105const USER_PREFIX: &str = "";
106const PLACEHOLDER_COLOR: RgbColor =
107 RgbColor(ui::PLACEHOLDER_R, ui::PLACEHOLDER_G, ui::PLACEHOLDER_B);
108const MAX_LOG_LINES: usize = 256;
109const MAX_LOG_DRAIN_PER_TICK: usize = 256;
110
111#[derive(Clone, Debug)]
112struct CollapsedPaste {
113 line_index: usize,
114 full_text: String,
115}
116
117#[derive(Clone, Debug, Default)]
118pub(crate) struct SuggestedPromptState {
119 pub(crate) active: bool,
120}
121
122#[derive(Clone, Copy, Debug, PartialEq, Eq)]
123pub(crate) enum InlinePromptSuggestionSource {
124 Llm,
125 Local,
126}
127
128#[derive(Clone, Debug, Default, PartialEq, Eq)]
129pub(crate) struct InlinePromptSuggestionState {
130 pub(crate) suggestion: Option<String>,
131 pub(crate) source: Option<InlinePromptSuggestionSource>,
132}
133
134pub(crate) enum ActiveOverlay {
135 Modal(Box<ModalState>),
136 Wizard(Box<WizardModalState>),
137}
138
139impl ActiveOverlay {
140 fn as_modal(&self) -> Option<&ModalState> {
141 match self {
142 Self::Modal(state) => Some(state),
143 Self::Wizard(_) => None,
144 }
145 }
146
147 fn as_modal_mut(&mut self) -> Option<&mut ModalState> {
148 match self {
149 Self::Modal(state) => Some(state),
150 Self::Wizard(_) => None,
151 }
152 }
153
154 fn as_wizard(&self) -> Option<&WizardModalState> {
155 match self {
156 Self::Wizard(state) => Some(state),
157 Self::Modal(_) => None,
158 }
159 }
160
161 fn as_wizard_mut(&mut self) -> Option<&mut WizardModalState> {
162 match self {
163 Self::Wizard(state) => Some(state),
164 Self::Modal(_) => None,
165 }
166 }
167
168 fn restore_input(&self) -> bool {
169 match self {
170 Self::Modal(state) => state.restore_input,
171 Self::Wizard(_) => true,
172 }
173 }
174
175 fn restore_cursor(&self) -> bool {
176 match self {
177 Self::Modal(state) => state.restore_cursor,
178 Self::Wizard(_) => true,
179 }
180 }
181}
182
183#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
184pub(crate) enum MouseDragTarget {
185 #[default]
186 None,
187 Transcript,
188 ModalText,
189 Input,
190}
191
192#[derive(Clone, Debug, Default, PartialEq, Eq)]
193pub(crate) struct FullscreenSessionState {
194 pub(crate) active: bool,
195 pub(crate) interaction: FullscreenInteractionSettings,
196}
197
198pub struct Session {
199 pub(crate) input_manager: InputManager,
202 pub(crate) scroll_manager: ScrollManager,
204 user_scrolled: bool,
205
206 pub(crate) lines: Vec<MessageLine>,
208 collapsed_pastes: Vec<CollapsedPaste>,
209 pub(crate) theme: InlineTheme,
210 pub(crate) styles: SessionStyles,
211 pub(crate) appearance: AppearanceConfig,
212 pub(crate) header_context: InlineHeaderContext,
213 pub(crate) header_rows: u16,
214 pub(crate) labels: MessageLabels,
215
216 prompt_prefix: String,
218 prompt_style: InlineTextStyle,
219 placeholder: Option<String>,
220 placeholder_style: Option<InlineTextStyle>,
221 pub(crate) input_status_left: Option<String>,
222 pub(crate) input_status_right: Option<String>,
223 copy_notification_until: Option<Instant>,
225 input_compact_mode: bool,
226
227 #[expect(dead_code)]
229 navigation_state: ListState,
230 input_enabled: bool,
231 cursor_visible: bool,
232 pub(crate) needs_redraw: bool,
233 pub(crate) needs_full_clear: bool,
234 pub(crate) transcript_clear_required: bool,
236 should_exit: bool,
237 scroll_cursor_steady_until: Option<Instant>,
238 last_shimmer_active: bool,
239 pub(crate) view_rows: u16,
240 pub(crate) input_height: u16,
241 pub(crate) transcript_rows: u16,
242 pub(crate) transcript_width: u16,
243 pub(crate) transcript_view_top: usize,
244 transcript_area: Option<Rect>,
245 input_area: Option<Rect>,
246 bottom_panel_area: Option<Rect>,
247 modal_list_area: Option<Rect>,
248 modal_text_areas: Vec<Rect>,
249 transcript_file_link_targets: Vec<TranscriptFileLinkTarget>,
250 modal_link_targets: Vec<TranscriptFileLinkTarget>,
251 hovered_transcript_file_link: Option<usize>,
252 last_mouse_position: Option<(u16, u16)>,
253 last_link_open: Option<(String, Instant)>,
254 pending_link_open: Option<String>,
255 held_key_modifiers: KeyModifiers,
256
257 log_receiver: Option<UnboundedReceiver<LogEntry>>,
259 log_lines: VecDeque<Arc<Text<'static>>>,
260 log_cached_text: Option<Arc<Text<'static>>>,
261 log_evicted: bool,
262 pub(crate) show_logs: bool,
263
264 transcript_cache: Option<TranscriptReflowCache>,
266 pub(crate) visible_lines_cache: Option<(usize, u16, usize, Arc<Vec<TranscriptLine>>)>,
269 pub(crate) queued_inputs: Vec<String>,
270 pub(crate) local_agents: Vec<LocalAgentEntry>,
271 pub(crate) local_agents_drawer_visible: bool,
272 pub(crate) subprocess_entries: Vec<String>,
273 pub(crate) subagent_preview: Option<String>,
274 queue_overlay_cache: Option<QueueOverlay>,
275 queue_overlay_version: u64,
276 active_overlay: Option<ActiveOverlay>,
277 overlay_queue: VecDeque<OverlayRequest>,
278 last_overlay_list_selection: Option<InlineListSelection>,
279 last_overlay_list_was_last: bool,
280 line_revision_counter: u64,
281 first_dirty_line: Option<usize>,
283 in_tool_code_fence: bool,
284
285 pub(crate) suggested_prompt_state: SuggestedPromptState,
287 pub(crate) inline_prompt_suggestion: InlinePromptSuggestionState,
288
289 pub(crate) thinking_spinner: ThinkingSpinner,
291 pub(crate) shimmer_state: ShimmerState,
292
293 pub(crate) reverse_search_state: reverse_search::ReverseSearchState,
295
296 pub(crate) active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
298
299 pub(crate) bindings: BindingStore,
301
302 #[expect(dead_code)]
304 pub(crate) clipboard: String,
305 pub(crate) vim_state: VimState,
306
307 pub(crate) mouse_selection: MouseSelectionState,
309 pub(crate) mouse_drag_target: MouseDragTarget,
310 pub(crate) fullscreen: FullscreenSessionState,
311
312 pub(crate) skip_confirmations: bool,
313
314 pub(crate) header_lines_cache: Option<Vec<Line<'static>>>,
316 pub(crate) header_height_cache: hashbrown::HashMap<u16, u16>,
317 pub(crate) queued_inputs_preview_cache: Option<Vec<String>>,
318 pub(crate) subprocess_entries_preview_cache: Option<Vec<String>>,
319
320 pub(crate) app_name: String,
323 pub(crate) workspace_root: Option<std::path::PathBuf>,
325 pub(crate) terminal_title_items: Option<Vec<String>>,
327 pub(crate) terminal_title_thread_label: Option<String>,
329 pub(crate) terminal_title_git_branch: Option<String>,
331 pub(crate) terminal_title_task_progress: Option<String>,
333 last_terminal_title: Option<String>,
335
336 pub(crate) is_streaming_final_answer: bool,
341}