github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/cli/modes/histlist.go (about)

     1  package modes
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/markusbkk/elvish/pkg/cli"
     7  	"github.com/markusbkk/elvish/pkg/cli/tk"
     8  	"github.com/markusbkk/elvish/pkg/store/storedefs"
     9  	"github.com/markusbkk/elvish/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() ([]storedefs.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  	codeArea, err := FocusedCodeArea(app)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  	if spec.AllCmds == nil {
    38  		return nil, errNoHistoryStore
    39  	}
    40  	if spec.Dedup == nil {
    41  		spec.Dedup = func() bool { return true }
    42  	}
    43  
    44  	cmds, err := spec.AllCmds()
    45  	if err != nil {
    46  		return nil, fmt.Errorf("db error: %v", err.Error())
    47  	}
    48  	last := map[string]int{}
    49  	for i, cmd := range cmds {
    50  		last[cmd.Text] = i
    51  	}
    52  	cmdItems := histlistItems{cmds, last}
    53  
    54  	w := tk.NewComboBox(tk.ComboBoxSpec{
    55  		CodeArea: tk.CodeAreaSpec{
    56  			Prompt: func() ui.Text {
    57  				content := " HISTORY "
    58  				if spec.Dedup() {
    59  					content += "(dedup on) "
    60  				}
    61  				return modeLine(content, true)
    62  			},
    63  			Highlighter: spec.Filter.Highlighter,
    64  		},
    65  		ListBox: tk.ListBoxSpec{
    66  			Bindings: spec.Bindings,
    67  			OnAccept: func(it tk.Items, i int) {
    68  				text := it.(histlistItems).entries[i].Text
    69  				codeArea.MutateState(func(s *tk.CodeAreaState) {
    70  					buf := &s.Buffer
    71  					if buf.Content == "" {
    72  						buf.InsertAtDot(text)
    73  					} else {
    74  						buf.InsertAtDot("\n" + text)
    75  					}
    76  				})
    77  				app.PopAddon()
    78  			},
    79  		},
    80  		OnFilter: func(w tk.ComboBox, p string) {
    81  			it := cmdItems.filter(spec.Filter.makePredicate(p), spec.Dedup())
    82  			w.ListBox().Reset(it, it.Len()-1)
    83  		},
    84  	})
    85  	return w, nil
    86  }
    87  
    88  type histlistItems struct {
    89  	entries []storedefs.Cmd
    90  	last    map[string]int
    91  }
    92  
    93  func (it histlistItems) filter(p func(string) bool, dedup bool) histlistItems {
    94  	var filtered []storedefs.Cmd
    95  	for i, entry := range it.entries {
    96  		text := entry.Text
    97  		if dedup && it.last[text] != i {
    98  			continue
    99  		}
   100  		if p(text) {
   101  			filtered = append(filtered, entry)
   102  		}
   103  	}
   104  	return histlistItems{filtered, nil}
   105  }
   106  
   107  func (it histlistItems) Show(i int) ui.Text {
   108  	entry := it.entries[i]
   109  	// TODO: The alignment of the index works up to 10000 entries.
   110  	return ui.T(fmt.Sprintf("%4d %s", entry.Seq, entry.Text))
   111  }
   112  
   113  func (it histlistItems) Len() int { return len(it.entries) }