github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/cli/mode/histlist.go (about)

     1  package mode
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"src.elv.sh/pkg/cli"
     7  	"src.elv.sh/pkg/cli/tk"
     8  	"src.elv.sh/pkg/store"
     9  	"src.elv.sh/pkg/ui"
    10  )
    11  
    12  // Histlist is a mode for browsing history and selecting entries to insert. It
    13  // is based on the ComboBox widget.
    14  type Histlist interface {
    15  	tk.ComboBox
    16  }
    17  
    18  // HistlistSpec specifies the configuration for the histlist mode.
    19  type HistlistSpec struct {
    20  	// Key bindings.
    21  	Bindings tk.Bindings
    22  	// AllCmds is called to retrieve all commands.
    23  	AllCmds func() ([]store.Cmd, error)
    24  	// Dedup is called to determine whether deduplication should be done.
    25  	// Defaults to true if unset.
    26  	Dedup func() bool
    27  	// Configuration for the filter.
    28  	Filter FilterSpec
    29  }
    30  
    31  // NewHistlist creates a new histlist mode.
    32  func NewHistlist(app cli.App, spec HistlistSpec) (Histlist, error) {
    33  	if spec.AllCmds == nil {
    34  		return nil, errNoHistoryStore
    35  	}
    36  	if spec.Dedup == nil {
    37  		spec.Dedup = func() bool { return true }
    38  	}
    39  
    40  	cmds, err := spec.AllCmds()
    41  	if err != nil {
    42  		return nil, fmt.Errorf("db error: %v", err.Error())
    43  	}
    44  	last := map[string]int{}
    45  	for i, cmd := range cmds {
    46  		last[cmd.Text] = i
    47  	}
    48  	cmdItems := histlistItems{cmds, last}
    49  
    50  	w := tk.NewComboBox(tk.ComboBoxSpec{
    51  		CodeArea: tk.CodeAreaSpec{
    52  			Prompt: func() ui.Text {
    53  				content := " HISTORY "
    54  				if spec.Dedup() {
    55  					content += "(dedup on) "
    56  				}
    57  				return modeLine(content, true)
    58  			},
    59  			Highlighter: spec.Filter.Highlighter,
    60  		},
    61  		ListBox: tk.ListBoxSpec{
    62  			Bindings: spec.Bindings,
    63  			OnAccept: func(it tk.Items, i int) {
    64  				text := it.(histlistItems).entries[i].Text
    65  				app.CodeArea().MutateState(func(s *tk.CodeAreaState) {
    66  					buf := &s.Buffer
    67  					if buf.Content == "" {
    68  						buf.InsertAtDot(text)
    69  					} else {
    70  						buf.InsertAtDot("\n" + text)
    71  					}
    72  				})
    73  				app.SetAddon(nil, false)
    74  			},
    75  		},
    76  		OnFilter: func(w tk.ComboBox, p string) {
    77  			it := cmdItems.filter(spec.Filter.makePredicate(p), spec.Dedup())
    78  			w.ListBox().Reset(it, it.Len()-1)
    79  		},
    80  	})
    81  	return w, nil
    82  }
    83  
    84  type histlistItems struct {
    85  	entries []store.Cmd
    86  	last    map[string]int
    87  }
    88  
    89  func (it histlistItems) filter(p func(string) bool, dedup bool) histlistItems {
    90  	var filtered []store.Cmd
    91  	for i, entry := range it.entries {
    92  		text := entry.Text
    93  		if dedup && it.last[text] != i {
    94  			continue
    95  		}
    96  		if p(text) {
    97  			filtered = append(filtered, entry)
    98  		}
    99  	}
   100  	return histlistItems{filtered, nil}
   101  }
   102  
   103  func (it histlistItems) Show(i int) ui.Text {
   104  	entry := it.entries[i]
   105  	// TODO: The alignment of the index works up to 10000 entries.
   106  	return ui.T(fmt.Sprintf("%4d %s", entry.Seq, entry.Text))
   107  }
   108  
   109  func (it histlistItems) Len() int { return len(it.entries) }