1package readline
2
3import (
4	"errors"
5	"io"
6	"sync"
7)
8
9var (
10	ErrInterrupt = errors.New("Interrupt")
11)
12
13type InterruptError struct {
14	Line []rune
15}
16
17func (*InterruptError) Error() string {
18	return "Interrupted"
19}
20
21type Operation struct {
22	m       sync.Mutex
23	cfg     *Config
24	t       *Terminal
25	buf     *RuneBuffer
26	outchan chan []rune
27	errchan chan error
28	w       io.Writer
29
30	history *opHistory
31	*opSearch
32	*opCompleter
33	*opPassword
34	*opVim
35}
36
37func (o *Operation) SetBuffer(what string) {
38	o.buf.Set([]rune(what))
39}
40
41type wrapWriter struct {
42	r      *Operation
43	t      *Terminal
44	target io.Writer
45}
46
47func (w *wrapWriter) Write(b []byte) (int, error) {
48	if !w.t.IsReading() {
49		return w.target.Write(b)
50	}
51
52	var (
53		n   int
54		err error
55	)
56	w.r.buf.Refresh(func() {
57		n, err = w.target.Write(b)
58	})
59
60	if w.r.IsSearchMode() {
61		w.r.SearchRefresh(-1)
62	}
63	if w.r.IsInCompleteMode() {
64		w.r.CompleteRefresh()
65	}
66	return n, err
67}
68
69func NewOperation(t *Terminal, cfg *Config) *Operation {
70	width := cfg.FuncGetWidth()
71	op := &Operation{
72		t:       t,
73		buf:     NewRuneBuffer(t, cfg.Prompt, cfg, width),
74		outchan: make(chan []rune),
75		errchan: make(chan error, 1),
76	}
77	op.w = op.buf.w
78	op.SetConfig(cfg)
79	op.opVim = newVimMode(op)
80	op.opCompleter = newOpCompleter(op.buf.w, op, width)
81	op.opPassword = newOpPassword(op)
82	op.cfg.FuncOnWidthChanged(func() {
83		newWidth := cfg.FuncGetWidth()
84		op.opCompleter.OnWidthChange(newWidth)
85		op.opSearch.OnWidthChange(newWidth)
86		op.buf.OnWidthChange(newWidth)
87	})
88	go op.ioloop()
89	return op
90}
91
92func (o *Operation) SetPrompt(s string) {
93	o.buf.SetPrompt(s)
94}
95
96func (o *Operation) SetMaskRune(r rune) {
97	o.buf.SetMask(r)
98}
99
100func (o *Operation) GetConfig() *Config {
101	o.m.Lock()
102	cfg := *o.cfg
103	o.m.Unlock()
104	return &cfg
105}
106
107func (o *Operation) ioloop() {
108	for {
109		keepInSearchMode := false
110		keepInCompleteMode := false
111		r := o.t.ReadRune()
112		if o.GetConfig().FuncFilterInputRune != nil {
113			var process bool
114			r, process = o.GetConfig().FuncFilterInputRune(r)
115			if !process {
116				o.buf.Refresh(nil) // to refresh the line
117				continue           // ignore this rune
118			}
119		}
120
121		if r == 0 { // io.EOF
122			if o.buf.Len() == 0 {
123				o.buf.Clean()
124				select {
125				case o.errchan <- io.EOF:
126				}
127				break
128			} else {
129				// if stdin got io.EOF and there is something left in buffer,
130				// let's flush them by sending CharEnter.
131				// And we will got io.EOF int next loop.
132				r = CharEnter
133			}
134		}
135		isUpdateHistory := true
136
137		if o.IsInCompleteSelectMode() {
138			keepInCompleteMode = o.HandleCompleteSelect(r)
139			if keepInCompleteMode {
140				continue
141			}
142
143			o.buf.Refresh(nil)
144			switch r {
145			case CharEnter, CharCtrlJ:
146				o.history.Update(o.buf.Runes(), false)
147				fallthrough
148			case CharInterrupt:
149				o.t.KickRead()
150				fallthrough
151			case CharBell:
152				continue
153			}
154		}
155
156		if o.IsEnableVimMode() {
157			r = o.HandleVim(r, o.t.ReadRune)
158			if r == 0 {
159				continue
160			}
161		}
162
163		switch r {
164		case CharBell:
165			if o.IsSearchMode() {
166				o.ExitSearchMode(true)
167				o.buf.Refresh(nil)
168			}
169			if o.IsInCompleteMode() {
170				o.ExitCompleteMode(true)
171				o.buf.Refresh(nil)
172			}
173		case CharTab:
174			if o.GetConfig().AutoComplete == nil {
175				o.t.Bell()
176				break
177			}
178			if o.OnComplete() {
179				keepInCompleteMode = true
180			} else {
181				o.t.Bell()
182				break
183			}
184
185		case CharBckSearch:
186			if !o.SearchMode(S_DIR_BCK) {
187				o.t.Bell()
188				break
189			}
190			keepInSearchMode = true
191		case CharCtrlU:
192			o.buf.KillFront()
193		case CharFwdSearch:
194			if !o.SearchMode(S_DIR_FWD) {
195				o.t.Bell()
196				break
197			}
198			keepInSearchMode = true
199		case CharKill:
200			o.buf.Kill()
201			keepInCompleteMode = true
202		case MetaForward:
203			o.buf.MoveToNextWord()
204		case CharTranspose:
205			o.buf.Transpose()
206		case MetaBackward:
207			o.buf.MoveToPrevWord()
208		case MetaDelete:
209			o.buf.DeleteWord()
210		case CharLineStart:
211			o.buf.MoveToLineStart()
212		case CharLineEnd:
213			o.buf.MoveToLineEnd()
214		case CharBackspace, CharCtrlH:
215			if o.IsSearchMode() {
216				o.SearchBackspace()
217				keepInSearchMode = true
218				break
219			}
220
221			if o.buf.Len() == 0 {
222				o.t.Bell()
223				break
224			}
225			o.buf.Backspace()
226			if o.IsInCompleteMode() {
227				o.OnComplete()
228			}
229		case CharCtrlZ:
230			o.buf.Clean()
231			o.t.SleepToResume()
232			o.Refresh()
233		case CharCtrlL:
234			ClearScreen(o.w)
235			o.Refresh()
236		case MetaBackspace, CharCtrlW:
237			o.buf.BackEscapeWord()
238		case CharCtrlY:
239			o.buf.Yank()
240		case CharEnter, CharCtrlJ:
241			if o.IsSearchMode() {
242				o.ExitSearchMode(false)
243			}
244			o.buf.MoveToLineEnd()
245			var data []rune
246			if !o.GetConfig().UniqueEditLine {
247				o.buf.WriteRune('\n')
248				data = o.buf.Reset()
249				data = data[:len(data)-1] // trim \n
250			} else {
251				o.buf.Clean()
252				data = o.buf.Reset()
253			}
254			o.outchan <- data
255			if !o.GetConfig().DisableAutoSaveHistory {
256				// ignore IO error
257				_ = o.history.New(data)
258			} else {
259				isUpdateHistory = false
260			}
261		case CharBackward:
262			o.buf.MoveBackward()
263		case CharForward:
264			o.buf.MoveForward()
265		case CharPrev:
266			buf := o.history.Prev()
267			if buf != nil {
268				o.buf.Set(buf)
269			} else {
270				o.t.Bell()
271			}
272		case CharNext:
273			buf, ok := o.history.Next()
274			if ok {
275				o.buf.Set(buf)
276			} else {
277				o.t.Bell()
278			}
279		case CharDelete:
280			if o.buf.Len() > 0 || !o.IsNormalMode() {
281				o.t.KickRead()
282				if !o.buf.Delete() {
283					o.t.Bell()
284				}
285				break
286			}
287
288			// treat as EOF
289			if !o.GetConfig().UniqueEditLine {
290				o.buf.WriteString(o.GetConfig().EOFPrompt + "\n")
291			}
292			o.buf.Reset()
293			isUpdateHistory = false
294			o.history.Revert()
295			o.errchan <- io.EOF
296			if o.GetConfig().UniqueEditLine {
297				o.buf.Clean()
298			}
299		case CharInterrupt:
300			if o.IsSearchMode() {
301				o.t.KickRead()
302				o.ExitSearchMode(true)
303				break
304			}
305			if o.IsInCompleteMode() {
306				o.t.KickRead()
307				o.ExitCompleteMode(true)
308				o.buf.Refresh(nil)
309				break
310			}
311			o.buf.MoveToLineEnd()
312			o.buf.Refresh(nil)
313			hint := o.GetConfig().InterruptPrompt + "\n"
314			if !o.GetConfig().UniqueEditLine {
315				o.buf.WriteString(hint)
316			}
317			remain := o.buf.Reset()
318			if !o.GetConfig().UniqueEditLine {
319				remain = remain[:len(remain)-len([]rune(hint))]
320			}
321			isUpdateHistory = false
322			o.history.Revert()
323			o.errchan <- &InterruptError{remain}
324		default:
325			if o.IsSearchMode() {
326				o.SearchChar(r)
327				keepInSearchMode = true
328				break
329			}
330			o.buf.WriteRune(r)
331			if o.IsInCompleteMode() {
332				o.OnComplete()
333				keepInCompleteMode = true
334			}
335		}
336
337		listener := o.GetConfig().Listener
338		if listener != nil {
339			newLine, newPos, ok := listener.OnChange(o.buf.Runes(), o.buf.Pos(), r)
340			if ok {
341				o.buf.SetWithIdx(newPos, newLine)
342			}
343		}
344
345		o.m.Lock()
346		if !keepInSearchMode && o.IsSearchMode() {
347			o.ExitSearchMode(false)
348			o.buf.Refresh(nil)
349		} else if o.IsInCompleteMode() {
350			if !keepInCompleteMode {
351				o.ExitCompleteMode(false)
352				o.Refresh()
353			} else {
354				o.buf.Refresh(nil)
355				o.CompleteRefresh()
356			}
357		}
358		if isUpdateHistory && !o.IsSearchMode() {
359			// it will cause null history
360			o.history.Update(o.buf.Runes(), false)
361		}
362		o.m.Unlock()
363	}
364}
365
366func (o *Operation) Stderr() io.Writer {
367	return &wrapWriter{target: o.GetConfig().Stderr, r: o, t: o.t}
368}
369
370func (o *Operation) Stdout() io.Writer {
371	return &wrapWriter{target: o.GetConfig().Stdout, r: o, t: o.t}
372}
373
374func (o *Operation) String() (string, error) {
375	r, err := o.Runes()
376	return string(r), err
377}
378
379func (o *Operation) Runes() ([]rune, error) {
380	o.t.EnterRawMode()
381	defer o.t.ExitRawMode()
382
383	listener := o.GetConfig().Listener
384	if listener != nil {
385		listener.OnChange(nil, 0, 0)
386	}
387
388	o.buf.Refresh(nil) // print prompt
389	o.t.KickRead()
390	select {
391	case r := <-o.outchan:
392		return r, nil
393	case err := <-o.errchan:
394		if e, ok := err.(*InterruptError); ok {
395			return e.Line, ErrInterrupt
396		}
397		return nil, err
398	}
399}
400
401func (o *Operation) PasswordEx(prompt string, l Listener) ([]byte, error) {
402	cfg := o.GenPasswordConfig()
403	cfg.Prompt = prompt
404	cfg.Listener = l
405	return o.PasswordWithConfig(cfg)
406}
407
408func (o *Operation) GenPasswordConfig() *Config {
409	return o.opPassword.PasswordConfig()
410}
411
412func (o *Operation) PasswordWithConfig(cfg *Config) ([]byte, error) {
413	if err := o.opPassword.EnterPasswordMode(cfg); err != nil {
414		return nil, err
415	}
416	defer o.opPassword.ExitPasswordMode()
417	return o.Slice()
418}
419
420func (o *Operation) Password(prompt string) ([]byte, error) {
421	return o.PasswordEx(prompt, nil)
422}
423
424func (o *Operation) SetTitle(t string) {
425	o.w.Write([]byte("\033[2;" + t + "\007"))
426}
427
428func (o *Operation) Slice() ([]byte, error) {
429	r, err := o.Runes()
430	if err != nil {
431		return nil, err
432	}
433	return []byte(string(r)), nil
434}
435
436func (o *Operation) Close() {
437	o.history.Close()
438}
439
440func (o *Operation) SetHistoryPath(path string) {
441	if o.history != nil {
442		o.history.Close()
443	}
444	o.cfg.HistoryFile = path
445	o.history = newOpHistory(o.cfg)
446}
447
448func (o *Operation) IsNormalMode() bool {
449	return !o.IsInCompleteMode() && !o.IsSearchMode()
450}
451
452func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
453	op.m.Lock()
454	defer op.m.Unlock()
455	if op.cfg == cfg {
456		return op.cfg, nil
457	}
458	if err := cfg.Init(); err != nil {
459		return op.cfg, err
460	}
461	old := op.cfg
462	op.cfg = cfg
463	op.SetPrompt(cfg.Prompt)
464	op.SetMaskRune(cfg.MaskRune)
465	op.buf.SetConfig(cfg)
466	width := op.cfg.FuncGetWidth()
467
468	if cfg.opHistory == nil {
469		op.SetHistoryPath(cfg.HistoryFile)
470		cfg.opHistory = op.history
471		cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width)
472	}
473	op.history = cfg.opHistory
474
475	// SetHistoryPath will close opHistory which already exists
476	// so if we use it next time, we need to reopen it by `InitHistory()`
477	op.history.Init()
478
479	if op.cfg.AutoComplete != nil {
480		op.opCompleter = newOpCompleter(op.buf.w, op, width)
481	}
482
483	op.opSearch = cfg.opSearch
484	return old, nil
485}
486
487func (o *Operation) ResetHistory() {
488	o.history.Reset()
489}
490
491// if err is not nil, it just mean it fail to write to file
492// other things goes fine.
493func (o *Operation) SaveHistory(content string) error {
494	return o.history.New([]rune(content))
495}
496
497func (o *Operation) Refresh() {
498	if o.t.IsReading() {
499		o.buf.Refresh(nil)
500	}
501}
502
503func (o *Operation) Clean() {
504	o.buf.Clean()
505}
506
507func FuncListener(f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)) Listener {
508	return &DumpListener{f: f}
509}
510
511type DumpListener struct {
512	f func(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
513}
514
515func (d *DumpListener) OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool) {
516	return d.f(line, pos, key)
517}
518
519type Listener interface {
520	OnChange(line []rune, pos int, key rune) (newLine []rune, newPos int, ok bool)
521}
522
523type Painter interface {
524	Paint(line []rune, pos int) []rune
525}
526
527type defaultPainter struct{}
528
529func (p *defaultPainter) Paint(line []rune, _ int) []rune {
530	return line
531}
532