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  }