~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/golang.org/x/crypto/ssh/terminal/terminal.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
4
 
 
5
package terminal
 
6
 
 
7
import (
 
8
        "bytes"
 
9
        "io"
 
10
        "sync"
 
11
        "unicode/utf8"
 
12
)
 
13
 
 
14
// EscapeCodes contains escape sequences that can be written to the terminal in
 
15
// order to achieve different styles of text.
 
16
type EscapeCodes struct {
 
17
        // Foreground colors
 
18
        Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
 
19
 
 
20
        // Reset all attributes
 
21
        Reset []byte
 
22
}
 
23
 
 
24
var vt100EscapeCodes = EscapeCodes{
 
25
        Black:   []byte{keyEscape, '[', '3', '0', 'm'},
 
26
        Red:     []byte{keyEscape, '[', '3', '1', 'm'},
 
27
        Green:   []byte{keyEscape, '[', '3', '2', 'm'},
 
28
        Yellow:  []byte{keyEscape, '[', '3', '3', 'm'},
 
29
        Blue:    []byte{keyEscape, '[', '3', '4', 'm'},
 
30
        Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
 
31
        Cyan:    []byte{keyEscape, '[', '3', '6', 'm'},
 
32
        White:   []byte{keyEscape, '[', '3', '7', 'm'},
 
33
 
 
34
        Reset: []byte{keyEscape, '[', '0', 'm'},
 
35
}
 
36
 
 
37
// Terminal contains the state for running a VT100 terminal that is capable of
 
38
// reading lines of input.
 
39
type Terminal struct {
 
40
        // AutoCompleteCallback, if non-null, is called for each keypress with
 
41
        // the full input line and the current position of the cursor (in
 
42
        // bytes, as an index into |line|). If it returns ok=false, the key
 
43
        // press is processed normally. Otherwise it returns a replacement line
 
44
        // and the new cursor position.
 
45
        AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
 
46
 
 
47
        // Escape contains a pointer to the escape codes for this terminal.
 
48
        // It's always a valid pointer, although the escape codes themselves
 
49
        // may be empty if the terminal doesn't support them.
 
50
        Escape *EscapeCodes
 
51
 
 
52
        // lock protects the terminal and the state in this object from
 
53
        // concurrent processing of a key press and a Write() call.
 
54
        lock sync.Mutex
 
55
 
 
56
        c      io.ReadWriter
 
57
        prompt []rune
 
58
 
 
59
        // line is the current line being entered.
 
60
        line []rune
 
61
        // pos is the logical position of the cursor in line
 
62
        pos int
 
63
        // echo is true if local echo is enabled
 
64
        echo bool
 
65
        // pasteActive is true iff there is a bracketed paste operation in
 
66
        // progress.
 
67
        pasteActive bool
 
68
 
 
69
        // cursorX contains the current X value of the cursor where the left
 
70
        // edge is 0. cursorY contains the row number where the first row of
 
71
        // the current line is 0.
 
72
        cursorX, cursorY int
 
73
        // maxLine is the greatest value of cursorY so far.
 
74
        maxLine int
 
75
 
 
76
        termWidth, termHeight int
 
77
 
 
78
        // outBuf contains the terminal data to be sent.
 
79
        outBuf []byte
 
80
        // remainder contains the remainder of any partial key sequences after
 
81
        // a read. It aliases into inBuf.
 
82
        remainder []byte
 
83
        inBuf     [256]byte
 
84
 
 
85
        // history contains previously entered commands so that they can be
 
86
        // accessed with the up and down keys.
 
87
        history stRingBuffer
 
88
        // historyIndex stores the currently accessed history entry, where zero
 
89
        // means the immediately previous entry.
 
90
        historyIndex int
 
91
        // When navigating up and down the history it's possible to return to
 
92
        // the incomplete, initial line. That value is stored in
 
93
        // historyPending.
 
94
        historyPending string
 
95
}
 
96
 
 
97
// NewTerminal runs a VT100 terminal on the given ReadWriter. If the ReadWriter is
 
98
// a local terminal, that terminal must first have been put into raw mode.
 
99
// prompt is a string that is written at the start of each input line (i.e.
 
100
// "> ").
 
101
func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
 
102
        return &Terminal{
 
103
                Escape:       &vt100EscapeCodes,
 
104
                c:            c,
 
105
                prompt:       []rune(prompt),
 
106
                termWidth:    80,
 
107
                termHeight:   24,
 
108
                echo:         true,
 
109
                historyIndex: -1,
 
110
        }
 
111
}
 
112
 
 
113
const (
 
114
        keyCtrlD     = 4
 
115
        keyCtrlU     = 21
 
116
        keyEnter     = '\r'
 
117
        keyEscape    = 27
 
118
        keyBackspace = 127
 
119
        keyUnknown   = 0xd800 /* UTF-16 surrogate area */ + iota
 
120
        keyUp
 
121
        keyDown
 
122
        keyLeft
 
123
        keyRight
 
124
        keyAltLeft
 
125
        keyAltRight
 
126
        keyHome
 
127
        keyEnd
 
128
        keyDeleteWord
 
129
        keyDeleteLine
 
130
        keyClearScreen
 
131
        keyPasteStart
 
132
        keyPasteEnd
 
133
)
 
134
 
 
135
var pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
 
136
var pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
 
137
 
 
138
// bytesToKey tries to parse a key sequence from b. If successful, it returns
 
139
// the key and the remainder of the input. Otherwise it returns utf8.RuneError.
 
140
func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
 
141
        if len(b) == 0 {
 
142
                return utf8.RuneError, nil
 
143
        }
 
144
 
 
145
        if !pasteActive {
 
146
                switch b[0] {
 
147
                case 1: // ^A
 
148
                        return keyHome, b[1:]
 
149
                case 5: // ^E
 
150
                        return keyEnd, b[1:]
 
151
                case 8: // ^H
 
152
                        return keyBackspace, b[1:]
 
153
                case 11: // ^K
 
154
                        return keyDeleteLine, b[1:]
 
155
                case 12: // ^L
 
156
                        return keyClearScreen, b[1:]
 
157
                case 23: // ^W
 
158
                        return keyDeleteWord, b[1:]
 
159
                }
 
160
        }
 
161
 
 
162
        if b[0] != keyEscape {
 
163
                if !utf8.FullRune(b) {
 
164
                        return utf8.RuneError, b
 
165
                }
 
166
                r, l := utf8.DecodeRune(b)
 
167
                return r, b[l:]
 
168
        }
 
169
 
 
170
        if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
 
171
                switch b[2] {
 
172
                case 'A':
 
173
                        return keyUp, b[3:]
 
174
                case 'B':
 
175
                        return keyDown, b[3:]
 
176
                case 'C':
 
177
                        return keyRight, b[3:]
 
178
                case 'D':
 
179
                        return keyLeft, b[3:]
 
180
                case 'H':
 
181
                        return keyHome, b[3:]
 
182
                case 'F':
 
183
                        return keyEnd, b[3:]
 
184
                }
 
185
        }
 
186
 
 
187
        if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
 
188
                switch b[5] {
 
189
                case 'C':
 
190
                        return keyAltRight, b[6:]
 
191
                case 'D':
 
192
                        return keyAltLeft, b[6:]
 
193
                }
 
194
        }
 
195
 
 
196
        if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) {
 
197
                return keyPasteStart, b[6:]
 
198
        }
 
199
 
 
200
        if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) {
 
201
                return keyPasteEnd, b[6:]
 
202
        }
 
203
 
 
204
        // If we get here then we have a key that we don't recognise, or a
 
205
        // partial sequence. It's not clear how one should find the end of a
 
206
        // sequence without knowing them all, but it seems that [a-zA-Z~] only
 
207
        // appears at the end of a sequence.
 
208
        for i, c := range b[0:] {
 
209
                if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
 
210
                        return keyUnknown, b[i+1:]
 
211
                }
 
212
        }
 
213
 
 
214
        return utf8.RuneError, b
 
215
}
 
216
 
 
217
// queue appends data to the end of t.outBuf
 
218
func (t *Terminal) queue(data []rune) {
 
219
        t.outBuf = append(t.outBuf, []byte(string(data))...)
 
220
}
 
221
 
 
222
var eraseUnderCursor = []rune{' ', keyEscape, '[', 'D'}
 
223
var space = []rune{' '}
 
224
 
 
225
func isPrintable(key rune) bool {
 
226
        isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
 
227
        return key >= 32 && !isInSurrogateArea
 
228
}
 
229
 
 
230
// moveCursorToPos appends data to t.outBuf which will move the cursor to the
 
231
// given, logical position in the text.
 
232
func (t *Terminal) moveCursorToPos(pos int) {
 
233
        if !t.echo {
 
234
                return
 
235
        }
 
236
 
 
237
        x := visualLength(t.prompt) + pos
 
238
        y := x / t.termWidth
 
239
        x = x % t.termWidth
 
240
 
 
241
        up := 0
 
242
        if y < t.cursorY {
 
243
                up = t.cursorY - y
 
244
        }
 
245
 
 
246
        down := 0
 
247
        if y > t.cursorY {
 
248
                down = y - t.cursorY
 
249
        }
 
250
 
 
251
        left := 0
 
252
        if x < t.cursorX {
 
253
                left = t.cursorX - x
 
254
        }
 
255
 
 
256
        right := 0
 
257
        if x > t.cursorX {
 
258
                right = x - t.cursorX
 
259
        }
 
260
 
 
261
        t.cursorX = x
 
262
        t.cursorY = y
 
263
        t.move(up, down, left, right)
 
264
}
 
265
 
 
266
func (t *Terminal) move(up, down, left, right int) {
 
267
        movement := make([]rune, 3*(up+down+left+right))
 
268
        m := movement
 
269
        for i := 0; i < up; i++ {
 
270
                m[0] = keyEscape
 
271
                m[1] = '['
 
272
                m[2] = 'A'
 
273
                m = m[3:]
 
274
        }
 
275
        for i := 0; i < down; i++ {
 
276
                m[0] = keyEscape
 
277
                m[1] = '['
 
278
                m[2] = 'B'
 
279
                m = m[3:]
 
280
        }
 
281
        for i := 0; i < left; i++ {
 
282
                m[0] = keyEscape
 
283
                m[1] = '['
 
284
                m[2] = 'D'
 
285
                m = m[3:]
 
286
        }
 
287
        for i := 0; i < right; i++ {
 
288
                m[0] = keyEscape
 
289
                m[1] = '['
 
290
                m[2] = 'C'
 
291
                m = m[3:]
 
292
        }
 
293
 
 
294
        t.queue(movement)
 
295
}
 
296
 
 
297
func (t *Terminal) clearLineToRight() {
 
298
        op := []rune{keyEscape, '[', 'K'}
 
299
        t.queue(op)
 
300
}
 
301
 
 
302
const maxLineLength = 4096
 
303
 
 
304
func (t *Terminal) setLine(newLine []rune, newPos int) {
 
305
        if t.echo {
 
306
                t.moveCursorToPos(0)
 
307
                t.writeLine(newLine)
 
308
                for i := len(newLine); i < len(t.line); i++ {
 
309
                        t.writeLine(space)
 
310
                }
 
311
                t.moveCursorToPos(newPos)
 
312
        }
 
313
        t.line = newLine
 
314
        t.pos = newPos
 
315
}
 
316
 
 
317
func (t *Terminal) advanceCursor(places int) {
 
318
        t.cursorX += places
 
319
        t.cursorY += t.cursorX / t.termWidth
 
320
        if t.cursorY > t.maxLine {
 
321
                t.maxLine = t.cursorY
 
322
        }
 
323
        t.cursorX = t.cursorX % t.termWidth
 
324
 
 
325
        if places > 0 && t.cursorX == 0 {
 
326
                // Normally terminals will advance the current position
 
327
                // when writing a character. But that doesn't happen
 
328
                // for the last character in a line. However, when
 
329
                // writing a character (except a new line) that causes
 
330
                // a line wrap, the position will be advanced two
 
331
                // places.
 
332
                //
 
333
                // So, if we are stopping at the end of a line, we
 
334
                // need to write a newline so that our cursor can be
 
335
                // advanced to the next line.
 
336
                t.outBuf = append(t.outBuf, '\n')
 
337
        }
 
338
}
 
339
 
 
340
func (t *Terminal) eraseNPreviousChars(n int) {
 
341
        if n == 0 {
 
342
                return
 
343
        }
 
344
 
 
345
        if t.pos < n {
 
346
                n = t.pos
 
347
        }
 
348
        t.pos -= n
 
349
        t.moveCursorToPos(t.pos)
 
350
 
 
351
        copy(t.line[t.pos:], t.line[n+t.pos:])
 
352
        t.line = t.line[:len(t.line)-n]
 
353
        if t.echo {
 
354
                t.writeLine(t.line[t.pos:])
 
355
                for i := 0; i < n; i++ {
 
356
                        t.queue(space)
 
357
                }
 
358
                t.advanceCursor(n)
 
359
                t.moveCursorToPos(t.pos)
 
360
        }
 
361
}
 
362
 
 
363
// countToLeftWord returns then number of characters from the cursor to the
 
364
// start of the previous word.
 
365
func (t *Terminal) countToLeftWord() int {
 
366
        if t.pos == 0 {
 
367
                return 0
 
368
        }
 
369
 
 
370
        pos := t.pos - 1
 
371
        for pos > 0 {
 
372
                if t.line[pos] != ' ' {
 
373
                        break
 
374
                }
 
375
                pos--
 
376
        }
 
377
        for pos > 0 {
 
378
                if t.line[pos] == ' ' {
 
379
                        pos++
 
380
                        break
 
381
                }
 
382
                pos--
 
383
        }
 
384
 
 
385
        return t.pos - pos
 
386
}
 
387
 
 
388
// countToRightWord returns then number of characters from the cursor to the
 
389
// start of the next word.
 
390
func (t *Terminal) countToRightWord() int {
 
391
        pos := t.pos
 
392
        for pos < len(t.line) {
 
393
                if t.line[pos] == ' ' {
 
394
                        break
 
395
                }
 
396
                pos++
 
397
        }
 
398
        for pos < len(t.line) {
 
399
                if t.line[pos] != ' ' {
 
400
                        break
 
401
                }
 
402
                pos++
 
403
        }
 
404
        return pos - t.pos
 
405
}
 
406
 
 
407
// visualLength returns the number of visible glyphs in s.
 
408
func visualLength(runes []rune) int {
 
409
        inEscapeSeq := false
 
410
        length := 0
 
411
 
 
412
        for _, r := range runes {
 
413
                switch {
 
414
                case inEscapeSeq:
 
415
                        if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
 
416
                                inEscapeSeq = false
 
417
                        }
 
418
                case r == '\x1b':
 
419
                        inEscapeSeq = true
 
420
                default:
 
421
                        length++
 
422
                }
 
423
        }
 
424
 
 
425
        return length
 
426
}
 
427
 
 
428
// handleKey processes the given key and, optionally, returns a line of text
 
429
// that the user has entered.
 
430
func (t *Terminal) handleKey(key rune) (line string, ok bool) {
 
431
        if t.pasteActive && key != keyEnter {
 
432
                t.addKeyToLine(key)
 
433
                return
 
434
        }
 
435
 
 
436
        switch key {
 
437
        case keyBackspace:
 
438
                if t.pos == 0 {
 
439
                        return
 
440
                }
 
441
                t.eraseNPreviousChars(1)
 
442
        case keyAltLeft:
 
443
                // move left by a word.
 
444
                t.pos -= t.countToLeftWord()
 
445
                t.moveCursorToPos(t.pos)
 
446
        case keyAltRight:
 
447
                // move right by a word.
 
448
                t.pos += t.countToRightWord()
 
449
                t.moveCursorToPos(t.pos)
 
450
        case keyLeft:
 
451
                if t.pos == 0 {
 
452
                        return
 
453
                }
 
454
                t.pos--
 
455
                t.moveCursorToPos(t.pos)
 
456
        case keyRight:
 
457
                if t.pos == len(t.line) {
 
458
                        return
 
459
                }
 
460
                t.pos++
 
461
                t.moveCursorToPos(t.pos)
 
462
        case keyHome:
 
463
                if t.pos == 0 {
 
464
                        return
 
465
                }
 
466
                t.pos = 0
 
467
                t.moveCursorToPos(t.pos)
 
468
        case keyEnd:
 
469
                if t.pos == len(t.line) {
 
470
                        return
 
471
                }
 
472
                t.pos = len(t.line)
 
473
                t.moveCursorToPos(t.pos)
 
474
        case keyUp:
 
475
                entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
 
476
                if !ok {
 
477
                        return "", false
 
478
                }
 
479
                if t.historyIndex == -1 {
 
480
                        t.historyPending = string(t.line)
 
481
                }
 
482
                t.historyIndex++
 
483
                runes := []rune(entry)
 
484
                t.setLine(runes, len(runes))
 
485
        case keyDown:
 
486
                switch t.historyIndex {
 
487
                case -1:
 
488
                        return
 
489
                case 0:
 
490
                        runes := []rune(t.historyPending)
 
491
                        t.setLine(runes, len(runes))
 
492
                        t.historyIndex--
 
493
                default:
 
494
                        entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
 
495
                        if ok {
 
496
                                t.historyIndex--
 
497
                                runes := []rune(entry)
 
498
                                t.setLine(runes, len(runes))
 
499
                        }
 
500
                }
 
501
        case keyEnter:
 
502
                t.moveCursorToPos(len(t.line))
 
503
                t.queue([]rune("\r\n"))
 
504
                line = string(t.line)
 
505
                ok = true
 
506
                t.line = t.line[:0]
 
507
                t.pos = 0
 
508
                t.cursorX = 0
 
509
                t.cursorY = 0
 
510
                t.maxLine = 0
 
511
        case keyDeleteWord:
 
512
                // Delete zero or more spaces and then one or more characters.
 
513
                t.eraseNPreviousChars(t.countToLeftWord())
 
514
        case keyDeleteLine:
 
515
                // Delete everything from the current cursor position to the
 
516
                // end of line.
 
517
                for i := t.pos; i < len(t.line); i++ {
 
518
                        t.queue(space)
 
519
                        t.advanceCursor(1)
 
520
                }
 
521
                t.line = t.line[:t.pos]
 
522
                t.moveCursorToPos(t.pos)
 
523
        case keyCtrlD:
 
524
                // Erase the character under the current position.
 
525
                // The EOF case when the line is empty is handled in
 
526
                // readLine().
 
527
                if t.pos < len(t.line) {
 
528
                        t.pos++
 
529
                        t.eraseNPreviousChars(1)
 
530
                }
 
531
        case keyCtrlU:
 
532
                t.eraseNPreviousChars(t.pos)
 
533
        case keyClearScreen:
 
534
                // Erases the screen and moves the cursor to the home position.
 
535
                t.queue([]rune("\x1b[2J\x1b[H"))
 
536
                t.queue(t.prompt)
 
537
                t.cursorX, t.cursorY = 0, 0
 
538
                t.advanceCursor(visualLength(t.prompt))
 
539
                t.setLine(t.line, t.pos)
 
540
        default:
 
541
                if t.AutoCompleteCallback != nil {
 
542
                        prefix := string(t.line[:t.pos])
 
543
                        suffix := string(t.line[t.pos:])
 
544
 
 
545
                        t.lock.Unlock()
 
546
                        newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
 
547
                        t.lock.Lock()
 
548
 
 
549
                        if completeOk {
 
550
                                t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
 
551
                                return
 
552
                        }
 
553
                }
 
554
                if !isPrintable(key) {
 
555
                        return
 
556
                }
 
557
                if len(t.line) == maxLineLength {
 
558
                        return
 
559
                }
 
560
                t.addKeyToLine(key)
 
561
        }
 
562
        return
 
563
}
 
564
 
 
565
// addKeyToLine inserts the given key at the current position in the current
 
566
// line.
 
567
func (t *Terminal) addKeyToLine(key rune) {
 
568
        if len(t.line) == cap(t.line) {
 
569
                newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
 
570
                copy(newLine, t.line)
 
571
                t.line = newLine
 
572
        }
 
573
        t.line = t.line[:len(t.line)+1]
 
574
        copy(t.line[t.pos+1:], t.line[t.pos:])
 
575
        t.line[t.pos] = key
 
576
        if t.echo {
 
577
                t.writeLine(t.line[t.pos:])
 
578
        }
 
579
        t.pos++
 
580
        t.moveCursorToPos(t.pos)
 
581
}
 
582
 
 
583
func (t *Terminal) writeLine(line []rune) {
 
584
        for len(line) != 0 {
 
585
                remainingOnLine := t.termWidth - t.cursorX
 
586
                todo := len(line)
 
587
                if todo > remainingOnLine {
 
588
                        todo = remainingOnLine
 
589
                }
 
590
                t.queue(line[:todo])
 
591
                t.advanceCursor(visualLength(line[:todo]))
 
592
                line = line[todo:]
 
593
        }
 
594
}
 
595
 
 
596
func (t *Terminal) Write(buf []byte) (n int, err error) {
 
597
        t.lock.Lock()
 
598
        defer t.lock.Unlock()
 
599
 
 
600
        if t.cursorX == 0 && t.cursorY == 0 {
 
601
                // This is the easy case: there's nothing on the screen that we
 
602
                // have to move out of the way.
 
603
                return t.c.Write(buf)
 
604
        }
 
605
 
 
606
        // We have a prompt and possibly user input on the screen. We
 
607
        // have to clear it first.
 
608
        t.move(0 /* up */, 0 /* down */, t.cursorX /* left */, 0 /* right */)
 
609
        t.cursorX = 0
 
610
        t.clearLineToRight()
 
611
 
 
612
        for t.cursorY > 0 {
 
613
                t.move(1 /* up */, 0, 0, 0)
 
614
                t.cursorY--
 
615
                t.clearLineToRight()
 
616
        }
 
617
 
 
618
        if _, err = t.c.Write(t.outBuf); err != nil {
 
619
                return
 
620
        }
 
621
        t.outBuf = t.outBuf[:0]
 
622
 
 
623
        if n, err = t.c.Write(buf); err != nil {
 
624
                return
 
625
        }
 
626
 
 
627
        t.writeLine(t.prompt)
 
628
        if t.echo {
 
629
                t.writeLine(t.line)
 
630
        }
 
631
 
 
632
        t.moveCursorToPos(t.pos)
 
633
 
 
634
        if _, err = t.c.Write(t.outBuf); err != nil {
 
635
                return
 
636
        }
 
637
        t.outBuf = t.outBuf[:0]
 
638
        return
 
639
}
 
640
 
 
641
// ReadPassword temporarily changes the prompt and reads a password, without
 
642
// echo, from the terminal.
 
643
func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
 
644
        t.lock.Lock()
 
645
        defer t.lock.Unlock()
 
646
 
 
647
        oldPrompt := t.prompt
 
648
        t.prompt = []rune(prompt)
 
649
        t.echo = false
 
650
 
 
651
        line, err = t.readLine()
 
652
 
 
653
        t.prompt = oldPrompt
 
654
        t.echo = true
 
655
 
 
656
        return
 
657
}
 
658
 
 
659
// ReadLine returns a line of input from the terminal.
 
660
func (t *Terminal) ReadLine() (line string, err error) {
 
661
        t.lock.Lock()
 
662
        defer t.lock.Unlock()
 
663
 
 
664
        return t.readLine()
 
665
}
 
666
 
 
667
func (t *Terminal) readLine() (line string, err error) {
 
668
        // t.lock must be held at this point
 
669
 
 
670
        if t.cursorX == 0 && t.cursorY == 0 {
 
671
                t.writeLine(t.prompt)
 
672
                t.c.Write(t.outBuf)
 
673
                t.outBuf = t.outBuf[:0]
 
674
        }
 
675
 
 
676
        lineIsPasted := t.pasteActive
 
677
 
 
678
        for {
 
679
                rest := t.remainder
 
680
                lineOk := false
 
681
                for !lineOk {
 
682
                        var key rune
 
683
                        key, rest = bytesToKey(rest, t.pasteActive)
 
684
                        if key == utf8.RuneError {
 
685
                                break
 
686
                        }
 
687
                        if !t.pasteActive {
 
688
                                if key == keyCtrlD {
 
689
                                        if len(t.line) == 0 {
 
690
                                                return "", io.EOF
 
691
                                        }
 
692
                                }
 
693
                                if key == keyPasteStart {
 
694
                                        t.pasteActive = true
 
695
                                        if len(t.line) == 0 {
 
696
                                                lineIsPasted = true
 
697
                                        }
 
698
                                        continue
 
699
                                }
 
700
                        } else if key == keyPasteEnd {
 
701
                                t.pasteActive = false
 
702
                                continue
 
703
                        }
 
704
                        if !t.pasteActive {
 
705
                                lineIsPasted = false
 
706
                        }
 
707
                        line, lineOk = t.handleKey(key)
 
708
                }
 
709
                if len(rest) > 0 {
 
710
                        n := copy(t.inBuf[:], rest)
 
711
                        t.remainder = t.inBuf[:n]
 
712
                } else {
 
713
                        t.remainder = nil
 
714
                }
 
715
                t.c.Write(t.outBuf)
 
716
                t.outBuf = t.outBuf[:0]
 
717
                if lineOk {
 
718
                        if t.echo {
 
719
                                t.historyIndex = -1
 
720
                                t.history.Add(line)
 
721
                        }
 
722
                        if lineIsPasted {
 
723
                                err = ErrPasteIndicator
 
724
                        }
 
725
                        return
 
726
                }
 
727
 
 
728
                // t.remainder is a slice at the beginning of t.inBuf
 
729
                // containing a partial key sequence
 
730
                readBuf := t.inBuf[len(t.remainder):]
 
731
                var n int
 
732
 
 
733
                t.lock.Unlock()
 
734
                n, err = t.c.Read(readBuf)
 
735
                t.lock.Lock()
 
736
 
 
737
                if err != nil {
 
738
                        return
 
739
                }
 
740
 
 
741
                t.remainder = t.inBuf[:n+len(t.remainder)]
 
742
        }
 
743
 
 
744
        panic("unreachable") // for Go 1.0.
 
745
}
 
746
 
 
747
// SetPrompt sets the prompt to be used when reading subsequent lines.
 
748
func (t *Terminal) SetPrompt(prompt string) {
 
749
        t.lock.Lock()
 
750
        defer t.lock.Unlock()
 
751
 
 
752
        t.prompt = []rune(prompt)
 
753
}
 
754
 
 
755
func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
 
756
        // Move cursor to column zero at the start of the line.
 
757
        t.move(t.cursorY, 0, t.cursorX, 0)
 
758
        t.cursorX, t.cursorY = 0, 0
 
759
        t.clearLineToRight()
 
760
        for t.cursorY < numPrevLines {
 
761
                // Move down a line
 
762
                t.move(0, 1, 0, 0)
 
763
                t.cursorY++
 
764
                t.clearLineToRight()
 
765
        }
 
766
        // Move back to beginning.
 
767
        t.move(t.cursorY, 0, 0, 0)
 
768
        t.cursorX, t.cursorY = 0, 0
 
769
 
 
770
        t.queue(t.prompt)
 
771
        t.advanceCursor(visualLength(t.prompt))
 
772
        t.writeLine(t.line)
 
773
        t.moveCursorToPos(t.pos)
 
774
}
 
775
 
 
776
func (t *Terminal) SetSize(width, height int) error {
 
777
        t.lock.Lock()
 
778
        defer t.lock.Unlock()
 
779
 
 
780
        if width == 0 {
 
781
                width = 1
 
782
        }
 
783
 
 
784
        oldWidth := t.termWidth
 
785
        t.termWidth, t.termHeight = width, height
 
786
 
 
787
        switch {
 
788
        case width == oldWidth:
 
789
                // If the width didn't change then nothing else needs to be
 
790
                // done.
 
791
                return nil
 
792
        case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
 
793
                // If there is nothing on current line and no prompt printed,
 
794
                // just do nothing
 
795
                return nil
 
796
        case width < oldWidth:
 
797
                // Some terminals (e.g. xterm) will truncate lines that were
 
798
                // too long when shinking. Others, (e.g. gnome-terminal) will
 
799
                // attempt to wrap them. For the former, repainting t.maxLine
 
800
                // works great, but that behaviour goes badly wrong in the case
 
801
                // of the latter because they have doubled every full line.
 
802
 
 
803
                // We assume that we are working on a terminal that wraps lines
 
804
                // and adjust the cursor position based on every previous line
 
805
                // wrapping and turning into two. This causes the prompt on
 
806
                // xterms to move upwards, which isn't great, but it avoids a
 
807
                // huge mess with gnome-terminal.
 
808
                if t.cursorX >= t.termWidth {
 
809
                        t.cursorX = t.termWidth - 1
 
810
                }
 
811
                t.cursorY *= 2
 
812
                t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
 
813
        case width > oldWidth:
 
814
                // If the terminal expands then our position calculations will
 
815
                // be wrong in the future because we think the cursor is
 
816
                // |t.pos| chars into the string, but there will be a gap at
 
817
                // the end of any wrapped line.
 
818
                //
 
819
                // But the position will actually be correct until we move, so
 
820
                // we can move back to the beginning and repaint everything.
 
821
                t.clearAndRepaintLinePlusNPrevious(t.maxLine)
 
822
        }
 
823
 
 
824
        _, err := t.c.Write(t.outBuf)
 
825
        t.outBuf = t.outBuf[:0]
 
826
        return err
 
827
}
 
828
 
 
829
type pasteIndicatorError struct{}
 
830
 
 
831
func (pasteIndicatorError) Error() string {
 
832
        return "terminal: ErrPasteIndicator not correctly handled"
 
833
}
 
834
 
 
835
// ErrPasteIndicator may be returned from ReadLine as the error, in addition
 
836
// to valid line data. It indicates that bracketed paste mode is enabled and
 
837
// that the returned line consists only of pasted data. Programs may wish to
 
838
// interpret pasted data more literally than typed data.
 
839
var ErrPasteIndicator = pasteIndicatorError{}
 
840
 
 
841
// SetBracketedPasteMode requests that the terminal bracket paste operations
 
842
// with markers. Not all terminals support this but, if it is supported, then
 
843
// enabling this mode will stop any autocomplete callback from running due to
 
844
// pastes. Additionally, any lines that are completely pasted will be returned
 
845
// from ReadLine with the error set to ErrPasteIndicator.
 
846
func (t *Terminal) SetBracketedPasteMode(on bool) {
 
847
        if on {
 
848
                io.WriteString(t.c, "\x1b[?2004h")
 
849
        } else {
 
850
                io.WriteString(t.c, "\x1b[?2004l")
 
851
        }
 
852
}
 
853
 
 
854
// stRingBuffer is a ring buffer of strings.
 
855
type stRingBuffer struct {
 
856
        // entries contains max elements.
 
857
        entries []string
 
858
        max     int
 
859
        // head contains the index of the element most recently added to the ring.
 
860
        head int
 
861
        // size contains the number of elements in the ring.
 
862
        size int
 
863
}
 
864
 
 
865
func (s *stRingBuffer) Add(a string) {
 
866
        if s.entries == nil {
 
867
                const defaultNumEntries = 100
 
868
                s.entries = make([]string, defaultNumEntries)
 
869
                s.max = defaultNumEntries
 
870
        }
 
871
 
 
872
        s.head = (s.head + 1) % s.max
 
873
        s.entries[s.head] = a
 
874
        if s.size < s.max {
 
875
                s.size++
 
876
        }
 
877
}
 
878
 
 
879
// NthPreviousEntry returns the value passed to the nth previous call to Add.
 
880
// If n is zero then the immediately prior value is returned, if one, then the
 
881
// next most recent, and so on. If such an element doesn't exist then ok is
 
882
// false.
 
883
func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) {
 
884
        if n >= s.size {
 
885
                return "", false
 
886
        }
 
887
        index := s.head - n
 
888
        if index < 0 {
 
889
                index += s.max
 
890
        }
 
891
        return s.entries[index], true
 
892
}