github.com/elves/elvish@v0.15.0/pkg/cli/addons/histwalk/histwalk.go (about) 1 // Package histwalk implements the history walking addon. 2 package histwalk 3 4 import ( 5 "errors" 6 "fmt" 7 8 "github.com/elves/elvish/pkg/cli" 9 "github.com/elves/elvish/pkg/cli/histutil" 10 "github.com/elves/elvish/pkg/cli/term" 11 ) 12 13 var ErrHistWalkInactive = errors.New("the histwalk addon is not active") 14 15 // Config keeps the configuration for the histwalk addon. 16 type Config struct { 17 // Keybinding. 18 Binding cli.Handler 19 // History store to walk. 20 Store histutil.Store 21 // Only walk through items with this prefix. 22 Prefix string 23 } 24 25 type widget struct { 26 app cli.App 27 cursor histutil.Cursor 28 Config 29 } 30 31 func (w *widget) Render(width, height int) *term.Buffer { 32 cmd, _ := w.cursor.Get() 33 content := cli.ModeLine(fmt.Sprintf(" HISTORY #%d ", cmd.Seq), false) 34 buf := term.NewBufferBuilder(width).WriteStyled(content).Buffer() 35 buf.TrimToLines(0, height) 36 return buf 37 } 38 39 func (w *widget) Handle(event term.Event) bool { 40 handled := w.Binding.Handle(event) 41 if handled { 42 return true 43 } 44 Accept(w.app) 45 return w.app.CodeArea().Handle(event) 46 } 47 48 func (w *widget) Focus() bool { return false } 49 50 func (w *widget) onWalk() { 51 cmd, _ := w.cursor.Get() 52 w.app.CodeArea().MutateState(func(s *cli.CodeAreaState) { 53 s.Pending = cli.PendingCode{ 54 From: len(w.Prefix), To: len(s.Buffer.Content), 55 Content: cmd.Text[len(w.Prefix):], 56 } 57 }) 58 } 59 60 // Start starts the histwalk addon. 61 func Start(app cli.App, cfg Config) { 62 if cfg.Store == nil { 63 app.Notify("no history store") 64 return 65 } 66 if cfg.Binding == nil { 67 cfg.Binding = cli.DummyHandler{} 68 } 69 cursor := cfg.Store.Cursor(cfg.Prefix) 70 cursor.Prev() 71 _, err := cursor.Get() 72 if err != nil { 73 app.Notify(err.Error()) 74 return 75 } 76 w := widget{app: app, Config: cfg, cursor: cursor} 77 w.onWalk() 78 app.MutateState(func(s *cli.State) { s.Addon = &w }) 79 app.Redraw() 80 } 81 82 // Prev walks to the previous entry in history. It returns ErrHistWalkInactive 83 // if the histwalk addon is not active, and histutil.ErrEndOfHistory if it would 84 // go over the end. 85 func Prev(app cli.App) error { 86 return walk(app, histutil.Cursor.Prev, histutil.Cursor.Next) 87 } 88 89 // Next walks to the next entry in history. It returns ErrHistWalkInactive if 90 // the histwalk addon is not active, and histutil.ErrEndOfHistory if it would go 91 // over the end. 92 func Next(app cli.App) error { 93 return walk(app, histutil.Cursor.Next, histutil.Cursor.Prev) 94 } 95 96 // Close closes the histwalk addon. It does nothing if the histwalk addon is not 97 // active. 98 func Close(app cli.App) { 99 if closeAddon(app) { 100 app.CodeArea().MutateState(func(s *cli.CodeAreaState) { 101 s.Pending = cli.PendingCode{} 102 }) 103 } 104 } 105 106 // Accept closes the histwalk addon, accepting the current shown command. It does 107 // nothing if the histwalk addon is not active. 108 func Accept(app cli.App) { 109 if closeAddon(app) { 110 app.CodeArea().MutateState(func(s *cli.CodeAreaState) { 111 s.ApplyPending() 112 }) 113 } 114 } 115 116 func closeAddon(app cli.App) bool { 117 var closed bool 118 app.MutateState(func(s *cli.State) { 119 if _, ok := s.Addon.(*widget); !ok { 120 return 121 } 122 s.Addon = nil 123 closed = true 124 }) 125 return closed 126 } 127 128 func walk(app cli.App, f func(histutil.Cursor), undo func(histutil.Cursor)) error { 129 w, ok := getWidget(app) 130 if !ok { 131 return ErrHistWalkInactive 132 } 133 f(w.cursor) 134 _, err := w.cursor.Get() 135 if err == nil { 136 w.onWalk() 137 } else if err == histutil.ErrEndOfHistory { 138 undo(w.cursor) 139 } 140 return err 141 } 142 143 func getWidget(app cli.App) (*widget, bool) { 144 w, ok := app.CopyState().Addon.(*widget) 145 return w, ok 146 }