@@ -6,6 +6,7 @@ package term
6
6
7
7
import (
8
8
"bytes"
9
+ "fmt"
9
10
"io"
10
11
"runtime"
11
12
"strconv"
@@ -36,6 +37,26 @@ var vt100EscapeCodes = EscapeCodes{
36
37
Reset : []byte {keyEscape , '[' , '0' , 'm' },
37
38
}
38
39
40
+ // A History provides a (possibly bounded) queue of input lines read by [Terminal.ReadLine].
41
+ type History interface {
42
+ // Add will be called by [Terminal.ReadLine] to add
43
+ // a new, most recent entry to the history.
44
+ // It is allowed to drop any entry, including
45
+ // the entry being added (e.g., if it's deemed an invalid entry),
46
+ // the least-recent entry (e.g., to keep the history bounded),
47
+ // or any other entry.
48
+ Add (entry string )
49
+
50
+ // Len returns the number of entries in the history.
51
+ Len () int
52
+
53
+ // At returns an entry from the history.
54
+ // Index 0 is the most-recently added entry and
55
+ // index Len()-1 is the least-recently added entry.
56
+ // If index is < 0 or >= Len(), it panics.
57
+ At (idx int ) string
58
+ }
59
+
39
60
// Terminal contains the state for running a VT100 terminal that is capable of
40
61
// reading lines of input.
41
62
type Terminal struct {
@@ -86,9 +107,14 @@ type Terminal struct {
86
107
remainder []byte
87
108
inBuf [256 ]byte
88
109
89
- // history contains previously entered commands so that they can be
90
- // accessed with the up and down keys.
91
- history stRingBuffer
110
+ // History records and retrieves lines of input read by [ReadLine] which
111
+ // a user can retrieve and navigate using the up and down arrow keys.
112
+ //
113
+ // It is not safe to call ReadLine concurrently with any methods on History.
114
+ //
115
+ // [NewTerminal] sets this to a default implementation that records the
116
+ // last 100 lines of input.
117
+ History History
92
118
// historyIndex stores the currently accessed history entry, where zero
93
119
// means the immediately previous entry.
94
120
historyIndex int
@@ -111,6 +137,7 @@ func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
111
137
termHeight : 24 ,
112
138
echo : true ,
113
139
historyIndex : - 1 ,
140
+ History : & stRingBuffer {},
114
141
}
115
142
}
116
143
@@ -450,6 +477,23 @@ func visualLength(runes []rune) int {
450
477
return length
451
478
}
452
479
480
+ // histroryAt unlocks the terminal and relocks it while calling History.At.
481
+ func (t * Terminal ) historyAt (idx int ) (string , bool ) {
482
+ t .lock .Unlock () // Unlock to avoid deadlock if History methods use the output writer.
483
+ defer t .lock .Lock () // panic in At (or Len) protection.
484
+ if idx < 0 || idx >= t .History .Len () {
485
+ return "" , false
486
+ }
487
+ return t .History .At (idx ), true
488
+ }
489
+
490
+ // historyAdd unlocks the terminal and relocks it while calling History.Add.
491
+ func (t * Terminal ) historyAdd (entry string ) {
492
+ t .lock .Unlock () // Unlock to avoid deadlock if History methods use the output writer.
493
+ defer t .lock .Lock () // panic in Add protection.
494
+ t .History .Add (entry )
495
+ }
496
+
453
497
// handleKey processes the given key and, optionally, returns a line of text
454
498
// that the user has entered.
455
499
func (t * Terminal ) handleKey (key rune ) (line string , ok bool ) {
@@ -497,7 +541,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
497
541
t .pos = len (t .line )
498
542
t .moveCursorToPos (t .pos )
499
543
case keyUp :
500
- entry , ok := t .history . NthPreviousEntry (t .historyIndex + 1 )
544
+ entry , ok := t .historyAt (t .historyIndex + 1 )
501
545
if ! ok {
502
546
return "" , false
503
547
}
@@ -516,7 +560,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
516
560
t .setLine (runes , len (runes ))
517
561
t .historyIndex --
518
562
default :
519
- entry , ok := t .history . NthPreviousEntry (t .historyIndex - 1 )
563
+ entry , ok := t .historyAt (t .historyIndex - 1 )
520
564
if ok {
521
565
t .historyIndex --
522
566
runes := []rune (entry )
@@ -781,7 +825,7 @@ func (t *Terminal) readLine() (line string, err error) {
781
825
if lineOk {
782
826
if t .echo {
783
827
t .historyIndex = - 1
784
- t .history . Add (line )
828
+ t .historyAdd (line )
785
829
}
786
830
if lineIsPasted {
787
831
err = ErrPasteIndicator
@@ -938,19 +982,23 @@ func (s *stRingBuffer) Add(a string) {
938
982
}
939
983
}
940
984
941
- // NthPreviousEntry returns the value passed to the nth previous call to Add.
985
+ func (s * stRingBuffer ) Len () int {
986
+ return s .size
987
+ }
988
+
989
+ // At returns the value passed to the nth previous call to Add.
942
990
// If n is zero then the immediately prior value is returned, if one, then the
943
991
// next most recent, and so on. If such an element doesn't exist then ok is
944
992
// false.
945
- func (s * stRingBuffer ) NthPreviousEntry (n int ) ( value string , ok bool ) {
993
+ func (s * stRingBuffer ) At (n int ) string {
946
994
if n < 0 || n >= s .size {
947
- return "" , false
995
+ panic ( fmt . Sprintf ( "term: history index [%d] out of range [0,%d)" , n , s . size ))
948
996
}
949
997
index := s .head - n
950
998
if index < 0 {
951
999
index += s .max
952
1000
}
953
- return s .entries [index ], true
1001
+ return s .entries [index ]
954
1002
}
955
1003
956
1004
// readPasswordLine reads from reader until it finds \n or io.EOF.
0 commit comments