forked from ktr0731/evans
/
prompt.go
131 lines (113 loc) · 3.33 KB
/
prompt.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
131
package prompt
import (
"io"
"github.com/AlecAivazis/survey"
goprompt "github.com/c-bata/go-prompt"
"github.com/ktr0731/evans/color"
)
// Prompt provides interactive interfaces to receive user input.
type Prompt interface {
// Run executes Input continually.
// It is called from REPL input prompter.
// Run will be finished when a user enters CTRL+d.
Run()
// Input receives user entered input.
// Input will be abort when a user enters CTRL+d.
Input() (string, error)
// Select displays a selection consists of opts.
Select(msg string, opts []string) (string, error)
// SetPrefix chnages current prompt prefix by passed one.
SetPrefix(prefix string)
// SetPrefixColor changes current prompt color by passed one.
SetPrefixColor(color color.Color) error
}
type prompt struct {
fieldPrompt *goprompt.Prompt
currentPrefix string
hasExecutor bool
// entered is changed from prompt.Enter key bind of c-bata/go-prompt.
// c-bata/go-prompt doesn't return EOF (ctrl+d), only returns empty string.
// So Evans cannot determine whether empty input is EOF or just entered.
// Therefore, Evans uses a tricky method to know EOF.
//
// 1. Register key bind for enter at New.
// 2. The key bind changes entered variable from KeyBindFunc.
// 3. prompt.Input checks entered field.
// If the input is empty string and entered field is false, it is EOF.
// 4. Input returns io.EOF as an error.
entered bool
}
// New instantiates a prompt which satisfied Prompt with c-bata/go-prompt.
// New will be replace by a mock when e2e testing.
//
// Prompt will panic when called Run if executor is nil.
//
// TODO: Don't declare New as a variable.
var New = func(executor func(string), completer func(goprompt.Document) []goprompt.Suggest, opt ...goprompt.Option) Prompt {
return newPrompt(executor, completer, opt...)
}
func newPrompt(executor func(string), completer func(goprompt.Document) []goprompt.Suggest, opt ...goprompt.Option) Prompt {
if executor == nil {
executor = func(in string) {
return
}
}
if completer == nil {
completer = func(d goprompt.Document) []goprompt.Suggest {
return nil
}
}
p := &prompt{
hasExecutor: executor != nil,
}
p.fieldPrompt = goprompt.New(
executor,
completer,
append(opt,
goprompt.OptionLivePrefix(p.livePrefix),
goprompt.OptionAddKeyBind(goprompt.KeyBind{
Key: goprompt.Enter,
Fn: func(_ *goprompt.Buffer) {
p.entered = true
},
}),
)...)
return p
}
// Run receives user input and call executor function passed at New.
// If executor is nil, Run will panic.
func (p *prompt) Run() {
if !p.hasExecutor {
panic("prompt.Run requires executor function at New")
}
p.fieldPrompt.Run()
}
func (p *prompt) Input() (string, error) {
p.entered = false
in := p.fieldPrompt.Input()
// ctrl+d
if !p.entered && in == "" {
return "", io.EOF
}
return in, nil
}
func (p *prompt) Select(msg string, opts []string) (string, error) {
var choice string
err := survey.AskOne(&survey.Select{
Message: msg,
Options: opts,
}, &choice, nil)
if err != nil {
return "", io.EOF
}
return choice, nil
}
func (p *prompt) SetPrefix(prefix string) {
p.currentPrefix = prefix
}
func (p *prompt) SetPrefixColor(color color.Color) error {
return goprompt.OptionPrefixTextColor(goprompt.Color(color))(p.fieldPrompt)
}
func (p *prompt) livePrefix() (string, bool) {
return p.currentPrefix, true
}