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  }