1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
|
package modes
import (
"fmt"
"strconv"
"strings"
"src.elv.sh/pkg/cli"
"src.elv.sh/pkg/cli/histutil"
"src.elv.sh/pkg/cli/tk"
"src.elv.sh/pkg/ui"
)
// Lastcmd is a mode for inspecting the last command, and inserting part of all
// of it. It is based on the ComboBox widget.
type Lastcmd interface {
tk.ComboBox
}
// LastcmdSpec specifies the configuration for the lastcmd mode.
type LastcmdSpec struct {
// Key bindings.
Bindings tk.Bindings
// Store provides the source for the last command.
Store LastcmdStore
// Wordifier breaks a command into words.
Wordifier func(string) []string
}
// LastcmdStore is a subset of histutil.Store used in lastcmd mode.
type LastcmdStore interface {
Cursor(prefix string) histutil.Cursor
}
var _ = LastcmdStore(histutil.Store(nil))
// NewLastcmd creates a new lastcmd mode.
func NewLastcmd(app cli.App, cfg LastcmdSpec) (Lastcmd, error) {
codeArea, err := FocusedCodeArea(app)
if err != nil {
return nil, err
}
if cfg.Store == nil {
return nil, errNoHistoryStore
}
c := cfg.Store.Cursor("")
c.Prev()
cmd, err := c.Get()
if err != nil {
return nil, fmt.Errorf("db error: %v", err)
}
wordifier := cfg.Wordifier
if wordifier == nil {
wordifier = strings.Fields
}
cmdText := cmd.Text
words := wordifier(cmdText)
entries := make([]lastcmdEntry, len(words)+1)
entries[0] = lastcmdEntry{content: cmdText}
for i, word := range words {
entries[i+1] = lastcmdEntry{strconv.Itoa(i), strconv.Itoa(i - len(words)), word}
}
accept := func(text string) {
codeArea.MutateState(func(s *tk.CodeAreaState) {
s.Buffer.InsertAtDot(text)
})
app.PopAddon()
}
w := tk.NewComboBox(tk.ComboBoxSpec{
CodeArea: tk.CodeAreaSpec{Prompt: modePrompt(" LASTCMD ", true)},
ListBox: tk.ListBoxSpec{
Bindings: cfg.Bindings,
OnAccept: func(it tk.Items, i int) {
accept(it.(lastcmdItems).entries[i].content)
},
},
OnFilter: func(w tk.ComboBox, p string) {
items := filterLastcmdItems(entries, p)
if len(items.entries) == 1 {
accept(items.entries[0].content)
} else {
w.ListBox().Reset(items, 0)
}
},
})
return w, nil
}
type lastcmdItems struct {
negFilter bool
entries []lastcmdEntry
}
type lastcmdEntry struct {
posIndex string
negIndex string
content string
}
func filterLastcmdItems(allEntries []lastcmdEntry, p string) lastcmdItems {
if p == "" {
return lastcmdItems{false, allEntries}
}
var entries []lastcmdEntry
negFilter := strings.HasPrefix(p, "-")
for _, entry := range allEntries {
if (negFilter && strings.HasPrefix(entry.negIndex, p)) ||
(!negFilter && strings.HasPrefix(entry.posIndex, p)) {
entries = append(entries, entry)
}
}
return lastcmdItems{negFilter, entries}
}
func (it lastcmdItems) Show(i int) ui.Text {
index := ""
entry := it.entries[i]
if it.negFilter {
index = entry.negIndex
} else {
index = entry.posIndex
}
// NOTE: We now use a hardcoded width of 3 for the index, which will work as
// long as the command has less than 1000 words (when filter is positive) or
// 100 words (when filter is negative).
return ui.T(fmt.Sprintf("%3s %s", index, entry.content))
}
func (it lastcmdItems) Len() int { return len(it.entries) }
|