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>
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
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()
|
||||
}
|
||||
Reference in New Issue
Block a user