1package readline
2
3import (
4	"bufio"
5	"fmt"
6	"io"
7	"strings"
8	"sync"
9	"sync/atomic"
10)
11
12type Terminal struct {
13	m         sync.Mutex
14	cfg       *Config
15	outchan   chan rune
16	closed    int32
17	stopChan  chan struct{}
18	kickChan  chan struct{}
19	wg        sync.WaitGroup
20	isReading int32
21	sleeping  int32
22
23	sizeChan chan string
24}
25
26func NewTerminal(cfg *Config) (*Terminal, error) {
27	if err := cfg.Init(); err != nil {
28		return nil, err
29	}
30	t := &Terminal{
31		cfg:      cfg,
32		kickChan: make(chan struct{}, 1),
33		outchan:  make(chan rune),
34		stopChan: make(chan struct{}, 1),
35		sizeChan: make(chan string, 1),
36	}
37
38	go t.ioloop()
39	return t, nil
40}
41
42// SleepToResume will sleep myself, and return only if I'm resumed.
43func (t *Terminal) SleepToResume() {
44	if !atomic.CompareAndSwapInt32(&t.sleeping, 0, 1) {
45		return
46	}
47	defer atomic.StoreInt32(&t.sleeping, 0)
48
49	t.ExitRawMode()
50	ch := WaitForResume()
51	SuspendMe()
52	<-ch
53	t.EnterRawMode()
54}
55
56func (t *Terminal) EnterRawMode() (err error) {
57	return t.cfg.FuncMakeRaw()
58}
59
60func (t *Terminal) ExitRawMode() (err error) {
61	return t.cfg.FuncExitRaw()
62}
63
64func (t *Terminal) Write(b []byte) (int, error) {
65	return t.cfg.Stdout.Write(b)
66}
67
68// WriteStdin prefill the next Stdin fetch
69// Next time you call ReadLine() this value will be writen before the user input
70func (t *Terminal) WriteStdin(b []byte) (int, error) {
71	return t.cfg.StdinWriter.Write(b)
72}
73
74type termSize struct {
75	left int
76	top  int
77}
78
79func (t *Terminal) GetOffset(f func(offset string)) {
80	go func() {
81		f(<-t.sizeChan)
82	}()
83	t.Write([]byte("\033[6n"))
84}
85
86func (t *Terminal) Print(s string) {
87	fmt.Fprintf(t.cfg.Stdout, "%s", s)
88}
89
90func (t *Terminal) PrintRune(r rune) {
91	fmt.Fprintf(t.cfg.Stdout, "%c", r)
92}
93
94func (t *Terminal) Readline() *Operation {
95	return NewOperation(t, t.cfg)
96}
97
98// return rune(0) if meet EOF
99func (t *Terminal) ReadRune() rune {
100	ch, ok := <-t.outchan
101	if !ok {
102		return rune(0)
103	}
104	return ch
105}
106
107func (t *Terminal) IsReading() bool {
108	return atomic.LoadInt32(&t.isReading) == 1
109}
110
111func (t *Terminal) KickRead() {
112	select {
113	case t.kickChan <- struct{}{}:
114	default:
115	}
116}
117
118func (t *Terminal) ioloop() {
119	t.wg.Add(1)
120	defer func() {
121		t.wg.Done()
122		close(t.outchan)
123	}()
124
125	var (
126		isEscape       bool
127		isEscapeEx     bool
128		expectNextChar bool
129	)
130
131	buf := bufio.NewReader(t.getStdin())
132	for {
133		if !expectNextChar {
134			atomic.StoreInt32(&t.isReading, 0)
135			select {
136			case <-t.kickChan:
137				atomic.StoreInt32(&t.isReading, 1)
138			case <-t.stopChan:
139				return
140			}
141		}
142		expectNextChar = false
143		r, _, err := buf.ReadRune()
144		if err != nil {
145			if strings.Contains(err.Error(), "interrupted system call") {
146				expectNextChar = true
147				continue
148			}
149			break
150		}
151
152		if isEscape {
153			isEscape = false
154			if r == CharEscapeEx {
155				expectNextChar = true
156				isEscapeEx = true
157				continue
158			}
159			r = escapeKey(r, buf)
160		} else if isEscapeEx {
161			isEscapeEx = false
162			if key := readEscKey(r, buf); key != nil {
163				r = escapeExKey(key)
164				// offset
165				if key.typ == 'R' {
166					if _, _, ok := key.Get2(); ok {
167						select {
168						case t.sizeChan <- key.attr:
169						default:
170						}
171					}
172					expectNextChar = true
173					continue
174				}
175			}
176			if r == 0 {
177				expectNextChar = true
178				continue
179			}
180		}
181
182		expectNextChar = true
183		switch r {
184		case CharEsc:
185			if t.cfg.VimMode {
186				t.outchan <- r
187				break
188			}
189			isEscape = true
190		case CharInterrupt, CharEnter, CharCtrlJ, CharEOT:
191			expectNextChar = false
192			fallthrough
193		default:
194			t.outchan <- r
195		}
196	}
197
198}
199
200func (t *Terminal) Bell() {
201	fmt.Fprintf(t, "%c", CharBell)
202}
203
204func (t *Terminal) Close() error {
205	if atomic.SwapInt32(&t.closed, 1) != 0 {
206		return nil
207	}
208	if closer, ok := t.cfg.Stdin.(io.Closer); ok {
209		closer.Close()
210	}
211	close(t.stopChan)
212	t.wg.Wait()
213	return t.ExitRawMode()
214}
215
216func (t *Terminal) GetConfig() *Config {
217	t.m.Lock()
218	cfg := *t.cfg
219	t.m.Unlock()
220	return &cfg
221}
222
223func (t *Terminal) getStdin() io.Reader {
224	t.m.Lock()
225	r := t.cfg.Stdin
226	t.m.Unlock()
227	return r
228}
229
230func (t *Terminal) SetConfig(c *Config) error {
231	if err := c.Init(); err != nil {
232		return err
233	}
234	t.m.Lock()
235	t.cfg = c
236	t.m.Unlock()
237	return nil
238}
239