@@ -6,6 +6,7 @@ package term
66
77import (
88 "bytes"
9+ "fmt"
910 "io"
1011 "runtime"
1112 "strconv"
@@ -36,6 +37,26 @@ var vt100EscapeCodes = EscapeCodes{
3637 Reset : []byte {keyEscape , '[' , '0' , 'm' },
3738}
3839
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+
3960// Terminal contains the state for running a VT100 terminal that is capable of
4061// reading lines of input.
4162type Terminal struct {
@@ -86,9 +107,14 @@ type Terminal struct {
86107 remainder []byte
87108 inBuf [256 ]byte
88109
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
92118 // historyIndex stores the currently accessed history entry, where zero
93119 // means the immediately previous entry.
94120 historyIndex int
@@ -111,6 +137,7 @@ func NewTerminal(c io.ReadWriter, prompt string) *Terminal {
111137 termHeight : 24 ,
112138 echo : true ,
113139 historyIndex : - 1 ,
140+ History : & stRingBuffer {},
114141 }
115142}
116143
@@ -450,6 +477,23 @@ func visualLength(runes []rune) int {
450477 return length
451478}
452479
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+
453497// handleKey processes the given key and, optionally, returns a line of text
454498// that the user has entered.
455499func (t * Terminal ) handleKey (key rune ) (line string , ok bool ) {
@@ -497,7 +541,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
497541 t .pos = len (t .line )
498542 t .moveCursorToPos (t .pos )
499543 case keyUp :
500- entry , ok := t .history . NthPreviousEntry (t .historyIndex + 1 )
544+ entry , ok := t .historyAt (t .historyIndex + 1 )
501545 if ! ok {
502546 return "" , false
503547 }
@@ -516,7 +560,7 @@ func (t *Terminal) handleKey(key rune) (line string, ok bool) {
516560 t .setLine (runes , len (runes ))
517561 t .historyIndex --
518562 default :
519- entry , ok := t .history . NthPreviousEntry (t .historyIndex - 1 )
563+ entry , ok := t .historyAt (t .historyIndex - 1 )
520564 if ok {
521565 t .historyIndex --
522566 runes := []rune (entry )
@@ -781,7 +825,7 @@ func (t *Terminal) readLine() (line string, err error) {
781825 if lineOk {
782826 if t .echo {
783827 t .historyIndex = - 1
784- t .history . Add (line )
828+ t .historyAdd (line )
785829 }
786830 if lineIsPasted {
787831 err = ErrPasteIndicator
@@ -938,19 +982,23 @@ func (s *stRingBuffer) Add(a string) {
938982 }
939983}
940984
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.
942990// If n is zero then the immediately prior value is returned, if one, then the
943991// next most recent, and so on. If such an element doesn't exist then ok is
944992// false.
945- func (s * stRingBuffer ) NthPreviousEntry (n int ) ( value string , ok bool ) {
993+ func (s * stRingBuffer ) At (n int ) string {
946994 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 ))
948996 }
949997 index := s .head - n
950998 if index < 0 {
951999 index += s .max
9521000 }
953- return s .entries [index ], true
1001+ return s .entries [index ]
9541002}
9551003
9561004// readPasswordLine reads from reader until it finds \n or io.EOF.
0 commit comments