//! Miscellaneous state: overlay enum, misc state bag, editor state. use std::path::PathBuf; use zesdex_infrastructure::Toast; /// Which modal overlay, if any, is currently shown over the main TUI view. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Overlay { /// No overlay; the main chat view is shown. None, /// Key bindings help screen. Help, /// Settings/configuration panel. Settings, /// Background bash job viewer. Bash, /// "Are you sure you want to quit?" confirmation. QuitConfirm, /// Raw key-code input capture (for binding custom keys). KeyInput, /// Inline editor (opened via `/edit`). Editor, /// Reasoning effort level selector. Effort, /// MCP server management panel. Mcp, /// Task list overlay. Todo, /// Project plan overlay. Plan, /// Session rewind / history scrubber. Rewind, /// Learning / lesson management panel. Learning, /// Token usage statistics panel. Usage, /// Generic loading spinner overlay. Loading, /// Model selector dropdown. ModelSelector, /// "Clear conversation?" confirmation. ClearConfirm, /// Git diff preview overlay. Diff, } impl Overlay { /// Human-readable name for this overlay variant. pub fn as_str(&self) -> &'static str { match self { Overlay::None => "none", Overlay::Help => "help", Overlay::Settings => "settings", Overlay::Bash => "bash", Overlay::QuitConfirm => "quit_confirm", Overlay::KeyInput => "key_input", Overlay::Editor => "editor", Overlay::Effort => "effort", Overlay::Mcp => "mcp", Overlay::Todo => "todo", Overlay::Plan => "plan", Overlay::Rewind => "rewind", Overlay::Learning => "learning", Overlay::Usage => "usage", Overlay::Loading => "loading", Overlay::ModelSelector => "model_selector", Overlay::ClearConfirm => "clear_confirm", Overlay::Diff => "diff", } } /// Whether any overlay (i.e. anything other than `None`) is active. pub fn is_active(self) -> bool { !matches!(self, Overlay::None) } } impl std::fmt::Display for Overlay { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_str(self.as_str()) } } /// Simple inline editor state for the TUI. #[derive(Debug, Clone)] pub struct EditorState { /// Path to the file being edited. pub path: PathBuf, /// Current buffer content. pub content: String, /// Cursor position (byte offset). pub cursor: usize, } impl EditorState { /// Create a new editor state for the given path. pub fn new(path: PathBuf, content: String) -> Self { let cursor = content.len(); EditorState { path, content, cursor, } } /// Return the full buffer content. pub fn as_string(&self) -> String { self.content.clone() } /// Delete one character to the left of the cursor. pub fn delete_left(&mut self) { if self.cursor > 0 { self.cursor -= 1; self.content.remove(self.cursor); } } } /// The "miscellaneous" slice of app state. #[derive(Debug, Clone)] pub struct MiscState { /// Currently active modal overlay (None = main chat view). pub overlay: Overlay, /// Active toast notifications. pub toasts: Vec, /// Timestamp (ms) of the last staleness sweep for lesson cache. pub last_staleness_sweep_ms: i64, /// Whether the agent is currently "thinking". pub thinking: bool, /// Current LLM reasoning effort level (1-5). pub effort_level: usize, /// Currently focused index in list-type overlays. pub selected_index: usize, /// Optional inline editor state. pub editor: Option, /// Whether the API connection is established. pub api_connected: bool, /// Monotonically increasing tick count, incremented each render frame. pub tick_count: u64, /// Cached content of the TODO file. pub todo_content: String, /// Cached content of the PLAN file. pub plan_content: String, /// Whether a lesson background task is currently running. pub lesson_running: bool, /// Text waiting to be written to the system clipboard. pub pending_clipboard_copy: Option, /// Cached git diff content for the preview overlay. pub diff_content: String, /// Scroll offset for the diff overlay. pub diff_scroll: usize, } impl MiscState { /// Create a fresh `MiscState` with no overlay, no toasts. pub fn new() -> Self { MiscState { overlay: Overlay::None, toasts: Vec::new(), last_staleness_sweep_ms: 0, thinking: false, effort_level: 1, selected_index: 0, editor: None, api_connected: false, tick_count: 0, todo_content: String::new(), plan_content: String::new(), lesson_running: false, pending_clipboard_copy: None, diff_content: String::new(), diff_scroll: 0, } } /// Append a toast notification to the active list. pub fn push_toast(&mut self, toast: Toast) { self.toasts.push(toast); } /// Remove all toasts whose lifetime has expired at `now_ms`. pub fn drain_expired_toasts(&mut self, now_ms: i64) { self.toasts.retain(|t| !t.expired(now_ms)); } } impl Default for MiscState { fn default() -> Self { Self::new() } }