Files
zesdex/apps/interfaces/tui/src/state/misc.rs
T
asepharyana 7b0b53671f style: format seluruh workspace dengan cargo fmt
Menyeragamkan format kode sesuai rustfmt (126 file). Sebelumnya
lefthook pre-commit 'cargo fmt --check' akan gagal pada commit apa pun.
2026-08-27 22:10:28 +07:00

200 lines
5.8 KiB
Rust

//! 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<Toast>,
/// 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<EditorState>,
/// 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<String>,
/// 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 and return all toasts whose lifetime has expired at `now_ms`.
pub fn drain_expired_toasts(&mut self, now_ms: i64) -> Vec<Toast> {
let expired: Vec<_> = self
.toasts
.iter()
.filter(|t| t.expired(now_ms))
.cloned()
.collect();
self.toasts.retain(|t| !t.expired(now_ms));
expired
}
}
impl Default for MiscState {
fn default() -> Self {
Self::new()
}
}