Skip to main content

opendev_tui/app/
state.rs

1//! Persistent application state shared across renders.
2
3use std::collections::{HashMap, VecDeque};
4use std::sync::{Arc, Mutex};
5use std::time::Instant;
6
7use crate::history::CommandHistory;
8use crate::selection::SelectionState;
9use crate::widgets::{Toast, TodoDisplayItem, WelcomePanelState};
10
11use super::{
12    AutonomyLevel, DisplayMessage, OperationMode, PendingItem, ReasoningLevel, ToolExecution,
13};
14
15/// Persistent application state shared across renders.
16#[derive(Debug)]
17pub struct AppState {
18    /// Whether the app is running.
19    pub running: bool,
20    /// Current operation mode.
21    pub mode: OperationMode,
22    /// Autonomy level (Manual / Semi-Auto / Auto).
23    pub autonomy: AutonomyLevel,
24    /// Reasoning effort level (Off / Low / Medium / High).
25    pub reasoning_level: ReasoningLevel,
26    /// Active model name.
27    pub model: String,
28    /// Current working directory.
29    pub working_dir: String,
30    /// Cached path shortener for display (avoids repeated syscalls).
31    pub path_shortener: crate::formatters::PathShortener,
32    /// Git branch name (if in a repo).
33    pub git_branch: Option<String>,
34    /// Tokens used in current session.
35    pub tokens_used: u64,
36    /// Token limit for the session.
37    pub tokens_limit: u64,
38    /// Context window usage percentage (0.0 - 100.0+).
39    pub context_usage_pct: f64,
40    /// Session cost in USD.
41    pub session_cost: f64,
42    /// MCP server status: (connected, total).
43    pub mcp_status: Option<(usize, usize)>,
44    /// Whether any MCP server has errors.
45    pub mcp_has_errors: bool,
46    /// Whether the agent is currently processing.
47    pub agent_active: bool,
48    /// Conversation messages for display.
49    pub messages: Vec<DisplayMessage>,
50    /// Current task progress (while agent is working).
51    pub task_progress: Option<crate::widgets::progress::TaskProgress>,
52    /// Spinner state for animation.
53    pub spinner: crate::widgets::spinner::SpinnerState,
54    /// Current user input buffer.
55    pub input_buffer: String,
56    /// Cursor position within the input buffer.
57    pub input_cursor: usize,
58    /// Active tool executions.
59    pub active_tools: Vec<ToolExecution>,
60    /// Scroll offset for the conversation view (lines from bottom).
61    pub scroll_offset: u32,
62    /// Whether the user has scrolled up (disables auto-scroll).
63    pub user_scrolled: bool,
64    /// Autocomplete engine for `/` commands and `@` file mentions.
65    pub autocomplete: crate::autocomplete::AutocompleteEngine,
66    /// Number of running background tasks.
67    pub background_task_count: usize,
68    /// Info about a recently-backgrounded task: (task_id, when).
69    pub backgrounded_task_info: Option<(String, Instant)>,
70    /// Active subagent executions for nested display.
71    pub active_subagents: Vec<crate::widgets::nested_tool::SubagentDisplayState>,
72    /// Shared todo manager for syncing panel state with tool results.
73    pub todo_manager: Option<Arc<Mutex<opendev_runtime::TodoManager>>>,
74    /// Todo items from the current plan (for the todo progress panel).
75    pub todo_items: Vec<TodoDisplayItem>,
76    /// Whether the todo panel is expanded (true) or collapsed (false).
77    pub todo_expanded: bool,
78    /// Whether thinking blocks should start expanded (toggled by Ctrl+I).
79    pub thinking_expanded: bool,
80    /// Spinner tick counter for todo panel animation.
81    pub todo_spinner_tick: usize,
82    /// Optional plan name for the todo panel title.
83    pub plan_name: Option<String>,
84    /// File change stats for current session: (files, additions, deletions).
85    pub file_changes: Option<(usize, u64, u64)>,
86    /// Application version string.
87    pub version: String,
88    /// Animated welcome panel state.
89    pub welcome_panel: WelcomePanelState,
90    /// Cached terminal width for tick-time access.
91    pub terminal_width: u16,
92    /// Cached terminal height for tick-time access.
93    pub terminal_height: u16,
94    /// Unified queue for items waiting to be processed by the foreground agent.
95    /// Contains both user messages and completed background results, processed FIFO.
96    pub pending_queue: VecDeque<PendingItem>,
97    /// Dirty flag — set to `true` when state changes; cleared after render.
98    pub dirty: bool,
99    /// Generation counter for message/tool state changes.
100    /// Incremented whenever messages, tool results, or collapse state change.
101    pub message_generation: u64,
102    /// Cached conversation lines (static message portion only, excludes spinners).
103    pub cached_lines: Vec<ratatui::text::Line<'static>>,
104    /// Generation counter at which `cached_lines` was last built.
105    pub lines_generation: u64,
106    /// Per-message content hashes for incremental cache rebuilds.
107    pub per_message_hashes: Vec<u64>,
108    /// Per-message line counts tracking how many cached_lines each message produced.
109    pub per_message_line_counts: Vec<usize>,
110    /// Per-message markdown render cache, keyed by hash of (role + content).
111    pub markdown_cache: HashMap<u64, Vec<ratatui::text::Line<'static>>>,
112    /// Terminal width at which cached_lines were last built (for resize invalidation).
113    pub cached_width: u16,
114    /// Per-message culling state from the last cache rebuild.
115    /// Used to detect when scrolling changes which messages are visible vs culled.
116    pub per_message_culled: Vec<bool>,
117    /// Scroll offset at the time cached_lines were last built.
118    pub cached_scroll_offset: u32,
119    /// Scroll acceleration: last scroll direction (true = up, false = down).
120    pub scroll_last_direction: Option<bool>,
121    /// Scroll acceleration: timestamp of the last scroll key press.
122    pub scroll_last_time: Option<Instant>,
123    /// Scroll acceleration: current acceleration level (0 = base, increases).
124    pub scroll_accel_level: u8,
125    /// Active color theme for the TUI.
126    pub theme: crate::formatters::style_tokens::Theme,
127    /// Name of the active theme.
128    pub theme_name: crate::formatters::style_tokens::ThemeName,
129    /// Command history for Up/Down arrow navigation.
130    pub command_history: CommandHistory,
131    /// Flag set by /compact command; agent loop consumes and triggers compaction.
132    pub compact_requested: bool,
133    /// Whether manual compaction is currently in progress.
134    pub compaction_active: bool,
135    /// Plan mode flag — when true, next UserSubmit injects plan reminder.
136    pub pending_plan_request: bool,
137    /// Plan content to display in the conversation (consumed after first render).
138    pub plan_content_display: Option<String>,
139    /// Whether we're waiting for the current tool to finish before backgrounding.
140    pub backgrounding_pending: bool,
141    /// Background agent task manager.
142    pub bg_agent_manager: crate::managers::BackgroundAgentManager,
143    /// Whether the task watcher panel (Alt+B) is open.
144    pub task_watcher_open: bool,
145    /// Index of focused cell in the task watcher grid (0-based).
146    pub task_watcher_focus: usize,
147    /// Per-task scroll offset in the task watcher (index = task_idx, value = lines scrolled up).
148    pub task_watcher_cell_scrolls: Vec<usize>,
149    /// Page offset when tasks exceed grid capacity.
150    pub task_watcher_page: usize,
151    /// When all tasks finished (for auto-close after 3s grace).
152    pub task_watcher_all_done_at: Option<Instant>,
153    /// Last task completion flash: (task_id, when).
154    pub last_task_completion: Option<(String, Instant)>,
155    /// Active toast notifications.
156    pub toasts: Vec<Toast>,
157    /// Whether leader key (Ctrl+X) is pending.
158    pub leader_pending: bool,
159    /// Timestamp of leader key press (for timeout).
160    pub leader_timestamp: Option<Instant>,
161    /// Undo stack: tree hashes from snapshot manager.
162    pub undo_stack: Vec<String>,
163    /// Redo stack: tree hashes for redo.
164    pub redo_stack: Vec<String>,
165    /// Whether debug panel is open.
166    pub debug_panel_open: bool,
167    /// Session title (set by the agent).
168    pub session_title: Option<String>,
169    /// Maps background subagent IDs to their parent background task IDs.
170    pub bg_subagent_map: HashMap<String, String>,
171    /// Per-subagent cancellation tokens for individual kill support.
172    pub subagent_cancel_tokens: HashMap<String, tokio_util::sync::CancellationToken>,
173    /// Text selection state for mouse-based copy.
174    pub selection: SelectionState,
175    /// Force a full terminal clear before next draw (resets ratatui's diff buffer).
176    pub force_clear: bool,
177    /// Timestamp of last user-interactive event (key, mouse, scroll).
178    /// Used to detect tab-switch return via timing gap.
179    pub last_event_time: Option<Instant>,
180}
181
182impl Default for AppState {
183    fn default() -> Self {
184        Self {
185            running: true,
186            mode: OperationMode::Normal,
187            autonomy: AutonomyLevel::SemiAuto,
188            reasoning_level: ReasoningLevel::Medium,
189            model: String::from("claude-sonnet-4"),
190            working_dir: String::from("."),
191            path_shortener: crate::formatters::PathShortener::new(Some(".")),
192            git_branch: None,
193            tokens_used: 0,
194            tokens_limit: 200_000,
195            context_usage_pct: 0.0,
196            session_cost: 0.0,
197            mcp_status: None,
198            mcp_has_errors: false,
199            agent_active: false,
200            messages: Vec::new(),
201            task_progress: None,
202            spinner: crate::widgets::spinner::SpinnerState::new(),
203            input_buffer: String::new(),
204            input_cursor: 0,
205            active_tools: Vec::new(),
206            scroll_offset: 0,
207            user_scrolled: false,
208            autocomplete: crate::autocomplete::AutocompleteEngine::new(
209                std::env::current_dir().unwrap_or_else(|_| std::path::PathBuf::from(".")),
210            ),
211            background_task_count: 0,
212            backgrounded_task_info: None,
213            active_subagents: Vec::new(),
214            todo_manager: None,
215            todo_items: Vec::new(),
216            todo_expanded: true,
217            thinking_expanded: false,
218            todo_spinner_tick: 0,
219            plan_name: None,
220            file_changes: None,
221            version: env!("CARGO_PKG_VERSION").to_string(),
222            welcome_panel: WelcomePanelState::new(),
223            terminal_width: 80,
224            terminal_height: 24,
225            pending_queue: VecDeque::new(),
226            dirty: true,
227            message_generation: 0,
228            cached_lines: Vec::new(),
229            lines_generation: u64::MAX, // Force initial build
230            per_message_hashes: Vec::new(),
231            per_message_line_counts: Vec::new(),
232            markdown_cache: HashMap::new(),
233            cached_width: 80,
234            per_message_culled: Vec::new(),
235            cached_scroll_offset: 0,
236            scroll_last_direction: None,
237            scroll_last_time: None,
238            scroll_accel_level: 0,
239            theme: crate::formatters::style_tokens::Theme::dark(),
240            theme_name: crate::formatters::style_tokens::ThemeName::Dark,
241            command_history: CommandHistory::new(),
242            compact_requested: false,
243            compaction_active: false,
244            pending_plan_request: false,
245            plan_content_display: None,
246            backgrounding_pending: false,
247            bg_agent_manager: crate::managers::BackgroundAgentManager::new(),
248            task_watcher_open: false,
249            task_watcher_focus: 0,
250            task_watcher_cell_scrolls: Vec::new(),
251            task_watcher_page: 0,
252            task_watcher_all_done_at: None,
253            last_task_completion: None,
254            toasts: Vec::new(),
255            leader_pending: false,
256            leader_timestamp: None,
257            undo_stack: Vec::new(),
258            redo_stack: Vec::new(),
259            debug_panel_open: false,
260            session_title: None,
261            bg_subagent_map: HashMap::new(),
262            subagent_cancel_tokens: HashMap::new(),
263            selection: SelectionState::default(),
264            force_clear: false,
265            last_event_time: None,
266        }
267    }
268}
269
270#[cfg(test)]
271#[path = "state_tests.rs"]
272mod tests;