-
Notifications
You must be signed in to change notification settings - Fork 0
/
lastcmd.go
130 lines (117 loc) · 3.19 KB
/
lastcmd.go
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"
"github.com/markusbkk/elvish/pkg/cli"
"github.com/markusbkk/elvish/pkg/cli/histutil"
"github.com/markusbkk/elvish/pkg/cli/tk"
"github.com/markusbkk/elvish/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) }