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