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  }