github.com/elves/elvish@v0.15.0/pkg/cli/addons/histlist/histlist.go (about)

     1  // Package histlist implements the history listing addon.
     2  package histlist
     3  
     4  import (
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/elves/elvish/pkg/cli"
     9  	"github.com/elves/elvish/pkg/cli/histutil"
    10  	"github.com/elves/elvish/pkg/store"
    11  	"github.com/elves/elvish/pkg/ui"
    12  )
    13  
    14  // Config contains configurations to start history listing.
    15  type Config struct {
    16  	// Binding provides key binding.
    17  	Binding cli.Handler
    18  	// Store provides the source of all commands.
    19  	Store Store
    20  	// Dedup is called to determine whether deduplication should be done.
    21  	// Defaults to true if unset.
    22  	Dedup func() bool
    23  	// CaseSensitive is called to determine whether the filter should be
    24  	// case-sensitive. Defaults to true if unset.
    25  	CaseSensitive func() bool
    26  }
    27  
    28  // Store wraps the AllCmds method. It is a subset of histutil.Store.
    29  type Store interface {
    30  	AllCmds() ([]store.Cmd, error)
    31  }
    32  
    33  var _ = Store(histutil.Store(nil))
    34  
    35  // Start starts history listing.
    36  func Start(app cli.App, cfg Config) {
    37  	if cfg.Store == nil {
    38  		app.Notify("no history store")
    39  		return
    40  	}
    41  	if cfg.Dedup == nil {
    42  		cfg.Dedup = func() bool { return true }
    43  	}
    44  	if cfg.CaseSensitive == nil {
    45  		cfg.CaseSensitive = func() bool { return true }
    46  	}
    47  
    48  	cmds, err := cfg.Store.AllCmds()
    49  	if err != nil {
    50  		app.Notify("db error: " + err.Error())
    51  	}
    52  	last := map[string]int{}
    53  	for i, cmd := range cmds {
    54  		last[cmd.Text] = i
    55  	}
    56  	cmdItems := items{cmds, last}
    57  
    58  	w := cli.NewComboBox(cli.ComboBoxSpec{
    59  		CodeArea: cli.CodeAreaSpec{Prompt: func() ui.Text {
    60  			content := " HISTORY "
    61  			if cfg.Dedup() {
    62  				content += "(dedup on) "
    63  			}
    64  			if !cfg.CaseSensitive() {
    65  				content += "(case-insensitive) "
    66  			}
    67  			return cli.ModeLine(content, true)
    68  		}},
    69  		ListBox: cli.ListBoxSpec{
    70  			OverlayHandler: cfg.Binding,
    71  			OnAccept: func(it cli.Items, i int) {
    72  				text := it.(items).entries[i].Text
    73  				app.CodeArea().MutateState(func(s *cli.CodeAreaState) {
    74  					buf := &s.Buffer
    75  					if buf.Content == "" {
    76  						buf.InsertAtDot(text)
    77  					} else {
    78  						buf.InsertAtDot("\n" + text)
    79  					}
    80  				})
    81  				app.MutateState(func(s *cli.State) { s.Addon = nil })
    82  			},
    83  		},
    84  		OnFilter: func(w cli.ComboBox, p string) {
    85  			it := cmdItems.filter(p, cfg.Dedup(), cfg.CaseSensitive())
    86  			w.ListBox().Reset(it, it.Len()-1)
    87  		},
    88  	})
    89  
    90  	app.MutateState(func(s *cli.State) { s.Addon = w })
    91  	app.Redraw()
    92  }
    93  
    94  type items struct {
    95  	entries []store.Cmd
    96  	last    map[string]int
    97  }
    98  
    99  func (it items) filter(p string, dedup, caseSensitive bool) items {
   100  	if p == "" && !dedup {
   101  		return it
   102  	}
   103  	if !caseSensitive {
   104  		p = strings.ToLower(p)
   105  	}
   106  	var filtered []store.Cmd
   107  	for i, entry := range it.entries {
   108  		text := entry.Text
   109  		if dedup && it.last[text] != i {
   110  			continue
   111  		}
   112  		if !caseSensitive {
   113  			text = strings.ToLower(text)
   114  		}
   115  		if strings.Contains(text, p) {
   116  			filtered = append(filtered, entry)
   117  		}
   118  	}
   119  	return items{filtered, nil}
   120  }
   121  
   122  func (it items) Show(i int) ui.Text {
   123  	entry := it.entries[i]
   124  	// TODO: The alignment of the index works up to 10000 entries.
   125  	return ui.T(fmt.Sprintf("%4d %s", entry.Seq, entry.Text))
   126  }
   127  
   128  func (it items) Len() int { return len(it.entries) }