github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/cli/mode/histwalk.go (about) 1 package mode 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 } 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 cursor histutil.Cursor 35 HistwalkSpec 36 } 37 38 func (w *histwalk) Render(width, height int) *term.Buffer { 39 cmd, _ := w.cursor.Get() 40 content := modeLine(fmt.Sprintf(" HISTORY #%d ", cmd.Seq), false) 41 buf := term.NewBufferBuilder(width).WriteStyled(content).Buffer() 42 buf.TrimToLines(0, height) 43 return buf 44 } 45 46 func (w *histwalk) Handle(event term.Event) bool { 47 handled := w.Bindings.Handle(w, event) 48 if handled { 49 return true 50 } 51 w.app.SetAddon(nil, true) 52 return w.app.CodeArea().Handle(event) 53 } 54 55 func (w *histwalk) Focus() bool { return false } 56 57 var errNoHistoryStore = errors.New("no history store") 58 59 // NewHistwalk creates a new Histwalk mode. 60 func NewHistwalk(app cli.App, cfg HistwalkSpec) (Histwalk, error) { 61 if cfg.Store == nil { 62 return nil, errNoHistoryStore 63 } 64 if cfg.Bindings == nil { 65 cfg.Bindings = tk.DummyBindings{} 66 } 67 cursor := cfg.Store.Cursor(cfg.Prefix) 68 cursor.Prev() 69 _, err := cursor.Get() 70 if err != nil { 71 return nil, err 72 } 73 w := histwalk{app: app, HistwalkSpec: cfg, cursor: cursor} 74 w.updatePending() 75 return &w, nil 76 } 77 78 func (w *histwalk) Prev() error { 79 return w.walk(histutil.Cursor.Prev, histutil.Cursor.Next) 80 } 81 82 func (w *histwalk) Next() error { 83 return w.walk(histutil.Cursor.Next, histutil.Cursor.Prev) 84 } 85 86 func (w *histwalk) walk(f func(histutil.Cursor), undo func(histutil.Cursor)) error { 87 f(w.cursor) 88 _, err := w.cursor.Get() 89 if err == nil { 90 w.updatePending() 91 } else if err == histutil.ErrEndOfHistory { 92 undo(w.cursor) 93 } 94 return err 95 } 96 97 func (w *histwalk) Close(accept bool) { 98 w.app.CodeArea().MutateState(func(s *tk.CodeAreaState) { 99 if accept { 100 s.ApplyPending() 101 } else { 102 s.Pending = tk.PendingCode{} 103 } 104 }) 105 } 106 107 func (w *histwalk) updatePending() { 108 cmd, _ := w.cursor.Get() 109 w.app.CodeArea().MutateState(func(s *tk.CodeAreaState) { 110 s.Pending = tk.PendingCode{ 111 From: len(w.Prefix), To: len(s.Buffer.Content), 112 Content: cmd.Text[len(w.Prefix):], 113 } 114 }) 115 }