package command import ( "context" "fmt" "sync" ) // Executor runs commands and supports undo/redo of successful ones. type Executor interface { Do(cmd Command) error Undo() error Redo() error CanUndo() bool CanRedo() bool LastUndoName() string LastRedoName() string } // History is an in-memory undo/redo stack for Command values. type History struct { mu sync.Mutex undoS []Command redoS []Command } // NewHistory creates an empty command history. func NewHistory() *History { return &History{} } // Do executes cmd and pushes it onto the undo stack. The redo stack is cleared. func (h *History) Do(cmd Command) error { if err := cmd.Execute(context.Background()); err != nil { return err } h.mu.Lock() defer h.mu.Unlock() h.undoS = append(h.undoS, cmd) h.redoS = nil return nil } // Undo reverses the last successful command. func (h *History) Undo() error { h.mu.Lock() if len(h.undoS) == 0 { h.mu.Unlock() return fmt.Errorf("nothing to undo") } cmd := h.undoS[len(h.undoS)-1] h.undoS = h.undoS[:len(h.undoS)-1] h.mu.Unlock() if err := cmd.Undo(context.Background()); err != nil { h.mu.Lock() h.undoS = append(h.undoS, cmd) h.mu.Unlock() return err } h.mu.Lock() h.redoS = append(h.redoS, cmd) h.mu.Unlock() return nil } // Redo re-applies the last undone command. func (h *History) Redo() error { h.mu.Lock() if len(h.redoS) == 0 { h.mu.Unlock() return fmt.Errorf("nothing to redo") } cmd := h.redoS[len(h.redoS)-1] h.redoS = h.redoS[:len(h.redoS)-1] h.mu.Unlock() if err := cmd.Execute(context.Background()); err != nil { h.mu.Lock() h.redoS = append(h.redoS, cmd) h.mu.Unlock() return err } h.mu.Lock() h.undoS = append(h.undoS, cmd) h.mu.Unlock() return nil } // CanUndo reports whether Undo would succeed. func (h *History) CanUndo() bool { h.mu.Lock() defer h.mu.Unlock() return len(h.undoS) > 0 } // CanRedo reports whether Redo would succeed. func (h *History) CanRedo() bool { h.mu.Lock() defer h.mu.Unlock() return len(h.redoS) > 0 } // LastUndoName returns the name of the next undo command, or "". func (h *History) LastUndoName() string { h.mu.Lock() defer h.mu.Unlock() if len(h.undoS) == 0 { return "" } return h.undoS[len(h.undoS)-1].Name() } // LastRedoName returns the name of the next redo command, or "". func (h *History) LastRedoName() string { h.mu.Lock() defer h.mu.Unlock() if len(h.redoS) == 0 { return "" } return h.redoS[len(h.redoS)-1].Name() }