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  }