1package readline
2
3import (
4	"io"
5	"os"
6	"sync"
7	"sync/atomic"
8)
9
10var (
11	Stdin  io.ReadCloser  = os.Stdin
12	Stdout io.WriteCloser = os.Stdout
13	Stderr io.WriteCloser = os.Stderr
14)
15
16var (
17	std     *Instance
18	stdOnce sync.Once
19)
20
21// global instance will not submit history automatic
22func getInstance() *Instance {
23	stdOnce.Do(func() {
24		std, _ = NewEx(&Config{
25			DisableAutoSaveHistory: true,
26		})
27	})
28	return std
29}
30
31// let readline load history from filepath
32// and try to persist history into disk
33// set fp to "" to prevent readline persisting history to disk
34// so the `AddHistory` will return nil error forever.
35func SetHistoryPath(fp string) {
36	ins := getInstance()
37	cfg := ins.Config.Clone()
38	cfg.HistoryFile = fp
39	ins.SetConfig(cfg)
40}
41
42// set auto completer to global instance
43func SetAutoComplete(completer AutoCompleter) {
44	ins := getInstance()
45	cfg := ins.Config.Clone()
46	cfg.AutoComplete = completer
47	ins.SetConfig(cfg)
48}
49
50// add history to global instance manually
51// raise error only if `SetHistoryPath` is set with a non-empty path
52func AddHistory(content string) error {
53	ins := getInstance()
54	return ins.SaveHistory(content)
55}
56
57func Password(prompt string) ([]byte, error) {
58	ins := getInstance()
59	return ins.ReadPassword(prompt)
60}
61
62// readline with global configs
63func Line(prompt string) (string, error) {
64	ins := getInstance()
65	ins.SetPrompt(prompt)
66	return ins.Readline()
67}
68
69type CancelableStdin struct {
70	r      io.Reader
71	mutex  sync.Mutex
72	stop   chan struct{}
73	closed int32
74	notify chan struct{}
75	data   []byte
76	read   int
77	err    error
78}
79
80func NewCancelableStdin(r io.Reader) *CancelableStdin {
81	c := &CancelableStdin{
82		r:      r,
83		notify: make(chan struct{}),
84		stop:   make(chan struct{}),
85	}
86	go c.ioloop()
87	return c
88}
89
90func (c *CancelableStdin) ioloop() {
91loop:
92	for {
93		select {
94		case <-c.notify:
95			c.read, c.err = c.r.Read(c.data)
96			select {
97			case c.notify <- struct{}{}:
98			case <-c.stop:
99				break loop
100			}
101		case <-c.stop:
102			break loop
103		}
104	}
105}
106
107func (c *CancelableStdin) Read(b []byte) (n int, err error) {
108	c.mutex.Lock()
109	defer c.mutex.Unlock()
110	if atomic.LoadInt32(&c.closed) == 1 {
111		return 0, io.EOF
112	}
113
114	c.data = b
115	select {
116	case c.notify <- struct{}{}:
117	case <-c.stop:
118		return 0, io.EOF
119	}
120	select {
121	case <-c.notify:
122		return c.read, c.err
123	case <-c.stop:
124		return 0, io.EOF
125	}
126}
127
128func (c *CancelableStdin) Close() error {
129	if atomic.CompareAndSwapInt32(&c.closed, 0, 1) {
130		close(c.stop)
131	}
132	return nil
133}
134
135// FillableStdin is a stdin reader which can prepend some data before
136// reading into the real stdin
137type FillableStdin struct {
138	sync.Mutex
139	stdin       io.Reader
140	stdinBuffer io.ReadCloser
141	buf         []byte
142	bufErr      error
143}
144
145// NewFillableStdin gives you FillableStdin
146func NewFillableStdin(stdin io.Reader) (io.ReadCloser, io.Writer) {
147	r, w := io.Pipe()
148	s := &FillableStdin{
149		stdinBuffer: r,
150		stdin:       stdin,
151	}
152	s.ioloop()
153	return s, w
154}
155
156func (s *FillableStdin) ioloop() {
157	go func() {
158		for {
159			bufR := make([]byte, 100)
160			var n int
161			n, s.bufErr = s.stdinBuffer.Read(bufR)
162			if s.bufErr != nil {
163				if s.bufErr == io.ErrClosedPipe {
164					break
165				}
166			}
167			s.Lock()
168			s.buf = append(s.buf, bufR[:n]...)
169			s.Unlock()
170		}
171	}()
172}
173
174// Read will read from the local buffer and if no data, read from stdin
175func (s *FillableStdin) Read(p []byte) (n int, err error) {
176	s.Lock()
177	i := len(s.buf)
178	if len(p) < i {
179		i = len(p)
180	}
181	if i > 0 {
182		n := copy(p, s.buf)
183		s.buf = s.buf[:0]
184		cerr := s.bufErr
185		s.bufErr = nil
186		s.Unlock()
187		return n, cerr
188	}
189	s.Unlock()
190	n, err = s.stdin.Read(p)
191	return n, err
192}
193
194func (s *FillableStdin) Close() error {
195	s.stdinBuffer.Close()
196	return nil
197}
198