Files
task-master/internal/command/executor.go
T
SkrueckenandCursor 24c1e146ec Add offline-first task-master TUI with tickets, board lists, and undo.
Charm-based CLI with JSON persistence, status/list filters, external editor editing, command-history undo/redo, and glamour markdown without AutoStyle OSC hangs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-19 10:21:48 +02:00

125 lines
2.4 KiB
Go

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()
}