Skip to content

Commit 3a2aa41

Browse files
author
mritd
committed
feat(prompt): 增加 select 组件
增加 select 下拉列表组件 Signed-off-by: mritd <[email protected]>
1 parent f3f67ad commit 3a2aa41

File tree

6 files changed

+416
-68
lines changed

6 files changed

+416
-68
lines changed

main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,3 +45,7 @@ func main() {
4545
basename := filepath.Base(os.Args[0])
4646
util.CheckAndExit(commandFor(basename, cmd.RootCmd).Execute())
4747
}
48+
49+
//func main() {
50+
// commit.TRun()
51+
//}

pkg/commit/ci.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,3 +174,37 @@ func Commit(cm *Message) {
174174

175175
fmt.Println("\n✔ Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live.")
176176
}
177+
178+
//func TRun() {
179+
//
180+
// commitTypes := []TypeMessage{
181+
// {Type: consts.FEAT, ZHDescription: "新功能", ENDescription: "Introducing new features"},
182+
// {Type: consts.FIX, ZHDescription: "修复 Bug", ENDescription: "Bug fix"},
183+
// {Type: consts.DOCS, ZHDescription: "添加文档", ENDescription: "Writing docs"},
184+
// {Type: consts.STYLE, ZHDescription: "调整格式", ENDescription: "Improving structure/format of the code"},
185+
// {Type: consts.REFACTOR, ZHDescription: "重构代码", ENDescription: "Refactoring code"},
186+
// {Type: consts.TEST, ZHDescription: "增加测试", ENDescription: "When adding missing tests"},
187+
// {Type: consts.CHORE, ZHDescription: "CI/CD 变动", ENDescription: "Changing CI/CD"},
188+
// {Type: consts.PERF, ZHDescription: "性能优化", ENDescription: "Improving performance"},
189+
// {Type: consts.EXIT, ZHDescription: "退出", ENDescription: "Exit commit"},
190+
// }
191+
//
192+
// cfg := &gitprompt.SelectConfig{
193+
// ActiveTpl: "» {{ .Type | cyan }} ({{ .ENDescription | cyan }})",
194+
// InactiveTpl: " {{ .Type | white }} ({{ .ENDescription | white }})",
195+
// SelectPrompt: "Commit Type",
196+
// SelectedTpl: "{{ \"» Type:\" | green }} {{ .Type }}",
197+
// DisPlaySize: 9,
198+
// DetailsTpl: `
199+
//--------- Commit Type ----------
200+
//{{ "Type:" | faint }} {{ .Type }}
201+
//{{ "Description:" | faint }} {{ .ZHDescription }}({{ .ENDescription }})`,
202+
// }
203+
//
204+
// s := &gitprompt.Select{
205+
// Items: commitTypes,
206+
// Config: cfg,
207+
// }
208+
//
209+
// fmt.Println(commitTypes[s.Run()])
210+
//}

pkg/prompt/codes.go

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,16 @@ const (
4949
var ResetCode = fmt.Sprintf("%s%dm", esc, reset)
5050

5151
const (
52-
hideCursor = esc + "?25l"
53-
showCursor = esc + "?25h"
54-
clearLine = esc + "2K"
55-
moveUp = esc + "1A"
56-
move2Up = esc + "2A"
57-
moveDown = esc + "1B"
58-
clearTerminal = "\033c"
52+
hideCursor = esc + "?25l"
53+
showCursor = esc + "?25h"
54+
clearLine = esc + "2K"
55+
clearDown = esc + "J"
56+
clearStartOfLine = esc + "1K"
57+
clearScreen = esc + "2J"
58+
moveUp = esc + "1A"
59+
move2Up = esc + "2A"
60+
moveDown = esc + "1B"
61+
clearTerminal = "\033c"
5962
)
6063

6164
// FuncMap defines template helpers for the output. It can be extended as a

pkg/prompt/list/list.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package list
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strings"
7+
)
8+
9+
// Searcher can be implemented to allow the list to search for results.
10+
type Searcher func(input string, index int) bool
11+
12+
// NotFound is an index returned when no item was selected. This could
13+
// happen due to a search without results.
14+
const NotFound = -1
15+
16+
// List holds a collection of items that can be displayed with an N number of
17+
// visible items. The list can be moved up, down by one item of time or an
18+
// entire page (ie: visible size). It keeps track of the current selected item.
19+
type List struct {
20+
items []*interface{}
21+
scope []*interface{}
22+
cursor int // cursor holds the index of the current selected item
23+
size int // size is the number of visible options
24+
start int
25+
Searcher Searcher
26+
}
27+
28+
// New creates and initializes a list. Items must be a slice type and size must
29+
// be greater than 0.
30+
func New(items interface{}, size int) (*List, error) {
31+
if size < 1 {
32+
return nil, fmt.Errorf("list size %d must be greater than 0", size)
33+
}
34+
35+
if items == nil || reflect.TypeOf(items).Kind() != reflect.Slice {
36+
return nil, fmt.Errorf("items %v is not a slice", items)
37+
}
38+
39+
slice := reflect.ValueOf(items)
40+
values := make([]*interface{}, slice.Len())
41+
42+
for i := range values {
43+
item := slice.Index(i).Interface()
44+
values[i] = &item
45+
}
46+
47+
return &List{size: size, items: values, scope: values}, nil
48+
}
49+
50+
// Prev moves the visible list back one item. If the selected item is out of
51+
// view, the new select item becomes the last visible item. If the list is
52+
// already at the top, nothing happens.
53+
func (l *List) Prev() {
54+
if l.cursor > 0 {
55+
l.cursor--
56+
}
57+
58+
if l.start > l.cursor {
59+
l.start = l.cursor
60+
}
61+
}
62+
63+
// Search allows the list to be filtered by a given term. The list must
64+
// implement the searcher method for that.
65+
func (l *List) Search(term string) {
66+
term = strings.Trim(term, " ")
67+
l.cursor = 0
68+
l.start = 0
69+
l.search(term)
70+
}
71+
72+
// CancelSearch stops the current search and returns the list to its
73+
// original order.
74+
func (l *List) CancelSearch() {
75+
l.cursor = 0
76+
l.start = 0
77+
l.scope = l.items
78+
}
79+
80+
func (l *List) search(term string) {
81+
var scope []*interface{}
82+
83+
for i, item := range l.items {
84+
if l.Searcher(term, i) {
85+
scope = append(scope, item)
86+
}
87+
}
88+
89+
l.scope = scope
90+
}
91+
92+
// Next moves the visible list forward one item. If the selected item is out of
93+
// view, the new select item becomes the first visible item. If the list is
94+
// already at the bottom, nothing happens.
95+
func (l *List) Next() {
96+
max := len(l.scope) - 1
97+
98+
if l.cursor < max {
99+
l.cursor++
100+
}
101+
102+
if l.start+l.size <= l.cursor {
103+
l.start = l.cursor - l.size + 1
104+
}
105+
}
106+
107+
// PageUp moves the visible list backward by x items. Where x is the size of the
108+
// visible items on the list. The selected item becomes the first visible item.
109+
// If the list is already at the bottom, the selected item becomes the last
110+
// visible item.
111+
func (l *List) PageUp() {
112+
start := l.start - l.size
113+
if start < 0 {
114+
l.start = 0
115+
} else {
116+
l.start = start
117+
}
118+
119+
cursor := l.start
120+
121+
if cursor < l.cursor {
122+
l.cursor = cursor
123+
}
124+
}
125+
126+
// PageDown moves the visible list forward by x items. Where x is the size of
127+
// the visible items on the list. The selected item becomes the first visible
128+
// item.
129+
func (l *List) PageDown() {
130+
start := l.start + l.size
131+
max := len(l.scope) - l.size
132+
133+
switch {
134+
case len(l.scope) < l.size:
135+
l.start = 0
136+
case start > max:
137+
l.start = max
138+
default:
139+
l.start = start
140+
}
141+
142+
cursor := l.start
143+
144+
if cursor == l.cursor {
145+
l.cursor = len(l.scope) - 1
146+
} else if cursor > l.cursor {
147+
l.cursor = cursor
148+
}
149+
}
150+
151+
// CanPageDown returns whether a list can still PageDown().
152+
func (l *List) CanPageDown() bool {
153+
max := len(l.scope)
154+
return l.start+l.size < max
155+
}
156+
157+
// CanPageUp returns whether a list can still PageUp().
158+
func (l *List) CanPageUp() bool {
159+
return l.start > 0
160+
}
161+
162+
// Index returns the index of the item currently selected.
163+
func (l *List) Index() int {
164+
selected := l.scope[l.cursor]
165+
166+
for i, item := range l.items {
167+
if item == selected {
168+
return i
169+
}
170+
}
171+
172+
return NotFound
173+
}
174+
175+
// Items returns a slice equal to the size of the list with the current visible
176+
// items and the index of the active item in this list.
177+
func (l *List) Items() ([]interface{}, int) {
178+
var result []interface{}
179+
max := len(l.scope)
180+
end := l.start + l.size
181+
182+
if end > max {
183+
end = max
184+
}
185+
186+
active := NotFound
187+
188+
for i, j := l.start, 0; i < end; i, j = i+1, j+1 {
189+
if l.cursor == i {
190+
active = j
191+
}
192+
193+
result = append(result, *l.scope[i])
194+
}
195+
196+
return result, active
197+
}

pkg/prompt/prompt.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,8 @@ func render(tpl *template.Template, data interface{}) []byte {
8888
return buf.Bytes()
8989
}
9090

91-
func filterInput(r rune) (rune, bool) {
91+
func promptFilterInput(r rune) (rune, bool) {
92+
9293
switch r {
9394
// block CtrlZ feature
9495
case readline.CharCtrlZ:
@@ -99,8 +100,9 @@ func filterInput(r rune) (rune, bool) {
99100
fmt.Print(clearLine)
100101
fmt.Print(moveUp)
101102
return r, true
103+
default:
104+
return r, true
102105
}
103-
return r, true
104106
}
105107

106108
func (p *Prompt) Run() string {
@@ -115,7 +117,7 @@ func (p *Prompt) Run() string {
115117
Prompt: string(displayPrompt),
116118
DisableAutoSaveHistory: true,
117119
InterruptPrompt: "^C",
118-
FuncFilterInputRune: filterInput,
120+
FuncFilterInputRune: promptFilterInput,
119121
})
120122
util.CheckAndExit(err)
121123

0 commit comments

Comments
 (0)