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) }