1// +build linux darwin openbsd freebsd netbsd
2
3package liner
4
5import (
6	"bufio"
7	"errors"
8	"os"
9	"os/signal"
10	"strconv"
11	"strings"
12	"syscall"
13	"time"
14)
15
16type nexter struct {
17	r   rune
18	err error
19}
20
21// State represents an open terminal
22type State struct {
23	commonState
24	origMode    termios
25	defaultMode termios
26	next        <-chan nexter
27	winch       chan os.Signal
28	pending     []rune
29	useCHA      bool
30}
31
32// NewLiner initializes a new *State, and sets the terminal into raw mode. To
33// restore the terminal to its previous state, call State.Close().
34func NewLiner() *State {
35	var s State
36	s.r = bufio.NewReader(os.Stdin)
37
38	s.terminalSupported = TerminalSupported()
39	if m, err := TerminalMode(); err == nil {
40		s.origMode = *m.(*termios)
41	} else {
42		s.inputRedirected = true
43	}
44	if _, err := getMode(syscall.Stdout); err != 0 {
45		s.outputRedirected = true
46	}
47	if s.inputRedirected && s.outputRedirected {
48		s.terminalSupported = false
49	}
50	if s.terminalSupported && !s.inputRedirected && !s.outputRedirected {
51		mode := s.origMode
52		mode.Iflag &^= icrnl | inpck | istrip | ixon
53		mode.Cflag |= cs8
54		mode.Lflag &^= syscall.ECHO | icanon | iexten
55		mode.ApplyMode()
56
57		winch := make(chan os.Signal, 1)
58		signal.Notify(winch, syscall.SIGWINCH)
59		s.winch = winch
60
61		s.checkOutput()
62	}
63
64	if !s.outputRedirected {
65		s.outputRedirected = !s.getColumns()
66	}
67
68	return &s
69}
70
71var errTimedOut = errors.New("timeout")
72
73func (s *State) startPrompt() {
74	if s.terminalSupported {
75		if m, err := TerminalMode(); err == nil {
76			s.defaultMode = *m.(*termios)
77			mode := s.defaultMode
78			mode.Lflag &^= isig
79			mode.ApplyMode()
80		}
81	}
82	s.restartPrompt()
83}
84
85func (s *State) inputWaiting() bool {
86	return len(s.next) > 0
87}
88
89func (s *State) restartPrompt() {
90	next := make(chan nexter, 200)
91	go func() {
92		for {
93			var n nexter
94			n.r, _, n.err = s.r.ReadRune()
95			next <- n
96			// Shut down nexter loop when an end condition has been reached
97			if n.err != nil || n.r == '\n' || n.r == '\r' || n.r == ctrlC || n.r == ctrlD {
98				close(next)
99				return
100			}
101		}
102	}()
103	s.next = next
104}
105
106func (s *State) stopPrompt() {
107	if s.terminalSupported {
108		s.defaultMode.ApplyMode()
109	}
110}
111
112func (s *State) nextPending(timeout <-chan time.Time) (rune, error) {
113	select {
114	case thing, ok := <-s.next:
115		if !ok {
116			return 0, ErrInternal
117		}
118		if thing.err != nil {
119			return 0, thing.err
120		}
121		s.pending = append(s.pending, thing.r)
122		return thing.r, nil
123	case <-timeout:
124		rv := s.pending[0]
125		s.pending = s.pending[1:]
126		return rv, errTimedOut
127	}
128}
129
130func (s *State) readNext() (interface{}, error) {
131	if len(s.pending) > 0 {
132		rv := s.pending[0]
133		s.pending = s.pending[1:]
134		return rv, nil
135	}
136	var r rune
137	select {
138	case thing, ok := <-s.next:
139		if !ok {
140			return 0, ErrInternal
141		}
142		if thing.err != nil {
143			return nil, thing.err
144		}
145		r = thing.r
146	case <-s.winch:
147		s.getColumns()
148		return winch, nil
149	}
150	if r != esc {
151		return r, nil
152	}
153	s.pending = append(s.pending, r)
154
155	// Wait at most 50 ms for the rest of the escape sequence
156	// If nothing else arrives, it was an actual press of the esc key
157	timeout := time.After(50 * time.Millisecond)
158	flag, err := s.nextPending(timeout)
159	if err != nil {
160		if err == errTimedOut {
161			return flag, nil
162		}
163		return unknown, err
164	}
165
166	switch flag {
167	case '[':
168		code, err := s.nextPending(timeout)
169		if err != nil {
170			if err == errTimedOut {
171				return code, nil
172			}
173			return unknown, err
174		}
175		switch code {
176		case 'A':
177			s.pending = s.pending[:0] // escape code complete
178			return up, nil
179		case 'B':
180			s.pending = s.pending[:0] // escape code complete
181			return down, nil
182		case 'C':
183			s.pending = s.pending[:0] // escape code complete
184			return right, nil
185		case 'D':
186			s.pending = s.pending[:0] // escape code complete
187			return left, nil
188		case 'F':
189			s.pending = s.pending[:0] // escape code complete
190			return end, nil
191		case 'H':
192			s.pending = s.pending[:0] // escape code complete
193			return home, nil
194		case 'Z':
195			s.pending = s.pending[:0] // escape code complete
196			return shiftTab, nil
197		case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
198			num := []rune{code}
199			for {
200				code, err := s.nextPending(timeout)
201				if err != nil {
202					if err == errTimedOut {
203						return code, nil
204					}
205					return nil, err
206				}
207				switch code {
208				case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
209					num = append(num, code)
210				case ';':
211					// Modifier code to follow
212					// This only supports Ctrl-left and Ctrl-right for now
213					x, _ := strconv.ParseInt(string(num), 10, 32)
214					if x != 1 {
215						// Can't be left or right
216						rv := s.pending[0]
217						s.pending = s.pending[1:]
218						return rv, nil
219					}
220					num = num[:0]
221					for {
222						code, err = s.nextPending(timeout)
223						if err != nil {
224							if err == errTimedOut {
225								rv := s.pending[0]
226								s.pending = s.pending[1:]
227								return rv, nil
228							}
229							return nil, err
230						}
231						switch code {
232						case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
233							num = append(num, code)
234						case 'C', 'D':
235							// right, left
236							mod, _ := strconv.ParseInt(string(num), 10, 32)
237							if mod != 5 {
238								// Not bare Ctrl
239								rv := s.pending[0]
240								s.pending = s.pending[1:]
241								return rv, nil
242							}
243							s.pending = s.pending[:0] // escape code complete
244							if code == 'C' {
245								return wordRight, nil
246							}
247							return wordLeft, nil
248						default:
249							// Not left or right
250							rv := s.pending[0]
251							s.pending = s.pending[1:]
252							return rv, nil
253						}
254					}
255				case '~':
256					s.pending = s.pending[:0] // escape code complete
257					x, _ := strconv.ParseInt(string(num), 10, 32)
258					switch x {
259					case 2:
260						return insert, nil
261					case 3:
262						return del, nil
263					case 5:
264						return pageUp, nil
265					case 6:
266						return pageDown, nil
267					case 1, 7:
268						return home, nil
269					case 4, 8:
270						return end, nil
271					case 15:
272						return f5, nil
273					case 17:
274						return f6, nil
275					case 18:
276						return f7, nil
277					case 19:
278						return f8, nil
279					case 20:
280						return f9, nil
281					case 21:
282						return f10, nil
283					case 23:
284						return f11, nil
285					case 24:
286						return f12, nil
287					default:
288						return unknown, nil
289					}
290				default:
291					// unrecognized escape code
292					rv := s.pending[0]
293					s.pending = s.pending[1:]
294					return rv, nil
295				}
296			}
297		}
298
299	case 'O':
300		code, err := s.nextPending(timeout)
301		if err != nil {
302			if err == errTimedOut {
303				return code, nil
304			}
305			return nil, err
306		}
307		s.pending = s.pending[:0] // escape code complete
308		switch code {
309		case 'c':
310			return wordRight, nil
311		case 'd':
312			return wordLeft, nil
313		case 'H':
314			return home, nil
315		case 'F':
316			return end, nil
317		case 'P':
318			return f1, nil
319		case 'Q':
320			return f2, nil
321		case 'R':
322			return f3, nil
323		case 'S':
324			return f4, nil
325		default:
326			return unknown, nil
327		}
328	case 'b':
329		s.pending = s.pending[:0] // escape code complete
330		return altB, nil
331	case 'd':
332		s.pending = s.pending[:0] // escape code complete
333		return altD, nil
334	case bs:
335		s.pending = s.pending[:0] // escape code complete
336		return altBs, nil
337	case 'f':
338		s.pending = s.pending[:0] // escape code complete
339		return altF, nil
340	case 'y':
341		s.pending = s.pending[:0] // escape code complete
342		return altY, nil
343	default:
344		rv := s.pending[0]
345		s.pending = s.pending[1:]
346		return rv, nil
347	}
348
349	// not reached
350	return r, nil
351}
352
353// Close returns the terminal to its previous mode
354func (s *State) Close() error {
355	signal.Stop(s.winch)
356	if !s.inputRedirected {
357		s.origMode.ApplyMode()
358	}
359	return nil
360}
361
362// TerminalSupported returns true if the current terminal supports
363// line editing features, and false if liner will use the 'dumb'
364// fallback for input.
365// Note that TerminalSupported does not check all factors that may
366// cause liner to not fully support the terminal (such as stdin redirection)
367func TerminalSupported() bool {
368	bad := map[string]bool{"": true, "dumb": true, "cons25": true}
369	return !bad[strings.ToLower(os.Getenv("TERM"))]
370}
371