1package readline
2
3import (
4	"bufio"
5	"bytes"
6	"container/list"
7	"fmt"
8	"os"
9	"strconv"
10	"strings"
11	"sync"
12	"time"
13	"unicode"
14)
15
16var (
17	isWindows = false
18)
19
20const (
21	CharLineStart = 1
22	CharBackward  = 2
23	CharInterrupt = 3
24	CharDelete    = 4
25	CharLineEnd   = 5
26	CharForward   = 6
27	CharBell      = 7
28	CharCtrlH     = 8
29	CharTab       = 9
30	CharCtrlJ     = 10
31	CharKill      = 11
32	CharCtrlL     = 12
33	CharEnter     = 13
34	CharNext      = 14
35	CharPrev      = 16
36	CharBckSearch = 18
37	CharFwdSearch = 19
38	CharTranspose = 20
39	CharCtrlU     = 21
40	CharCtrlW     = 23
41	CharCtrlY     = 25
42	CharCtrlZ     = 26
43	CharEsc       = 27
44	CharEscapeEx  = 91
45	CharBackspace = 127
46)
47
48const (
49	MetaBackward rune = -iota - 1
50	MetaForward
51	MetaDelete
52	MetaBackspace
53	MetaTranspose
54)
55
56// WaitForResume need to call before current process got suspend.
57// It will run a ticker until a long duration is occurs,
58// which means this process is resumed.
59func WaitForResume() chan struct{} {
60	ch := make(chan struct{})
61	var wg sync.WaitGroup
62	wg.Add(1)
63	go func() {
64		ticker := time.NewTicker(10 * time.Millisecond)
65		t := time.Now()
66		wg.Done()
67		for {
68			now := <-ticker.C
69			if now.Sub(t) > 100*time.Millisecond {
70				break
71			}
72			t = now
73		}
74		ticker.Stop()
75		ch <- struct{}{}
76	}()
77	wg.Wait()
78	return ch
79}
80
81func Restore(fd int, state *State) error {
82	err := restoreTerm(fd, state)
83	if err != nil {
84		// errno 0 means everything is ok :)
85		if err.Error() == "errno 0" {
86			return nil
87		} else {
88			return err
89		}
90	}
91	return nil
92}
93
94func IsPrintable(key rune) bool {
95	isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
96	return key >= 32 && !isInSurrogateArea
97}
98
99// translate Esc[X
100func escapeExKey(key *escapeKeyPair) rune {
101	var r rune
102	switch key.typ {
103	case 'D':
104		r = CharBackward
105	case 'C':
106		r = CharForward
107	case 'A':
108		r = CharPrev
109	case 'B':
110		r = CharNext
111	case 'H':
112		r = CharLineStart
113	case 'F':
114		r = CharLineEnd
115	case '~':
116		if key.attr == "3" {
117			r = CharDelete
118		}
119	default:
120	}
121	return r
122}
123
124type escapeKeyPair struct {
125	attr string
126	typ  rune
127}
128
129func (e *escapeKeyPair) Get2() (int, int, bool) {
130	sp := strings.Split(e.attr, ";")
131	if len(sp) < 2 {
132		return -1, -1, false
133	}
134	s1, err := strconv.Atoi(sp[0])
135	if err != nil {
136		return -1, -1, false
137	}
138	s2, err := strconv.Atoi(sp[1])
139	if err != nil {
140		return -1, -1, false
141	}
142	return s1, s2, true
143}
144
145func readEscKey(r rune, reader *bufio.Reader) *escapeKeyPair {
146	p := escapeKeyPair{}
147	buf := bytes.NewBuffer(nil)
148	for {
149		if r == ';' {
150		} else if unicode.IsNumber(r) {
151		} else {
152			p.typ = r
153			break
154		}
155		buf.WriteRune(r)
156		r, _, _ = reader.ReadRune()
157	}
158	p.attr = buf.String()
159	return &p
160}
161
162// translate EscX to Meta+X
163func escapeKey(r rune, reader *bufio.Reader) rune {
164	switch r {
165	case 'b':
166		r = MetaBackward
167	case 'f':
168		r = MetaForward
169	case 'd':
170		r = MetaDelete
171	case CharTranspose:
172		r = MetaTranspose
173	case CharBackspace:
174		r = MetaBackspace
175	case 'O':
176		d, _, _ := reader.ReadRune()
177		switch d {
178		case 'H':
179			r = CharLineStart
180		case 'F':
181			r = CharLineEnd
182		default:
183			reader.UnreadRune()
184		}
185	case CharEsc:
186
187	}
188	return r
189}
190
191func SplitByLine(start, screenWidth int, rs []rune) []string {
192	var ret []string
193	buf := bytes.NewBuffer(nil)
194	currentWidth := start
195	for _, r := range rs {
196		w := runes.Width(r)
197		currentWidth += w
198		buf.WriteRune(r)
199		if currentWidth >= screenWidth {
200			ret = append(ret, buf.String())
201			buf.Reset()
202			currentWidth = 0
203		}
204	}
205	ret = append(ret, buf.String())
206	return ret
207}
208
209// calculate how many lines for N character
210func LineCount(screenWidth, w int) int {
211	r := w / screenWidth
212	if w%screenWidth != 0 {
213		r++
214	}
215	return r
216}
217
218func IsWordBreak(i rune) bool {
219	switch {
220	case i >= 'a' && i <= 'z':
221	case i >= 'A' && i <= 'Z':
222	case i >= '0' && i <= '9':
223	default:
224		return true
225	}
226	return false
227}
228
229func GetInt(s []string, def int) int {
230	if len(s) == 0 {
231		return def
232	}
233	c, err := strconv.Atoi(s[0])
234	if err != nil {
235		return def
236	}
237	return c
238}
239
240type RawMode struct {
241	state *State
242}
243
244func (r *RawMode) Enter() (err error) {
245	r.state, err = MakeRaw(GetStdin())
246	return err
247}
248
249func (r *RawMode) Exit() error {
250	if r.state == nil {
251		return nil
252	}
253	return Restore(GetStdin(), r.state)
254}
255
256// -----------------------------------------------------------------------------
257
258func sleep(n int) {
259	Debug(n)
260	time.Sleep(2000 * time.Millisecond)
261}
262
263// print a linked list to Debug()
264func debugList(l *list.List) {
265	idx := 0
266	for e := l.Front(); e != nil; e = e.Next() {
267		Debug(idx, fmt.Sprintf("%+v", e.Value))
268		idx++
269	}
270}
271
272// append log info to another file
273func Debug(o ...interface{}) {
274	f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
275	fmt.Fprintln(f, o...)
276	f.Close()
277}
278