1
2
3
4
5 package term
6
7 import (
8 "bytes"
9 "fmt"
10 "io"
11 "runtime"
12 "strconv"
13 "sync"
14 "unicode/utf8"
15 )
16
17
18
19 type EscapeCodes struct {
20
21 Black, Red, Green, Yellow, Blue, Magenta, Cyan, White []byte
22
23
24 Reset []byte
25 }
26
27 var vt100EscapeCodes = EscapeCodes{
28 Black: []byte{keyEscape, '[', '3', '0', 'm'},
29 Red: []byte{keyEscape, '[', '3', '1', 'm'},
30 Green: []byte{keyEscape, '[', '3', '2', 'm'},
31 Yellow: []byte{keyEscape, '[', '3', '3', 'm'},
32 Blue: []byte{keyEscape, '[', '3', '4', 'm'},
33 Magenta: []byte{keyEscape, '[', '3', '5', 'm'},
34 Cyan: []byte{keyEscape, '[', '3', '6', 'm'},
35 White: []byte{keyEscape, '[', '3', '7', 'm'},
36
37 Reset: []byte{keyEscape, '[', '0', 'm'},
38 }
39
40
41 type History interface {
42
43
44
45
46
47
48 Add(entry string)
49
50
51 Len() int
52
53
54
55
56
57 At(idx int) string
58 }
59
60
61
62 type Terminal struct {
63
64
65
66
67
68
69
70 AutoCompleteCallback func(line string, pos int, key rune) (newLine string, newPos int, ok bool)
71
72
73
74
75 Escape *EscapeCodes
76
77
78
79 lock sync.Mutex
80
81 c io.ReadWriter
82 prompt []rune
83
84
85 line []rune
86
87 pos int
88
89 echo bool
90
91
92 pasteActive bool
93
94
95
96
97 cursorX, cursorY int
98
99 maxLine int
100
101 termWidth, termHeight int
102
103
104 outBuf []byte
105
106
107 remainder []byte
108 inBuf [256]byte
109
110
111
112
113
114
115
116
117 History History
118
119
120 historyIndex int
121
122
123
124 historyPending string
125 }
126
127
128
129
130
131 func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
132 return &Terminal{
133 Escape: &vt100EscapeCodes,
134 c: c,
135 prompt: []rune(prompt),
136 termWidth: 80,
137 termHeight: 24,
138 echo: true,
139 historyIndex: -1,
140 History: &stRingBuffer{},
141 }
142 }
143
144 const (
145 keyCtrlC = 3
146 keyCtrlD = 4
147 keyCtrlU = 21
148 keyEnter = '\r'
149 keyLF = '\n'
150 keyEscape = 27
151 keyBackspace = 127
152 keyUnknown = 0xd800 + iota
153 keyUp
154 keyDown
155 keyLeft
156 keyRight
157 keyAltLeft
158 keyAltRight
159 keyHome
160 keyEnd
161 keyDeleteWord
162 keyDeleteLine
163 keyDelete
164 keyClearScreen
165 keyTranspose
166 keyPasteStart
167 keyPasteEnd
168 )
169
170 var (
171 crlf = []byte{'\r', '\n'}
172 pasteStart = []byte{keyEscape, '[', '2', '0', '0', '~'}
173 pasteEnd = []byte{keyEscape, '[', '2', '0', '1', '~'}
174 )
175
176
177
178 func bytesToKey(b []byte, pasteActive bool) (rune, []byte) {
179 if len(b) == 0 {
180 return utf8.RuneError, nil
181 }
182
183 if !pasteActive {
184 switch b[0] {
185 case 1:
186 return keyHome, b[1:]
187 case 2:
188 return keyLeft, b[1:]
189 case 5:
190 return keyEnd, b[1:]
191 case 6:
192 return keyRight, b[1:]
193 case 8:
194 return keyBackspace, b[1:]
195 case 11:
196 return keyDeleteLine, b[1:]
197 case 12:
198 return keyClearScreen, b[1:]
199 case 20:
200 return keyTranspose, b[1:]
201 case 23:
202 return keyDeleteWord, b[1:]
203 case 14:
204 return keyDown, b[1:]
205 case 16:
206 return keyUp, b[1:]
207 }
208 }
209
210 if b[0] != keyEscape {
211 if !utf8.FullRune(b) {
212 return utf8.RuneError, b
213 }
214 r, l := utf8.DecodeRune(b)
215 return r, b[l:]
216 }
217
218 if !pasteActive && len(b) >= 3 && b[0] == keyEscape && b[1] == '[' {
219 switch b[2] {
220 case 'A':
221 return keyUp, b[3:]
222 case 'B':
223 return keyDown, b[3:]
224 case 'C':
225 return keyRight, b[3:]
226 case 'D':
227 return keyLeft, b[3:]
228 case 'H':
229 return keyHome, b[3:]
230 case 'F':
231 return keyEnd, b[3:]
232 }
233 }
234
235 if !pasteActive && len(b) >= 4 && b[0] == keyEscape && b[1] == '[' && b[2] == '3' && b[3] == '~' {
236 return keyDelete, b[4:]
237 }
238
239 if !pasteActive && len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' {
240 switch b[5] {
241 case 'C':
242 return keyAltRight, b[6:]
243 case 'D':
244 return keyAltLeft, b[6:]
245 }
246 }
247
248 if !pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteStart) {
249 return keyPasteStart, b[6:]
250 }
251
252 if pasteActive && len(b) >= 6 && bytes.Equal(b[:6], pasteEnd) {
253 return keyPasteEnd, b[6:]
254 }
255
256
257
258
259
260 for i, c := range b[0:] {
261 if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '~' {
262 return keyUnknown, b[i+1:]
263 }
264 }
265
266 return utf8.RuneError, b
267 }
268
269
270 func (t *Terminal) queue(data []rune) {
271 t.outBuf = append(t.outBuf, []byte(string(data))...)
272 }
273
274 var space = []rune{' '}
275
276 func isPrintable(key rune) bool {
277 isInSurrogateArea := key >= 0xd800 && key <= 0xdbff
278 return key >= 32 && !isInSurrogateArea
279 }
280
281
282
283 func (t *Terminal) moveCursorToPos(pos int) {
284 if !t.echo {
285 return
286 }
287
288 x := visualLength(t.prompt) + pos
289 y := x / t.termWidth
290 x = x % t.termWidth
291
292 up := 0
293 if y < t.cursorY {
294 up = t.cursorY - y
295 }
296
297 down := 0
298 if y > t.cursorY {
299 down = y - t.cursorY
300 }
301
302 left := 0
303 if x < t.cursorX {
304 left = t.cursorX - x
305 }
306
307 right := 0
308 if x > t.cursorX {
309 right = x - t.cursorX
310 }
311
312 t.cursorX = x
313 t.cursorY = y
314 t.move(up, down, left, right)
315 }
316
317 func (t *Terminal) move(up, down, left, right int) {
318 m := []rune{}
319
320
321
322
323 if up == 1 {
324 m = append(m, keyEscape, '[', 'A')
325 } else if up > 1 {
326 m = append(m, keyEscape, '[')
327 m = append(m, []rune(strconv.Itoa(up))...)
328 m = append(m, 'A')
329 }
330
331 if down == 1 {
332 m = append(m, keyEscape, '[', 'B')
333 } else if down > 1 {
334 m = append(m, keyEscape, '[')
335 m = append(m, []rune(strconv.Itoa(down))...)
336 m = append(m, 'B')
337 }
338
339 if right == 1 {
340 m = append(m, keyEscape, '[', 'C')
341 } else if right > 1 {
342 m = append(m, keyEscape, '[')
343 m = append(m, []rune(strconv.Itoa(right))...)
344 m = append(m, 'C')
345 }
346
347 if left == 1 {
348 m = append(m, keyEscape, '[', 'D')
349 } else if left > 1 {
350 m = append(m, keyEscape, '[')
351 m = append(m, []rune(strconv.Itoa(left))...)
352 m = append(m, 'D')
353 }
354
355 t.queue(m)
356 }
357
358 func (t *Terminal) clearLineToRight() {
359 op := []rune{keyEscape, '[', 'K'}
360 t.queue(op)
361 }
362
363 const maxLineLength = 4096
364
365 func (t *Terminal) setLine(newLine []rune, newPos int) {
366 if t.echo {
367 t.moveCursorToPos(0)
368 t.writeLine(newLine)
369 for i := len(newLine); i < len(t.line); i++ {
370 t.writeLine(space)
371 }
372 t.moveCursorToPos(newPos)
373 }
374 t.line = newLine
375 t.pos = newPos
376 }
377
378 func (t *Terminal) advanceCursor(places int) {
379 t.cursorX += places
380 t.cursorY += t.cursorX / t.termWidth
381 if t.cursorY > t.maxLine {
382 t.maxLine = t.cursorY
383 }
384 t.cursorX = t.cursorX % t.termWidth
385
386 if places > 0 && t.cursorX == 0 {
387
388
389
390
391
392
393
394
395
396
397 t.outBuf = append(t.outBuf, '\r', '\n')
398 }
399 }
400
401 func (t *Terminal) eraseNPreviousChars(n int) {
402 if n == 0 {
403 return
404 }
405
406 if t.pos < n {
407 n = t.pos
408 }
409 t.pos -= n
410 t.moveCursorToPos(t.pos)
411
412 copy(t.line[t.pos:], t.line[n+t.pos:])
413 t.line = t.line[:len(t.line)-n]
414 if t.echo {
415 t.writeLine(t.line[t.pos:])
416 for i := 0; i < n; i++ {
417 t.queue(space)
418 }
419 t.advanceCursor(n)
420 t.moveCursorToPos(t.pos)
421 }
422 }
423
424
425
426 func (t *Terminal) countToLeftWord() int {
427 if t.pos == 0 {
428 return 0
429 }
430
431 pos := t.pos - 1
432 for pos > 0 {
433 if t.line[pos] != ' ' {
434 break
435 }
436 pos--
437 }
438 for pos > 0 {
439 if t.line[pos] == ' ' {
440 pos++
441 break
442 }
443 pos--
444 }
445
446 return t.pos - pos
447 }
448
449
450
451 func (t *Terminal) countToRightWord() int {
452 pos := t.pos
453 for pos < len(t.line) {
454 if t.line[pos] == ' ' {
455 break
456 }
457 pos++
458 }
459 for pos < len(t.line) {
460 if t.line[pos] != ' ' {
461 break
462 }
463 pos++
464 }
465 return pos - t.pos
466 }
467
468
469 func visualLength(runes []rune) int {
470 inEscapeSeq := false
471 length := 0
472
473 for _, r := range runes {
474 switch {
475 case inEscapeSeq:
476 if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') {
477 inEscapeSeq = false
478 }
479 case r == '\x1b':
480 inEscapeSeq = true
481 default:
482 length++
483 }
484 }
485
486 return length
487 }
488
489
490 func (t *Terminal) historyAt(idx int) (string, bool) {
491 t.lock.Unlock()
492 defer t.lock.Lock()
493 if idx < 0 || idx >= t.History.Len() {
494 return "", false
495 }
496 return t.History.At(idx), true
497 }
498
499
500 func (t *Terminal) historyAdd(entry string) {
501 t.lock.Unlock()
502 defer t.lock.Lock()
503 t.History.Add(entry)
504 }
505
506
507
508 func (t *Terminal) handleKey(key rune) (line string, ok bool) {
509 if t.pasteActive && key != keyEnter && key != keyLF {
510 t.addKeyToLine(key)
511 return
512 }
513
514 switch key {
515 case keyBackspace:
516 if t.pos == 0 {
517 return
518 }
519 t.eraseNPreviousChars(1)
520 case keyAltLeft:
521
522 t.pos -= t.countToLeftWord()
523 t.moveCursorToPos(t.pos)
524 case keyAltRight:
525
526 t.pos += t.countToRightWord()
527 t.moveCursorToPos(t.pos)
528 case keyLeft:
529 if t.pos == 0 {
530 return
531 }
532 t.pos--
533 t.moveCursorToPos(t.pos)
534 case keyRight:
535 if t.pos == len(t.line) {
536 return
537 }
538 t.pos++
539 t.moveCursorToPos(t.pos)
540 case keyHome:
541 if t.pos == 0 {
542 return
543 }
544 t.pos = 0
545 t.moveCursorToPos(t.pos)
546 case keyEnd:
547 if t.pos == len(t.line) {
548 return
549 }
550 t.pos = len(t.line)
551 t.moveCursorToPos(t.pos)
552 case keyUp:
553 entry, ok := t.historyAt(t.historyIndex + 1)
554 if !ok {
555 return "", false
556 }
557 if t.historyIndex == -1 {
558 t.historyPending = string(t.line)
559 }
560 t.historyIndex++
561 runes := []rune(entry)
562 t.setLine(runes, len(runes))
563 case keyDown:
564 switch t.historyIndex {
565 case -1:
566 return
567 case 0:
568 runes := []rune(t.historyPending)
569 t.setLine(runes, len(runes))
570 t.historyIndex--
571 default:
572 entry, ok := t.historyAt(t.historyIndex - 1)
573 if ok {
574 t.historyIndex--
575 runes := []rune(entry)
576 t.setLine(runes, len(runes))
577 }
578 }
579 case keyEnter, keyLF:
580 t.moveCursorToPos(len(t.line))
581 t.queue([]rune("\r\n"))
582 line = string(t.line)
583 ok = true
584 t.line = t.line[:0]
585 t.pos = 0
586 t.cursorX = 0
587 t.cursorY = 0
588 t.maxLine = 0
589 case keyDeleteWord:
590
591 t.eraseNPreviousChars(t.countToLeftWord())
592 case keyDeleteLine:
593
594
595 for i := t.pos; i < len(t.line); i++ {
596 t.queue(space)
597 t.advanceCursor(1)
598 }
599 t.line = t.line[:t.pos]
600 t.moveCursorToPos(t.pos)
601 case keyCtrlD, keyDelete:
602
603
604
605 if t.pos < len(t.line) {
606 t.pos++
607 t.eraseNPreviousChars(1)
608 }
609 case keyCtrlU:
610 t.eraseNPreviousChars(t.pos)
611 case keyTranspose:
612
613 if len(t.line) < 2 || t.pos < 1 {
614 return
615 }
616 swap := t.pos
617 if swap == len(t.line) {
618 swap--
619 }
620 t.line[swap-1], t.line[swap] = t.line[swap], t.line[swap-1]
621 if t.pos < len(t.line) {
622 t.pos++
623 }
624 if t.echo {
625 t.moveCursorToPos(swap - 1)
626 t.writeLine(t.line[swap-1:])
627 t.moveCursorToPos(t.pos)
628 }
629 case keyClearScreen:
630
631 t.queue([]rune("\x1b[2J\x1b[H"))
632 t.queue(t.prompt)
633 t.cursorX, t.cursorY = 0, 0
634 t.advanceCursor(visualLength(t.prompt))
635 t.setLine(t.line, t.pos)
636 default:
637 if t.AutoCompleteCallback != nil {
638 prefix := string(t.line[:t.pos])
639 suffix := string(t.line[t.pos:])
640
641 t.lock.Unlock()
642 newLine, newPos, completeOk := t.AutoCompleteCallback(prefix+suffix, len(prefix), key)
643 t.lock.Lock()
644
645 if completeOk {
646 t.setLine([]rune(newLine), utf8.RuneCount([]byte(newLine)[:newPos]))
647 return
648 }
649 }
650 if !isPrintable(key) {
651 return
652 }
653 if len(t.line) == maxLineLength {
654 return
655 }
656 t.addKeyToLine(key)
657 }
658 return
659 }
660
661
662
663 func (t *Terminal) addKeyToLine(key rune) {
664 if len(t.line) == cap(t.line) {
665 newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
666 copy(newLine, t.line)
667 t.line = newLine
668 }
669 t.line = t.line[:len(t.line)+1]
670 copy(t.line[t.pos+1:], t.line[t.pos:])
671 t.line[t.pos] = key
672 if t.echo {
673 t.writeLine(t.line[t.pos:])
674 }
675 t.pos++
676 t.moveCursorToPos(t.pos)
677 }
678
679 func (t *Terminal) writeLine(line []rune) {
680 for len(line) != 0 {
681 remainingOnLine := t.termWidth - t.cursorX
682 todo := len(line)
683 if todo > remainingOnLine {
684 todo = remainingOnLine
685 }
686 t.queue(line[:todo])
687 t.advanceCursor(visualLength(line[:todo]))
688 line = line[todo:]
689 }
690 }
691
692
693 func writeWithCRLF(w io.Writer, buf []byte) (n int, err error) {
694 for len(buf) > 0 {
695 i := bytes.IndexByte(buf, '\n')
696 todo := len(buf)
697 if i >= 0 {
698 todo = i
699 }
700
701 var nn int
702 nn, err = w.Write(buf[:todo])
703 n += nn
704 if err != nil {
705 return n, err
706 }
707 buf = buf[todo:]
708
709 if i >= 0 {
710 if _, err = w.Write(crlf); err != nil {
711 return n, err
712 }
713 n++
714 buf = buf[1:]
715 }
716 }
717
718 return n, nil
719 }
720
721 func (t *Terminal) Write(buf []byte) (n int, err error) {
722 t.lock.Lock()
723 defer t.lock.Unlock()
724
725 if t.cursorX == 0 && t.cursorY == 0 {
726
727
728 return writeWithCRLF(t.c, buf)
729 }
730
731
732
733 t.move(0 , 0 , t.cursorX , 0 )
734 t.cursorX = 0
735 t.clearLineToRight()
736
737 for t.cursorY > 0 {
738 t.move(1 , 0, 0, 0)
739 t.cursorY--
740 t.clearLineToRight()
741 }
742
743 if _, err = t.c.Write(t.outBuf); err != nil {
744 return
745 }
746 t.outBuf = t.outBuf[:0]
747
748 if n, err = writeWithCRLF(t.c, buf); err != nil {
749 return
750 }
751
752 t.writeLine(t.prompt)
753 if t.echo {
754 t.writeLine(t.line)
755 }
756
757 t.moveCursorToPos(t.pos)
758
759 if _, err = t.c.Write(t.outBuf); err != nil {
760 return
761 }
762 t.outBuf = t.outBuf[:0]
763 return
764 }
765
766
767
768
769
770 func (t *Terminal) ReadPassword(prompt string) (line string, err error) {
771 t.lock.Lock()
772 defer t.lock.Unlock()
773
774 oldPrompt := t.prompt
775 t.prompt = []rune(prompt)
776 t.echo = false
777 oldAutoCompleteCallback := t.AutoCompleteCallback
778 t.AutoCompleteCallback = nil
779 defer func() {
780 t.AutoCompleteCallback = oldAutoCompleteCallback
781 }()
782
783 line, err = t.readLine()
784
785 t.prompt = oldPrompt
786 t.echo = true
787
788 return
789 }
790
791
792 func (t *Terminal) ReadLine() (line string, err error) {
793 t.lock.Lock()
794 defer t.lock.Unlock()
795
796 return t.readLine()
797 }
798
799 func (t *Terminal) readLine() (line string, err error) {
800
801
802 if t.cursorX == 0 && t.cursorY == 0 {
803 t.writeLine(t.prompt)
804 t.c.Write(t.outBuf)
805 t.outBuf = t.outBuf[:0]
806 }
807
808 lineIsPasted := t.pasteActive
809
810 for {
811 rest := t.remainder
812 lineOk := false
813 for !lineOk {
814 var key rune
815 key, rest = bytesToKey(rest, t.pasteActive)
816 if key == utf8.RuneError {
817 break
818 }
819 if !t.pasteActive {
820 if key == keyCtrlD {
821 if len(t.line) == 0 {
822 return "", io.EOF
823 }
824 }
825 if key == keyCtrlC {
826 return "", io.EOF
827 }
828 if key == keyPasteStart {
829 t.pasteActive = true
830 if len(t.line) == 0 {
831 lineIsPasted = true
832 }
833 continue
834 }
835 } else if key == keyPasteEnd {
836 t.pasteActive = false
837 continue
838 }
839 if !t.pasteActive {
840 lineIsPasted = false
841 }
842
843 if key == keyEnter && len(rest) > 0 && rest[0] == keyLF {
844 rest = rest[1:]
845 }
846 line, lineOk = t.handleKey(key)
847 }
848 if len(rest) > 0 {
849 n := copy(t.inBuf[:], rest)
850 t.remainder = t.inBuf[:n]
851 } else {
852 t.remainder = nil
853 }
854 t.c.Write(t.outBuf)
855 t.outBuf = t.outBuf[:0]
856 if lineOk {
857 if t.echo {
858 t.historyIndex = -1
859 t.historyAdd(line)
860 }
861 if lineIsPasted {
862 err = ErrPasteIndicator
863 }
864 return
865 }
866
867
868
869 readBuf := t.inBuf[len(t.remainder):]
870 var n int
871
872 t.lock.Unlock()
873 n, err = t.c.Read(readBuf)
874 t.lock.Lock()
875
876 if err != nil {
877 return
878 }
879
880 t.remainder = t.inBuf[:n+len(t.remainder)]
881 }
882 }
883
884
885 func (t *Terminal) SetPrompt(prompt string) {
886 t.lock.Lock()
887 defer t.lock.Unlock()
888
889 t.prompt = []rune(prompt)
890 }
891
892 func (t *Terminal) clearAndRepaintLinePlusNPrevious(numPrevLines int) {
893
894 t.move(t.cursorY, 0, t.cursorX, 0)
895 t.cursorX, t.cursorY = 0, 0
896 t.clearLineToRight()
897 for t.cursorY < numPrevLines {
898
899 t.move(0, 1, 0, 0)
900 t.cursorY++
901 t.clearLineToRight()
902 }
903
904 t.move(t.cursorY, 0, 0, 0)
905 t.cursorX, t.cursorY = 0, 0
906
907 t.queue(t.prompt)
908 t.advanceCursor(visualLength(t.prompt))
909 t.writeLine(t.line)
910 t.moveCursorToPos(t.pos)
911 }
912
913 func (t *Terminal) SetSize(width, height int) error {
914 t.lock.Lock()
915 defer t.lock.Unlock()
916
917 if width == 0 {
918 width = 1
919 }
920
921 oldWidth := t.termWidth
922 t.termWidth, t.termHeight = width, height
923
924 switch {
925 case width == oldWidth:
926
927
928 return nil
929 case len(t.line) == 0 && t.cursorX == 0 && t.cursorY == 0:
930
931
932 return nil
933 case width < oldWidth:
934
935
936
937
938
939
940
941
942
943
944
945 if t.cursorX >= t.termWidth {
946 t.cursorX = t.termWidth - 1
947 }
948 t.cursorY *= 2
949 t.clearAndRepaintLinePlusNPrevious(t.maxLine * 2)
950 case width > oldWidth:
951
952
953
954
955
956
957
958 t.clearAndRepaintLinePlusNPrevious(t.maxLine)
959 }
960
961 _, err := t.c.Write(t.outBuf)
962 t.outBuf = t.outBuf[:0]
963 return err
964 }
965
966 type pasteIndicatorError struct{}
967
968 func (pasteIndicatorError) Error() string {
969 return "terminal: ErrPasteIndicator not correctly handled"
970 }
971
972
973
974
975
976 var ErrPasteIndicator = pasteIndicatorError{}
977
978
979
980
981
982
983 func (t *Terminal) SetBracketedPasteMode(on bool) {
984 if on {
985 io.WriteString(t.c, "\x1b[?2004h")
986 } else {
987 io.WriteString(t.c, "\x1b[?2004l")
988 }
989 }
990
991
992 type stRingBuffer struct {
993
994 entries []string
995 max int
996
997 head int
998
999 size int
1000 }
1001
1002 func (s *stRingBuffer) Add(a string) {
1003 if s.entries == nil {
1004 const defaultNumEntries = 100
1005 s.entries = make([]string, defaultNumEntries)
1006 s.max = defaultNumEntries
1007 }
1008
1009 s.head = (s.head + 1) % s.max
1010 s.entries[s.head] = a
1011 if s.size < s.max {
1012 s.size++
1013 }
1014 }
1015
1016 func (s *stRingBuffer) Len() int {
1017 return s.size
1018 }
1019
1020
1021
1022
1023
1024 func (s *stRingBuffer) At(n int) string {
1025 if n < 0 || n >= s.size {
1026 panic(fmt.Sprintf("term: history index [%d] out of range [0,%d)", n, s.size))
1027 }
1028 index := s.head - n
1029 if index < 0 {
1030 index += s.max
1031 }
1032 return s.entries[index]
1033 }
1034
1035
1036
1037
1038
1039
1040 func readPasswordLine(reader io.Reader) ([]byte, error) {
1041 var buf [1]byte
1042 var ret []byte
1043
1044 for {
1045 n, err := reader.Read(buf[:])
1046 if n > 0 {
1047 switch buf[0] {
1048 case '\b':
1049 if len(ret) > 0 {
1050 ret = ret[:len(ret)-1]
1051 }
1052 case '\n':
1053 if runtime.GOOS != "windows" {
1054 return ret, nil
1055 }
1056
1057 case '\r':
1058 if runtime.GOOS == "windows" {
1059 return ret, nil
1060 }
1061
1062 default:
1063 ret = append(ret, buf[0])
1064 }
1065 continue
1066 }
1067 if err != nil {
1068 if err == io.EOF && len(ret) > 0 {
1069 return ret, nil
1070 }
1071 return ret, err
1072 }
1073 }
1074 }
1075
View as plain text