1// Readline is a pure go implementation for GNU-Readline kind library.
2//
3// example:
4// 	rl, err := readline.New("> ")
5// 	if err != nil {
6// 		panic(err)
7// 	}
8// 	defer rl.Close()
9//
10// 	for {
11// 		line, err := rl.Readline()
12// 		if err != nil { // io.EOF
13// 			break
14// 		}
15// 		println(line)
16// 	}
17//
18package readline
19
20import "io"
21
22type Instance struct {
23	Config    *Config
24	Terminal  *Terminal
25	Operation *Operation
26}
27
28type Config struct {
29	// prompt supports ANSI escape sequence, so we can color some characters even in windows
30	Prompt string
31
32	// readline will persist historys to file where HistoryFile specified
33	HistoryFile string
34	// specify the max length of historys, it's 500 by default, set it to -1 to disable history
35	HistoryLimit           int
36	DisableAutoSaveHistory bool
37	// enable case-insensitive history searching
38	HistorySearchFold bool
39
40	// AutoCompleter will called once user press TAB
41	AutoComplete AutoCompleter
42
43	// Any key press will pass to Listener
44	// NOTE: Listener will be triggered by (nil, 0, 0) immediately
45	Listener Listener
46
47	Painter Painter
48
49	// If VimMode is true, readline will in vim.insert mode by default
50	VimMode bool
51
52	InterruptPrompt string
53	EOFPrompt       string
54
55	FuncGetWidth func() int
56
57	Stdin       io.ReadCloser
58	StdinWriter io.Writer
59	Stdout      io.Writer
60	Stderr      io.Writer
61
62	EnableMask bool
63	MaskRune   rune
64
65	// erase the editing line after user submited it
66	// it use in IM usually.
67	UniqueEditLine bool
68
69	// filter input runes (may be used to disable CtrlZ or for translating some keys to different actions)
70	// -> output = new (translated) rune and true/false if continue with processing this one
71	FuncFilterInputRune func(rune) (rune, bool)
72
73	// force use interactive even stdout is not a tty
74	FuncIsTerminal      func() bool
75	FuncMakeRaw         func() error
76	FuncExitRaw         func() error
77	FuncOnWidthChanged  func(func())
78	ForceUseInteractive bool
79
80	// private fields
81	inited    bool
82	opHistory *opHistory
83	opSearch  *opSearch
84}
85
86func (c *Config) useInteractive() bool {
87	if c.ForceUseInteractive {
88		return true
89	}
90	return c.FuncIsTerminal()
91}
92
93func (c *Config) Init() error {
94	if c.inited {
95		return nil
96	}
97	c.inited = true
98	if c.Stdin == nil {
99		c.Stdin = NewCancelableStdin(Stdin)
100	}
101
102	c.Stdin, c.StdinWriter = NewFillableStdin(c.Stdin)
103
104	if c.Stdout == nil {
105		c.Stdout = Stdout
106	}
107	if c.Stderr == nil {
108		c.Stderr = Stderr
109	}
110	if c.HistoryLimit == 0 {
111		c.HistoryLimit = 500
112	}
113
114	if c.InterruptPrompt == "" {
115		c.InterruptPrompt = "^C"
116	} else if c.InterruptPrompt == "\n" {
117		c.InterruptPrompt = ""
118	}
119	if c.EOFPrompt == "" {
120		c.EOFPrompt = "^D"
121	} else if c.EOFPrompt == "\n" {
122		c.EOFPrompt = ""
123	}
124
125	if c.AutoComplete == nil {
126		c.AutoComplete = &TabCompleter{}
127	}
128	if c.FuncGetWidth == nil {
129		c.FuncGetWidth = GetScreenWidth
130	}
131	if c.FuncIsTerminal == nil {
132		c.FuncIsTerminal = DefaultIsTerminal
133	}
134	rm := new(RawMode)
135	if c.FuncMakeRaw == nil {
136		c.FuncMakeRaw = rm.Enter
137	}
138	if c.FuncExitRaw == nil {
139		c.FuncExitRaw = rm.Exit
140	}
141	if c.FuncOnWidthChanged == nil {
142		c.FuncOnWidthChanged = DefaultOnWidthChanged
143	}
144
145	return nil
146}
147
148func (c Config) Clone() *Config {
149	c.opHistory = nil
150	c.opSearch = nil
151	return &c
152}
153
154func (c *Config) SetListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) {
155	c.Listener = FuncListener(f)
156}
157
158func (c *Config) SetPainter(p Painter) {
159	c.Painter = p
160}
161
162func NewEx(cfg *Config) (*Instance, error) {
163	t, err := NewTerminal(cfg)
164	if err != nil {
165		return nil, err
166	}
167	rl := t.Readline()
168	if cfg.Painter == nil {
169		cfg.Painter = &defaultPainter{}
170	}
171	return &Instance{
172		Config:    cfg,
173		Terminal:  t,
174		Operation: rl,
175	}, nil
176}
177
178func New(prompt string) (*Instance, error) {
179	return NewEx(&Config{Prompt: prompt})
180}
181
182func (i *Instance) ResetHistory() {
183	i.Operation.ResetHistory()
184}
185
186func (i *Instance) SetPrompt(s string) {
187	i.Operation.SetPrompt(s)
188}
189
190func (i *Instance) SetMaskRune(r rune) {
191	i.Operation.SetMaskRune(r)
192}
193
194// change history persistence in runtime
195func (i *Instance) SetHistoryPath(p string) {
196	i.Operation.SetHistoryPath(p)
197}
198
199// readline will refresh automatic when write through Stdout()
200func (i *Instance) Stdout() io.Writer {
201	return i.Operation.Stdout()
202}
203
204// readline will refresh automatic when write through Stdout()
205func (i *Instance) Stderr() io.Writer {
206	return i.Operation.Stderr()
207}
208
209// switch VimMode in runtime
210func (i *Instance) SetVimMode(on bool) {
211	i.Operation.SetVimMode(on)
212}
213
214func (i *Instance) IsVimMode() bool {
215	return i.Operation.IsEnableVimMode()
216}
217
218func (i *Instance) GenPasswordConfig() *Config {
219	return i.Operation.GenPasswordConfig()
220}
221
222// we can generate a config by `i.GenPasswordConfig()`
223func (i *Instance) ReadPasswordWithConfig(cfg *Config) ([]byte, error) {
224	return i.Operation.PasswordWithConfig(cfg)
225}
226
227func (i *Instance) ReadPasswordEx(prompt string, l Listener) ([]byte, error) {
228	return i.Operation.PasswordEx(prompt, l)
229}
230
231func (i *Instance) ReadPassword(prompt string) ([]byte, error) {
232	return i.Operation.Password(prompt)
233}
234
235type Result struct {
236	Line  string
237	Error error
238}
239
240func (l *Result) CanContinue() bool {
241	return len(l.Line) != 0 && l.Error == ErrInterrupt
242}
243
244func (l *Result) CanBreak() bool {
245	return !l.CanContinue() && l.Error != nil
246}
247
248func (i *Instance) Line() *Result {
249	ret, err := i.Readline()
250	return &Result{ret, err}
251}
252
253// err is one of (nil, io.EOF, readline.ErrInterrupt)
254func (i *Instance) Readline() (string, error) {
255	return i.Operation.String()
256}
257
258func (i *Instance) ReadlineWithDefault(what string) (string, error) {
259	i.Operation.SetBuffer(what)
260	return i.Operation.String()
261}
262
263func (i *Instance) SaveHistory(content string) error {
264	return i.Operation.SaveHistory(content)
265}
266
267// same as readline
268func (i *Instance) ReadSlice() ([]byte, error) {
269	return i.Operation.Slice()
270}
271
272// we must make sure that call Close() before process exit.
273func (i *Instance) Close() error {
274	if err := i.Terminal.Close(); err != nil {
275		return err
276	}
277	i.Config.Stdin.Close()
278	i.Operation.Close()
279	return nil
280}
281func (i *Instance) Clean() {
282	i.Operation.Clean()
283}
284
285func (i *Instance) Write(b []byte) (int, error) {
286	return i.Stdout().Write(b)
287}
288
289// WriteStdin prefill the next Stdin fetch
290// Next time you call ReadLine() this value will be writen before the user input
291// ie :
292//  i := readline.New()
293//  i.WriteStdin([]byte("test"))
294//  _, _= i.Readline()
295//
296// gives
297//
298// > test[cursor]
299func (i *Instance) WriteStdin(val []byte) (int, error) {
300	return i.Terminal.WriteStdin(val)
301}
302
303func (i *Instance) SetConfig(cfg *Config) *Config {
304	if i.Config == cfg {
305		return cfg
306	}
307	old := i.Config
308	i.Config = cfg
309	i.Operation.SetConfig(cfg)
310	i.Terminal.SetConfig(cfg)
311	return old
312}
313
314func (i *Instance) Refresh() {
315	i.Operation.Refresh()
316}
317
318// HistoryDisable the save of the commands into the history
319func (i *Instance) HistoryDisable() {
320	i.Operation.history.Disable()
321}
322
323// HistoryEnable the save of the commands into the history (default on)
324func (i *Instance) HistoryEnable() {
325	i.Operation.history.Enable()
326}
327