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, 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 DiffPreviewState, InlineCommand, InlineEvent, InlineHeaderContext, InlineMessageKind,
22 InlineTextStyle, InlineTheme, OverlayRequest,
23 },
24};
25use crate::config::constants::ui;
26use crate::ui::tui::widgets::SessionWidget;
27
28pub mod file_palette;
29mod header;
30mod impl_events;
31mod impl_init;
32mod impl_input;
33mod impl_layout;
34mod impl_logs;
35mod impl_render;
36mod impl_scroll;
37mod impl_style;
38mod inline_list;
39mod input;
40mod input_manager;
41mod list_panel;
42mod message;
43pub mod modal;
44pub mod mouse_selection;
45mod navigation;
46mod queue;
47pub mod render;
48mod scroll;
49pub mod slash;
50pub mod slash_palette;
51pub mod styling;
52mod text_utils;
53mod transcript;
54pub mod utils;
55pub mod wrapping;
56
57mod command;
59mod editing;
60
61pub mod config;
62mod diff_preview;
63mod events;
64pub mod history_picker;
65mod message_renderer;
66mod messages;
67mod palette;
68mod reflow;
69mod reverse_search;
70mod spinner;
71mod state;
72pub mod terminal_capabilities;
73mod terminal_title;
74#[cfg(test)]
75mod tests;
76mod tool_renderer;
77mod trust;
78
79use self::file_palette::FilePalette;
80use self::history_picker::HistoryPickerState;
81use self::input_manager::InputManager;
82use self::message::{MessageLabels, MessageLine};
83use self::modal::{ModalState, WizardModalState};
84
85use self::config::AppearanceConfig;
86pub(crate) use self::input::status_requires_shimmer;
87use self::mouse_selection::MouseSelectionState;
88use self::queue::QueueOverlay;
89use self::scroll::ScrollManager;
90use self::slash_palette::SlashPalette;
91use self::spinner::{ShimmerState, ThinkingSpinner};
92use self::styling::SessionStyles;
93use self::transcript::TranscriptReflowCache;
94#[cfg(test)]
95use super::types::InlineHeaderHighlight;
96use crate::ui::tui::log::{LogEntry, highlight_log_entry};
98
99const USER_PREFIX: &str = "";
100const PLACEHOLDER_COLOR: RgbColor =
101 RgbColor(ui::PLACEHOLDER_R, ui::PLACEHOLDER_G, ui::PLACEHOLDER_B);
102const MAX_LOG_LINES: usize = 256;
103const MAX_LOG_DRAIN_PER_TICK: usize = 256;
104
105#[derive(Clone, Debug)]
106struct CollapsedPaste {
107 line_index: usize,
108 full_text: String,
109}
110
111pub(crate) enum ActiveOverlay {
112 Modal(Box<ModalState>),
113 Wizard(Box<WizardModalState>),
114 Diff(Box<DiffPreviewState>),
115}
116
117impl ActiveOverlay {
118 fn as_modal(&self) -> Option<&ModalState> {
119 match self {
120 Self::Modal(state) => Some(state),
121 Self::Wizard(_) | Self::Diff(_) => None,
122 }
123 }
124
125 fn as_modal_mut(&mut self) -> Option<&mut ModalState> {
126 match self {
127 Self::Modal(state) => Some(state),
128 Self::Wizard(_) | Self::Diff(_) => None,
129 }
130 }
131
132 fn as_wizard(&self) -> Option<&WizardModalState> {
133 match self {
134 Self::Wizard(state) => Some(state),
135 Self::Modal(_) | Self::Diff(_) => None,
136 }
137 }
138
139 fn as_wizard_mut(&mut self) -> Option<&mut WizardModalState> {
140 match self {
141 Self::Wizard(state) => Some(state),
142 Self::Modal(_) | Self::Diff(_) => None,
143 }
144 }
145
146 fn as_diff(&self) -> Option<&DiffPreviewState> {
147 match self {
148 Self::Diff(state) => Some(state),
149 Self::Modal(_) | Self::Wizard(_) => None,
150 }
151 }
152
153 fn as_diff_mut(&mut self) -> Option<&mut DiffPreviewState> {
154 match self {
155 Self::Diff(state) => Some(state),
156 Self::Modal(_) | Self::Wizard(_) => None,
157 }
158 }
159
160 fn is_modal_like(&self) -> bool {
161 matches!(self, Self::Modal(_) | Self::Wizard(_))
162 }
163
164 fn restore_input(&self) -> bool {
165 match self {
166 Self::Modal(state) => state.restore_input,
167 Self::Wizard(_) | Self::Diff(_) => true,
168 }
169 }
170
171 fn restore_cursor(&self) -> bool {
172 match self {
173 Self::Modal(state) => state.restore_cursor,
174 Self::Wizard(_) | Self::Diff(_) => true,
175 }
176 }
177}
178
179pub struct Session {
180 pub(crate) input_manager: InputManager,
183 pub(crate) scroll_manager: ScrollManager,
185 user_scrolled: bool,
186
187 pub(crate) lines: Vec<MessageLine>,
189 collapsed_pastes: Vec<CollapsedPaste>,
190 pub(crate) theme: InlineTheme,
191 pub(crate) styles: SessionStyles,
192 pub(crate) appearance: AppearanceConfig,
193 pub(crate) header_context: InlineHeaderContext,
194 pub(crate) header_rows: u16,
195 pub(crate) labels: MessageLabels,
196
197 prompt_prefix: String,
199 prompt_style: InlineTextStyle,
200 placeholder: Option<String>,
201 placeholder_style: Option<InlineTextStyle>,
202 pub(crate) input_status_left: Option<String>,
203 pub(crate) input_status_right: Option<String>,
204 input_compact_mode: bool,
205
206 slash_palette: SlashPalette,
208 #[allow(dead_code)]
209 navigation_state: ListState,
210 input_enabled: bool,
211 cursor_visible: bool,
212 pub(crate) needs_redraw: bool,
213 pub(crate) needs_full_clear: bool,
214 pub(crate) transcript_content_changed: bool,
216 should_exit: bool,
217 scroll_cursor_steady_until: Option<Instant>,
218 last_shimmer_active: bool,
219 pub(crate) view_rows: u16,
220 pub(crate) input_height: u16,
221 pub(crate) transcript_rows: u16,
222 pub(crate) transcript_width: u16,
223 pub(crate) transcript_view_top: usize,
224 transcript_area: Option<Rect>,
225 input_area: Option<Rect>,
226
227 log_receiver: Option<UnboundedReceiver<LogEntry>>,
229 log_lines: VecDeque<Arc<Text<'static>>>,
230 log_cached_text: Option<Arc<Text<'static>>>,
231 log_evicted: bool,
232 pub(crate) show_logs: bool,
233
234 transcript_cache: Option<TranscriptReflowCache>,
236 pub(crate) visible_lines_cache: Option<(usize, u16, Arc<Vec<Line<'static>>>)>,
239 pub(crate) queued_inputs: Vec<String>,
240 queue_overlay_cache: Option<QueueOverlay>,
241 queue_overlay_version: u64,
242 active_overlay: Option<ActiveOverlay>,
243 overlay_queue: VecDeque<OverlayRequest>,
244 line_revision_counter: u64,
245 first_dirty_line: Option<usize>,
247 in_tool_code_fence: bool,
248
249 pub(crate) file_palette: Option<FilePalette>,
251 pub(crate) file_palette_active: bool,
252 pub(crate) inline_lists_visible: bool,
253
254 pub(crate) thinking_spinner: ThinkingSpinner,
256 pub(crate) shimmer_state: ShimmerState,
257
258 pub(crate) reverse_search_state: reverse_search::ReverseSearchState,
260
261 pub(crate) history_picker_state: HistoryPickerState,
263
264 pub(crate) active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
266
267 #[allow(dead_code)]
269 pub(crate) clipboard: String,
270
271 pub(crate) mouse_selection: MouseSelectionState,
273
274 pub(crate) skip_confirmations: bool,
275
276 pub(crate) header_lines_cache: Option<Vec<Line<'static>>>,
278 pub(crate) header_height_cache: hashbrown::HashMap<u16, u16>,
279 pub(crate) queued_inputs_preview_cache: Option<Vec<String>>,
280
281 pub(crate) app_name: String,
284 pub(crate) workspace_root: Option<std::path::PathBuf>,
286 last_terminal_title: Option<String>,
288
289 pub(crate) is_streaming_final_answer: bool,
294}