src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/cli/modes/histwalk.go (about) 1 package modes 2 3 import ( 4 "errors" 5 "fmt" 6 7 "src.elv.sh/pkg/cli" 8 "src.elv.sh/pkg/cli/histutil" 9 "src.elv.sh/pkg/cli/term" 10 "src.elv.sh/pkg/cli/tk" 11 ) 12 13 // Histwalk is a mode for walking through history. 14 type Histwalk interface { 15 tk.Widget 16 // Walk to the previous entry in history. 17 Prev() error 18 // Walk to the next entry in history. 19 Next() error 20 // Update buffer with current entry. Always returns a nil error. 21 Accept() error 22 } 23 24 // HistwalkSpec specifies the configuration for the histwalk mode. 25 type HistwalkSpec struct { 26 // Key bindings. 27 Bindings tk.Bindings 28 // History store to walk. 29 Store histutil.Store 30 // Only walk through items with this prefix. 31 Prefix string 32 } 33 34 type histwalk struct { 35 app cli.App 36 attachedTo tk.CodeArea 37 cursor histutil.Cursor 38 HistwalkSpec 39 } 40 41 func (w *histwalk) Render(width, height int) *term.Buffer { 42 buf := w.render(width) 43 buf.TrimToLines(0, height) 44 return buf 45 } 46 47 func (w *histwalk) MaxHeight(width, height int) int { 48 return len(w.render(width).Lines) 49 } 50 51 func (w *histwalk) render(width int) *term.Buffer { 52 cmd, _ := w.cursor.Get() 53 content := modeLine(fmt.Sprintf(" HISTORY #%d ", cmd.Seq), false) 54 return term.NewBufferBuilder(width).WriteStyled(content).Buffer() 55 } 56 57 func (w *histwalk) Handle(event term.Event) bool { 58 handled := w.Bindings.Handle(w, event) 59 if handled { 60 return true 61 } 62 w.attachedTo.MutateState((*tk.CodeAreaState).ApplyPending) 63 w.app.PopAddon() 64 return w.attachedTo.Handle(event) 65 } 66 67 func (w *histwalk) Focus() bool { return false } 68 69 var errNoHistoryStore = errors.New("no history store") 70 71 // NewHistwalk creates a new Histwalk mode. 72 func NewHistwalk(app cli.App, cfg HistwalkSpec) (Histwalk, error) { 73 codeArea, err := FocusedCodeArea(app) 74 if err != nil { 75 return nil, err 76 } 77 if cfg.Store == nil { 78 return nil, errNoHistoryStore 79 } 80 if cfg.Bindings == nil { 81 cfg.Bindings = tk.DummyBindings{} 82 } 83 cursor := cfg.Store.Cursor(cfg.Prefix) 84 cursor.Prev() 85 if _, err := cursor.Get(); err != nil { 86 return nil, err 87 } 88 w := histwalk{app: app, attachedTo: codeArea, HistwalkSpec: cfg, cursor: cursor} 89 w.updatePending() 90 return &w, nil 91 } 92 93 func (w *histwalk) Prev() error { 94 return w.walk(histutil.Cursor.Prev, histutil.Cursor.Next) 95 } 96 97 func (w *histwalk) Next() error { 98 return w.walk(histutil.Cursor.Next, histutil.Cursor.Prev) 99 } 100 101 func (w *histwalk) walk(f func(histutil.Cursor), undo func(histutil.Cursor)) error { 102 f(w.cursor) 103 _, err := w.cursor.Get() 104 if err == nil { 105 w.updatePending() 106 } else if err == histutil.ErrEndOfHistory { 107 undo(w.cursor) 108 } 109 return err 110 } 111 112 func (w *histwalk) Dismiss() { 113 w.attachedTo.MutateState(func(s *tk.CodeAreaState) { s.Pending = tk.PendingCode{} }) 114 } 115 116 func (w *histwalk) updatePending() { 117 cmd, _ := w.cursor.Get() 118 w.attachedTo.MutateState(func(s *tk.CodeAreaState) { 119 s.Pending = tk.PendingCode{ 120 From: len(w.Prefix), To: len(s.Buffer.Content), 121 Content: cmd.Text[len(w.Prefix):], 122 } 123 }) 124 } 125 126 func (w *histwalk) Accept() error { 127 w.attachedTo.MutateState((*tk.CodeAreaState).ApplyPending) 128 w.app.PopAddon() 129 return nil 130 }