bettertui_engine 0.1.0

High-performance terminal UI framework
Documentation
use super::selection::SelectionRange;

#[derive(Debug, Clone)]
pub enum UndoAction {
    InsertChar { pos: usize, ch: char },
    InsertStr { pos: usize, text: String },
    DeleteChar { pos: usize, ch: char },
    DeleteRange { range: SelectionRange, text: String },
}

#[derive(Debug, Clone)]
pub struct UndoManager {
    undo_stack: Vec<UndoAction>,
    redo_stack: Vec<UndoAction>,
    max_undo: usize,
}

impl Default for UndoManager {
    fn default() -> Self {
        Self::new()
    }
}

impl UndoManager {
    pub fn new() -> Self {
        Self { undo_stack: Vec::new(), redo_stack: Vec::new(), max_undo: 1000 }
    }

    pub fn with_max_undo(max_undo: usize) -> Self {
        Self { undo_stack: Vec::new(), redo_stack: Vec::new(), max_undo }
    }

    pub fn push(&mut self, action: UndoAction) {
        self.undo_stack.push(action);
        self.redo_stack.clear();

        if self.undo_stack.len() > self.max_undo {
            self.undo_stack.remove(0);
        }
    }

    pub fn undo(&mut self) -> Option<UndoAction> {
        if let Some(action) = self.undo_stack.pop() {
            self.redo_stack.push(action.clone());
            Some(action)
        } else {
            None
        }
    }

    pub fn redo(&mut self) -> Option<UndoAction> {
        if let Some(action) = self.redo_stack.pop() {
            self.undo_stack.push(action.clone());
            Some(action)
        } else {
            None
        }
    }

    pub fn can_undo(&self) -> bool {
        !self.undo_stack.is_empty()
    }

    pub fn can_redo(&self) -> bool {
        !self.redo_stack.is_empty()
    }

    pub fn undo_count(&self) -> usize {
        self.undo_stack.len()
    }

    pub fn redo_count(&self) -> usize {
        self.redo_stack.len()
    }

    pub fn clear(&mut self) {
        self.undo_stack.clear();
        self.redo_stack.clear();
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn undo_manager_new() {
        let manager = UndoManager::new();
        assert!(!manager.can_undo());
        assert!(!manager.can_redo());
    }

    #[test]
    fn undo_manager_default() {
        let manager = UndoManager::default();
        assert!(!manager.can_undo());
        assert!(!manager.can_redo());
    }

    #[test]
    fn undo_manager_push_undo() {
        let mut manager = UndoManager::new();
        manager.push(UndoAction::InsertChar { pos: 0, ch: 'a' });
        assert!(manager.can_undo());
        assert!(!manager.can_redo());
    }

    #[test]
    fn undo_manager_undo() {
        let mut manager = UndoManager::new();
        manager.push(UndoAction::InsertChar { pos: 0, ch: 'a' });
        let action = manager.undo();
        assert!(action.is_some());
        assert!(!manager.can_undo());
        assert!(manager.can_redo());
    }

    #[test]
    fn undo_manager_redo() {
        let mut manager = UndoManager::new();
        manager.push(UndoAction::InsertChar { pos: 0, ch: 'a' });
        manager.undo();
        let action = manager.redo();
        assert!(action.is_some());
        assert!(manager.can_undo());
        assert!(!manager.can_redo());
    }

    #[test]
    fn undo_manager_clear() {
        let mut manager = UndoManager::new();
        manager.push(UndoAction::InsertChar { pos: 0, ch: 'a' });
        manager.clear();
        assert!(!manager.can_undo());
        assert!(!manager.can_redo());
    }

    #[test]
    fn undo_manager_max_undo() {
        let mut manager = UndoManager::with_max_undo(2);
        manager.push(UndoAction::InsertChar { pos: 0, ch: 'a' });
        manager.push(UndoAction::InsertChar { pos: 1, ch: 'b' });
        manager.push(UndoAction::InsertChar { pos: 2, ch: 'c' });
        assert_eq!(manager.undo_count(), 2);
    }
}