Skip to main content

vtcode_tui/core_tui/
session.rs

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
60// New modular components (refactored from main session.rs)
61mod 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;
102// TaskPlan integration intentionally omitted in this UI crate.
103use 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    // --- Managers (Phase 2) ---
200    /// Manages user input, cursor, and command history
201    pub(crate) input_manager: InputManager,
202    /// Manages scroll state and viewport metrics
203    pub(crate) scroll_manager: ScrollManager,
204    user_scrolled: bool,
205
206    // --- Message Management ---
207    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/Input Display ---
217    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    /// Transient "copied" confirmation shown in the input status row.
224    copy_notification_until: Option<Instant>,
225    input_compact_mode: bool,
226
227    // --- UI State ---
228    #[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    /// Track whether the transcript viewport must be cleared before repainting.
235    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    // --- Logging ---
258    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    // --- Rendering ---
265    transcript_cache: Option<TranscriptReflowCache>,
266    /// Cache of visible lines by (scroll_offset, width) - shared via Arc for zero-copy reads
267    /// Avoids expensive clone on cache hits
268    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    /// Track the first line that needs reflow/update to avoid O(N) scans
282    first_dirty_line: Option<usize>,
283    in_tool_code_fence: bool,
284
285    // --- Prompt Suggestions ---
286    pub(crate) suggested_prompt_state: SuggestedPromptState,
287    pub(crate) inline_prompt_suggestion: InlinePromptSuggestionState,
288
289    // --- Thinking Indicator ---
290    pub(crate) thinking_spinner: ThinkingSpinner,
291    pub(crate) shimmer_state: ShimmerState,
292
293    // --- Reverse Search ---
294    pub(crate) reverse_search_state: reverse_search::ReverseSearchState,
295
296    // --- PTY Session Management ---
297    pub(crate) active_pty_sessions: Option<Arc<std::sync::atomic::AtomicUsize>>,
298
299    // --- Keybinding store ---
300    pub(crate) bindings: BindingStore,
301
302    // --- Clipboard for yank/paste operations ---
303    #[expect(dead_code)]
304    pub(crate) clipboard: String,
305    pub(crate) vim_state: VimState,
306
307    // --- Mouse Text Selection ---
308    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    // --- Performance Caching ---
315    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    // --- Terminal Title ---
321    /// Product/app name used in terminal title branding
322    pub(crate) app_name: String,
323    /// Workspace root path for dynamic title generation
324    pub(crate) workspace_root: Option<std::path::PathBuf>,
325    /// Raw config items for terminal title rendering (`None` means use defaults).
326    pub(crate) terminal_title_items: Option<Vec<String>>,
327    /// Active thread label shown in terminal title when configured.
328    pub(crate) terminal_title_thread_label: Option<String>,
329    /// Active git branch shown in terminal title when configured.
330    pub(crate) terminal_title_git_branch: Option<String>,
331    /// Latest task tracker progress label extracted from the task panel.
332    pub(crate) terminal_title_task_progress: Option<String>,
333    /// Last set terminal title to avoid redundant updates
334    last_terminal_title: Option<String>,
335
336    // --- Streaming State ---
337    /// Track if the assistant is currently streaming a final answer.
338    /// When true, user input should be queued instead of submitted immediately
339    /// to prevent race conditions with turn completion (see GitHub #12569).
340    pub(crate) is_streaming_final_answer: bool,
341}