gcc/ChangeLog:
[official-gcc.git] / libgo / go / exp / terminal / terminal.go
blobc1ed0c0c4437d69f51d28cecc32f58f1fd3d354e
1 // Copyright 2011 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 package terminal
7 import (
8 "io"
9 "sync"
12 // EscapeCodes contains escape sequences that can be written to the terminal in
13 // order to achieve different styles of text.
14 type EscapeCodes struct {
15 // Foreground colors
16 Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
18 // Reset all attributes
19 Reset []byte
22 var vt100EscapeCodes = EscapeCodes{
23 Black: []byte{keyEscape, '[', '3', '0', 'm'},
24 Red: []byte{keyEscape, '[', '3', '1', 'm'},
25 Green: []byte{keyEscape, '[', '3', '2', 'm'},
26 Yellow: []byte{keyEscape, '[', '3', '3', 'm'},
27 Blue: []byte{keyEscape, '[', '3', '4', 'm'},
28 Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
29 Cyan: []byte{keyEscape, '[', '3', '6', 'm'},
30 White: []byte{keyEscape, '[', '3', '7', 'm'},
32 Reset: []byte{keyEscape, '[', '0', 'm'},
35 // Terminal contains the state for running a VT100 terminal that is capable of
36 // reading lines of input.
37 type Terminal struct {
38 // AutoCompleteCallback, if non-null, is called for each keypress
39 // with the full input line and the current position of the cursor.
40 // If it returns a nil newLine, the key press is processed normally.
41 // Otherwise it returns a replacement line and the new cursor position.
42 AutoCompleteCallback func(line []byte, pos, key int) (newLine []byte, newPos int)
44 // Escape contains a pointer to the escape codes for this terminal.
45 // It's always a valid pointer, although the escape codes themselves
46 // may be empty if the terminal doesn't support them.
47 Escape *EscapeCodes
49 // lock protects the terminal and the state in this object from
50 // concurrent processing of a key press and a Write() call.
51 lock sync.Mutex
53 c io.ReadWriter
54 prompt string
56 // line is the current line being entered.
57 line []byte
58 // pos is the logical position of the cursor in line
59 pos int
60 // echo is true if local echo is enabled
61 echo bool
63 // cursorX contains the current X value of the cursor where the left
64 // edge is 0. cursorY contains the row number where the first row of
65 // the current line is 0.
66 cursorX, cursorY int
67 // maxLine is the greatest value of cursorY so far.
68 maxLine int
70 termWidth, termHeight int
72 // outBuf contains the terminal data to be sent.
73 outBuf []byte
74 // remainder contains the remainder of any partial key sequences after
75 // a read. It aliases into inBuf.
76 remainder []byte
77 inBuf [256]byte
80 // NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
81 // a local terminal, that terminal must first have been put into raw mode.
82 // prompt is a string that is written at the start of each input line (i.e.
83 // "> ").
84 func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
85 return &Terminal{
86 Escape: &vt100EscapeCodes,
87 c: c,
88 prompt: prompt,
89 termWidth: 80,
90 termHeight: 24,
91 echo: true,
95 const (
96 keyCtrlD = 4
97 keyEnter = '\r'
98 keyEscape = 27
99 keyBackspace = 127
100 keyUnknown = 256 + iota
101 keyUp
102 keyDown
103 keyLeft
104 keyRight
105 keyAltLeft
106 keyAltRight
109 // bytesToKey tries to parse a key sequence from b. If successful, it returns
110 // the key and the remainder of the input. Otherwise it returns -1.
111 func bytesToKey(b []byte) (int, []byte) {
112 if len(b) == 0 {
113 return -1, nil
116 if b[0] != keyEscape {
117 return int(b[0]), b[1:]
120 if len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
121 switch b[2] {
122 case 'A':
123 return keyUp, b[3:]
124 case 'B':
125 return keyDown, b[3:]
126 case 'C':
127 return keyRight, b[3:]
128 case 'D':
129 return keyLeft, b[3:]
133 if len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
134 switch b[5] {
135 case 'C':
136 return keyAltRight, b[6:]
137 case 'D':
138 return keyAltLeft, b[6:]
142 // If we get here then we have a key that we don't recognise, or a
143 // partial sequence. It's not clear how one should find the end of a
144 // sequence without knowing them all, but it seems that [a-zA-Z] only
145 // appears at the end of a sequence.
146 for i, c := range b[0:] {
147 if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' {
148 return keyUnknown, b[i+1:]
152 return -1, b
155 // queue appends data to the end of t.outBuf
156 func (t *Terminal) queue(data []byte) {
157 t.outBuf = append(t.outBuf, data...)
160 var eraseUnderCursor = []byte{' ', keyEscape, '[', 'D'}
161 var space = []byte{' '}
163 func isPrintable(key int) bool {
164 return key >= 32 && key < 127
167 // moveCursorToPos appends data to t.outBuf which will move the cursor to the
168 // given, logical position in the text.
169 func (t *Terminal) moveCursorToPos(pos int) {
170 if !t.echo {
171 return
174 x := len(t.prompt) + pos
175 y := x / t.termWidth
176 x = x % t.termWidth
178 up := 0
179 if y < t.cursorY {
180 up = t.cursorY - y
183 down := 0
184 if y > t.cursorY {
185 down = y - t.cursorY
188 left := 0
189 if x < t.cursorX {
190 left = t.cursorX - x
193 right := 0
194 if x > t.cursorX {
195 right = x - t.cursorX
198 t.cursorX = x
199 t.cursorY = y
200 t.move(up, down, left, right)
203 func (t *Terminal) move(up, down, left, right int) {
204 movement := make([]byte, 3*(up+down+left+right))
205 m := movement
206 for i := 0; i < up; i++ {
207 m[0] = keyEscape
208 m[1] = '['
209 m[2] = 'A'
210 m = m[3:]
212 for i := 0; i < down; i++ {
213 m[0] = keyEscape
214 m[1] = '['
215 m[2] = 'B'
216 m = m[3:]
218 for i := 0; i < left; i++ {
219 m[0] = keyEscape
220 m[1] = '['
221 m[2] = 'D'
222 m = m[3:]
224 for i := 0; i < right; i++ {
225 m[0] = keyEscape
226 m[1] = '['
227 m[2] = 'C'
228 m = m[3:]
231 t.queue(movement)
234 func (t *Terminal) clearLineToRight() {
235 op := []byte{keyEscape, '[', 'K'}
236 t.queue(op)
239 const maxLineLength = 4096
241 // handleKey processes the given key and, optionally, returns a line of text
242 // that the user has entered.
243 func (t *Terminal) handleKey(key int) (line string, ok bool) {
244 switch key {
245 case keyBackspace:
246 if t.pos == 0 {
247 return
249 t.pos--
250 t.moveCursorToPos(t.pos)
252 copy(t.line[t.pos:], t.line[1+t.pos:])
253 t.line = t.line[:len(t.line)-1]
254 if t.echo {
255 t.writeLine(t.line[t.pos:])
257 t.queue(eraseUnderCursor)
258 t.moveCursorToPos(t.pos)
259 case keyAltLeft:
260 // move left by a word.
261 if t.pos == 0 {
262 return
264 t.pos--
265 for t.pos > 0 {
266 if t.line[t.pos] != ' ' {
267 break
269 t.pos--
271 for t.pos > 0 {
272 if t.line[t.pos] == ' ' {
273 t.pos++
274 break
276 t.pos--
278 t.moveCursorToPos(t.pos)
279 case keyAltRight:
280 // move right by a word.
281 for t.pos < len(t.line) {
282 if t.line[t.pos] == ' ' {
283 break
285 t.pos++
287 for t.pos < len(t.line) {
288 if t.line[t.pos] != ' ' {
289 break
291 t.pos++
293 t.moveCursorToPos(t.pos)
294 case keyLeft:
295 if t.pos == 0 {
296 return
298 t.pos--
299 t.moveCursorToPos(t.pos)
300 case keyRight:
301 if t.pos == len(t.line) {
302 return
304 t.pos++
305 t.moveCursorToPos(t.pos)
306 case keyEnter:
307 t.moveCursorToPos(len(t.line))
308 t.queue([]byte("\r\n"))
309 line = string(t.line)
310 ok = true
311 t.line = t.line[:0]
312 t.pos = 0
313 t.cursorX = 0
314 t.cursorY = 0
315 t.maxLine = 0
316 default:
317 if t.AutoCompleteCallback != nil {
318 t.lock.Unlock()
319 newLine, newPos := t.AutoCompleteCallback(t.line, t.pos, key)
320 t.lock.Lock()
322 if newLine != nil {
323 if t.echo {
324 t.moveCursorToPos(0)
325 t.writeLine(newLine)
326 for i := len(newLine); i < len(t.line); i++ {
327 t.writeLine(space)
329 t.moveCursorToPos(newPos)
331 t.line = newLine
332 t.pos = newPos
333 return
336 if !isPrintable(key) {
337 return
339 if len(t.line) == maxLineLength {
340 return
342 if len(t.line) == cap(t.line) {
343 newLine := make([]byte, len(t.line), 2*(1+len(t.line)))
344 copy(newLine, t.line)
345 t.line = newLine
347 t.line = t.line[:len(t.line)+1]
348 copy(t.line[t.pos+1:], t.line[t.pos:])
349 t.line[t.pos] = byte(key)
350 if t.echo {
351 t.writeLine(t.line[t.pos:])
353 t.pos++
354 t.moveCursorToPos(t.pos)
356 return
359 func (t *Terminal) writeLine(line []byte) {
360 for len(line) != 0 {
361 remainingOnLine := t.termWidth - t.cursorX
362 todo := len(line)
363 if todo > remainingOnLine {
364 todo = remainingOnLine
366 t.queue(line[:todo])
367 t.cursorX += todo
368 line = line[todo:]
370 if t.cursorX == t.termWidth {
371 t.cursorX = 0
372 t.cursorY++
373 if t.cursorY > t.maxLine {
374 t.maxLine = t.cursorY
380 func (t *Terminal) Write(buf []byte) (n int, err error) {
381 t.lock.Lock()
382 defer t.lock.Unlock()
384 if t.cursorX == 0 && t.cursorY == 0 {
385 // This is the easy case: there's nothing on the screen that we
386 // have to move out of the way.
387 return t.c.Write(buf)
390 // We have a prompt and possibly user input on the screen. We
391 // have to clear it first.
392 t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
393 t.cursorX = 0
394 t.clearLineToRight()
396 for t.cursorY > 0 {
397 t.move(1 /* up */, 0, 0, 0)
398 t.cursorY--
399 t.clearLineToRight()
402 if _, err = t.c.Write(t.outBuf); err != nil {
403 return
405 t.outBuf = t.outBuf[:0]
407 if n, err = t.c.Write(buf); err != nil {
408 return
411 t.queue([]byte(t.prompt))
412 chars := len(t.prompt)
413 if t.echo {
414 t.queue(t.line)
415 chars += len(t.line)
417 t.cursorX = chars % t.termWidth
418 t.cursorY = chars / t.termWidth
419 t.moveCursorToPos(t.pos)
421 if _, err = t.c.Write(t.outBuf); err != nil {
422 return
424 t.outBuf = t.outBuf[:0]
425 return
428 // ReadPassword temporarily changes the prompt and reads a password, without
429 // echo, from the terminal.
430 func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
431 t.lock.Lock()
432 defer t.lock.Unlock()
434 oldPrompt := t.prompt
435 t.prompt = prompt
436 t.echo = false
438 line, err = t.readLine()
440 t.prompt = oldPrompt
441 t.echo = true
443 return
446 // ReadLine returns a line of input from the terminal.
447 func (t *Terminal) ReadLine() (line string, err error) {
448 t.lock.Lock()
449 defer t.lock.Unlock()
451 return t.readLine()
454 func (t *Terminal) readLine() (line string, err error) {
455 // t.lock must be held at this point
457 if t.cursorX == 0 && t.cursorY == 0 {
458 t.writeLine([]byte(t.prompt))
459 t.c.Write(t.outBuf)
460 t.outBuf = t.outBuf[:0]
463 for {
464 rest := t.remainder
465 lineOk := false
466 for !lineOk {
467 var key int
468 key, rest = bytesToKey(rest)
469 if key < 0 {
470 break
472 if key == keyCtrlD {
473 return "", io.EOF
475 line, lineOk = t.handleKey(key)
477 if len(rest) > 0 {
478 n := copy(t.inBuf[:], rest)
479 t.remainder = t.inBuf[:n]
480 } else {
481 t.remainder = nil
483 t.c.Write(t.outBuf)
484 t.outBuf = t.outBuf[:0]
485 if lineOk {
486 return
489 // t.remainder is a slice at the beginning of t.inBuf
490 // containing a partial key sequence
491 readBuf := t.inBuf[len(t.remainder):]
492 var n int
494 t.lock.Unlock()
495 n, err = t.c.Read(readBuf)
496 t.lock.Lock()
498 if err != nil {
499 return
502 t.remainder = t.inBuf[:n+len(t.remainder)]
504 panic("unreachable")
507 // SetPrompt sets the prompt to be used when reading subsequent lines.
508 func (t *Terminal) SetPrompt(prompt string) {
509 t.lock.Lock()
510 defer t.lock.Unlock()
512 t.prompt = prompt
515 func (t *Terminal) SetSize(width, height int) {
516 t.lock.Lock()
517 defer t.lock.Unlock()
519 t.termWidth, t.termHeight = width, height