OpenCores
URL https://opencores.org/ocsvn/openrisc/openrisc/trunk

Subversion Repositories openrisc

[/] [openrisc/] [trunk/] [gnu-dev/] [or1k-gcc/] [libgo/] [go/] [exp/] [terminal/] [terminal.go] - Blame information for rev 747

Details | Compare with Previous | View Log

Line No. Rev Author Line
1 747 jeremybenn
// 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.
4
 
5
package terminal
6
 
7
import (
8
        "io"
9
        "sync"
10
)
11
 
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
17
 
18
        // Reset all attributes
19
        Reset []byte
20
}
21
 
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'},
31
 
32
        Reset: []byte{keyEscape, '[', '0', 'm'},
33
}
34
 
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)
43
 
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
48
 
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
52
 
53
        c      io.ReadWriter
54
        prompt string
55
 
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
62
 
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
69
 
70
        termWidth, termHeight int
71
 
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
78
}
79
 
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,
92
        }
93
}
94
 
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
107
)
108
 
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
114
        }
115
 
116
        if b[0] != keyEscape {
117
                return int(b[0]), b[1:]
118
        }
119
 
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:]
130
                }
131
        }
132
 
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:]
139
                }
140
        }
141
 
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:]
149
                }
150
        }
151
 
152
        return -1, b
153
}
154
 
155
// queue appends data to the end of t.outBuf
156
func (t *Terminal) queue(data []byte) {
157
        t.outBuf = append(t.outBuf, data...)
158
}
159
 
160
var eraseUnderCursor = []byte{' ', keyEscape, '[', 'D'}
161
var space = []byte{' '}
162
 
163
func isPrintable(key int) bool {
164
        return key >= 32 && key < 127
165
}
166
 
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
172
        }
173
 
174
        x := len(t.prompt) + pos
175
        y := x / t.termWidth
176
        x = x % t.termWidth
177
 
178
        up := 0
179
        if y < t.cursorY {
180
                up = t.cursorY - y
181
        }
182
 
183
        down := 0
184
        if y > t.cursorY {
185
                down = y - t.cursorY
186
        }
187
 
188
        left := 0
189
        if x < t.cursorX {
190
                left = t.cursorX - x
191
        }
192
 
193
        right := 0
194
        if x > t.cursorX {
195
                right = x - t.cursorX
196
        }
197
 
198
        t.cursorX = x
199
        t.cursorY = y
200
        t.move(up, down, left, right)
201
}
202
 
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:]
211
        }
212
        for i := 0; i < down; i++ {
213
                m[0] = keyEscape
214
                m[1] = '['
215
                m[2] = 'B'
216
                m = m[3:]
217
        }
218
        for i := 0; i < left; i++ {
219
                m[0] = keyEscape
220
                m[1] = '['
221
                m[2] = 'D'
222
                m = m[3:]
223
        }
224
        for i := 0; i < right; i++ {
225
                m[0] = keyEscape
226
                m[1] = '['
227
                m[2] = 'C'
228
                m = m[3:]
229
        }
230
 
231
        t.queue(movement)
232
}
233
 
234
func (t *Terminal) clearLineToRight() {
235
        op := []byte{keyEscape, '[', 'K'}
236
        t.queue(op)
237
}
238
 
239
const maxLineLength = 4096
240
 
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
248
                }
249
                t.pos--
250
                t.moveCursorToPos(t.pos)
251
 
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:])
256
                }
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
263
                }
264
                t.pos--
265
                for t.pos > 0 {
266
                        if t.line[t.pos] != ' ' {
267
                                break
268
                        }
269
                        t.pos--
270
                }
271
                for t.pos > 0 {
272
                        if t.line[t.pos] == ' ' {
273
                                t.pos++
274
                                break
275
                        }
276
                        t.pos--
277
                }
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
284
                        }
285
                        t.pos++
286
                }
287
                for t.pos < len(t.line) {
288
                        if t.line[t.pos] != ' ' {
289
                                break
290
                        }
291
                        t.pos++
292
                }
293
                t.moveCursorToPos(t.pos)
294
        case keyLeft:
295
                if t.pos == 0 {
296
                        return
297
                }
298
                t.pos--
299
                t.moveCursorToPos(t.pos)
300
        case keyRight:
301
                if t.pos == len(t.line) {
302
                        return
303
                }
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()
321
 
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)
328
                                        }
329
                                        t.moveCursorToPos(newPos)
330
                                }
331
                                t.line = newLine
332
                                t.pos = newPos
333
                                return
334
                        }
335
                }
336
                if !isPrintable(key) {
337
                        return
338
                }
339
                if len(t.line) == maxLineLength {
340
                        return
341
                }
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
346
                }
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:])
352
                }
353
                t.pos++
354
                t.moveCursorToPos(t.pos)
355
        }
356
        return
357
}
358
 
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
365
                }
366
                t.queue(line[:todo])
367
                t.cursorX += todo
368
                line = line[todo:]
369
 
370
                if t.cursorX == t.termWidth {
371
                        t.cursorX = 0
372
                        t.cursorY++
373
                        if t.cursorY > t.maxLine {
374
                                t.maxLine = t.cursorY
375
                        }
376
                }
377
        }
378
}
379
 
380
func (t *Terminal) Write(buf []byte) (n int, err error) {
381
        t.lock.Lock()
382
        defer t.lock.Unlock()
383
 
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)
388
        }
389
 
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()
395
 
396
        for t.cursorY > 0 {
397
                t.move(1, /* up */ 0, 0, 0)
398
                t.cursorY--
399
                t.clearLineToRight()
400
        }
401
 
402
        if _, err = t.c.Write(t.outBuf); err != nil {
403
                return
404
        }
405
        t.outBuf = t.outBuf[:0]
406
 
407
        if n, err = t.c.Write(buf); err != nil {
408
                return
409
        }
410
 
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)
416
        }
417
        t.cursorX = chars % t.termWidth
418
        t.cursorY = chars / t.termWidth
419
        t.moveCursorToPos(t.pos)
420
 
421
        if _, err = t.c.Write(t.outBuf); err != nil {
422
                return
423
        }
424
        t.outBuf = t.outBuf[:0]
425
        return
426
}
427
 
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()
433
 
434
        oldPrompt := t.prompt
435
        t.prompt = prompt
436
        t.echo = false
437
 
438
        line, err = t.readLine()
439
 
440
        t.prompt = oldPrompt
441
        t.echo = true
442
 
443
        return
444
}
445
 
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()
450
 
451
        return t.readLine()
452
}
453
 
454
func (t *Terminal) readLine() (line string, err error) {
455
        // t.lock must be held at this point
456
 
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]
461
        }
462
 
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
471
                        }
472
                        if key == keyCtrlD {
473
                                return "", io.EOF
474
                        }
475
                        line, lineOk = t.handleKey(key)
476
                }
477
                if len(rest) > 0 {
478
                        n := copy(t.inBuf[:], rest)
479
                        t.remainder = t.inBuf[:n]
480
                } else {
481
                        t.remainder = nil
482
                }
483
                t.c.Write(t.outBuf)
484
                t.outBuf = t.outBuf[:0]
485
                if lineOk {
486
                        return
487
                }
488
 
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
493
 
494
                t.lock.Unlock()
495
                n, err = t.c.Read(readBuf)
496
                t.lock.Lock()
497
 
498
                if err != nil {
499
                        return
500
                }
501
 
502
                t.remainder = t.inBuf[:n+len(t.remainder)]
503
        }
504
        panic("unreachable")
505
}
506
 
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()
511
 
512
        t.prompt = prompt
513
}
514
 
515
func (t *Terminal) SetSize(width, height int) {
516
        t.lock.Lock()
517
        defer t.lock.Unlock()
518
 
519
        t.termWidth, t.termHeight = width, height
520
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.