use std::path::PathBuf; use std::sync::Arc; use tokio::sync::RwLock; use super::types::Overlay; #[derive(Clone)] pub struct DirCache { entries: Arc>>, } impl DirCache { pub fn new() -> Self { DirCache { entries: Arc::new(RwLock::new(Vec::new())), } } pub async fn set(&self, paths: Vec) { let mut w = self.entries.write().await; *w = paths; } pub async fn get(&self) -> Vec { let r = self.entries.read().await; r.clone() } } #[derive(Debug, Clone)] pub struct ScrollState { pub offset: usize, pub max_visible: usize, } impl ScrollState { pub fn new() -> Self { ScrollState { offset: 0, max_visible: 30, } } pub fn scroll_up(&mut self) { if self.offset > 0 { self.offset -= 1; } } pub fn scroll_down(&mut self, total: usize) { let max_offset = total.saturating_sub(self.max_visible); if self.offset < max_offset { self.offset += 1; } } pub fn scroll_to_bottom(&mut self, total: usize) { self.offset = total.saturating_sub(self.max_visible); } pub fn set_max_visible(&mut self, max: usize) { self.max_visible = max; } } #[derive(Debug, Clone)] pub struct InputState { pub buffer: String, pub cursor: usize, pub history: Vec, pub history_idx: Option, } impl InputState { pub fn new() -> Self { InputState { buffer: String::new(), cursor: 0, history: Vec::new(), history_idx: None, } } pub fn char_left(&mut self) { if self.cursor > 0 { self.cursor -= 1; } } pub fn char_right(&mut self) { if self.cursor < self.buffer.len() { self.cursor += 1; } } pub fn insert(&mut self, c: char) { self.buffer.insert(self.cursor, c); self.cursor += 1; } pub fn delete_left(&mut self) { if self.cursor > 0 { self.cursor -= 1; self.buffer.remove(self.cursor); } } pub fn delete_right(&mut self) { if self.cursor < self.buffer.len() { self.buffer.remove(self.cursor); } } pub fn submit(&mut self) -> String { let result = self.buffer.clone(); if !result.is_empty() { self.history.push(result.clone()); self.history_idx = None; } self.buffer.clear(); self.cursor = 0; result } pub fn history_up(&mut self) { if self.history.is_empty() { return; } let idx = match self.history_idx { Some(i) if i > 0 => i - 1, None => self.history.len() - 1, Some(_) => return, }; self.history_idx = Some(idx); self.buffer = self.history[idx].clone(); self.cursor = self.buffer.len(); } pub fn history_down(&mut self) { match self.history_idx { Some(i) if i < self.history.len() - 1 => { let idx = i + 1; self.history_idx = Some(idx); self.buffer = self.history[idx].clone(); self.cursor = self.buffer.len(); } Some(_) => { self.history_idx = None; self.buffer.clear(); self.cursor = 0; } None => {} } } pub fn clear(&mut self) { self.buffer.clear(); self.cursor = 0; self.history_idx = None; } } #[derive(Debug, Clone)] pub struct MiscState { pub overlay: Overlay, pub toasts: Vec, pub dirty: bool, pub yolo_armed: bool, pub security_armed: bool, pub security_acknowledged: bool, pub esc_press_count: u32, pub last_staleness_sweep_ms: i64, pub effort_level: usize, pub editor: Option, } impl MiscState { pub fn new() -> Self { MiscState { overlay: Overlay::None, toasts: Vec::new(), dirty: true, yolo_armed: false, security_armed: false, security_acknowledged: false, esc_press_count: 0, last_staleness_sweep_ms: 0, effort_level: 1, editor: None, } } pub fn push_toast(&mut self, toast: super::types::Toast) { self.toasts.push(toast); } pub fn drain_expired_toasts(&mut self, now_ms: i64) -> Vec { let expired: Vec<_> = self.toasts.iter().filter(|t| t.expired(now_ms)).cloned().collect(); self.toasts.retain(|t| !t.expired(now_ms)); expired } }