diff --git a/README.md b/README.md index d03d0ae..5a66244 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,12 @@ # Go terminal/console support -[![Go Reference](https://pkg.go.dev/badge/golang.org/x/term.svg)](https://pkg.go.dev/golang.org/x/term) +[![Go Reference](https://pkg.go.dev/badge/fortio.org/term.svg)](https://pkg.go.dev/fortio.org/term) This repository provides Go terminal and console support packages. +It's a fork of the [golang.org/x/term](https://pkg.go.dev/golang.org/x/term) pending approval of improvements upstream. ## Download/Install -The easiest way to install is to run `go get -u golang.org/x/term`. You can -also manually git clone the repository to `$GOPATH/src/golang.org/x/term`. +The easiest way to use is to run `go get -u fortio.org/term`. -## Report Issues / Send Patches - -This repository uses Gerrit for code changes. To learn how to submit changes to -this repository, see https://golang.org/doc/contribute.html. - -The main issue tracker for the term repository is located at -https://github.com/golang/go/issues. Prefix your issue with "x/term:" in the -subject line, so it is easy to find. +Rather than use this lowlevel library, use [fortio.org/terminal](https://github.com/fortio/terminal#terminal) which wraps this one into a higher level and simpler more powerful API. diff --git a/go.mod b/go.mod index de7ea0f..85f7984 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module golang.org/x/term +module fortio.org/term go 1.18 diff --git a/term_test.go b/term_test.go index 561f3d4..48e5d57 100644 --- a/term_test.go +++ b/term_test.go @@ -9,7 +9,7 @@ import ( "runtime" "testing" - "golang.org/x/term" + "fortio.org/term" ) func TestIsTerminalTempFile(t *testing.T) { diff --git a/terminal.go b/terminal.go index f636667..74ce989 100644 --- a/terminal.go +++ b/terminal.go @@ -86,7 +86,7 @@ type Terminal struct { // history contains previously entered commands so that they can be // accessed with the up and down keys. - history stRingBuffer + history *stRingBuffer // historyIndex stores the currently accessed history entry, where zero // means the immediately previous entry. historyIndex int @@ -109,6 +109,7 @@ func NewTerminal(c io.ReadWriter, prompt string) *Terminal { termHeight: 24, echo: true, historyIndex: -1, + history: NewHistory(defaultNumEntries), } } @@ -797,6 +798,37 @@ func (t *Terminal) readLine() (line string, err error) { } } +// History returns a slice of strings containing the history of entered commands so far. +func (t *Terminal) History() []string { + t.lock.Lock() + defer t.lock.Unlock() + res := []string{} + for i := 0; ; i++ { + c, ok := t.history.NthPreviousEntry(i) + if !ok { + break + } + res = append(res, c) + } + return res +} + +// NewHistory resets the history to one of a given capacity. +func (t *Terminal) NewHistory(capacity int) { + t.lock.Lock() + defer t.lock.Unlock() + t.history = NewHistory(capacity) +} + +// AddToHistory populates history. +func (t *Terminal) AddToHistory(entry ...string) { + t.lock.Lock() + defer t.lock.Unlock() + for _, e := range entry { + t.history.Add(e) + } +} + // SetPrompt sets the prompt to be used when reading subsequent lines. func (t *Terminal) SetPrompt(prompt string) { t.lock.Lock() @@ -915,13 +947,23 @@ type stRingBuffer struct { size int } +func NewHistory(capacity int) *stRingBuffer { + return &stRingBuffer{ + entries: make([]string, capacity), + max: capacity, + } +} + +const defaultNumEntries = 100 + func (s *stRingBuffer) Add(a string) { if s.entries == nil { - const defaultNumEntries = 100 s.entries = make([]string, defaultNumEntries) s.max = defaultNumEntries } - + if s.entries[s.head] == a { + return // already there at the top + } s.head = (s.head + 1) % s.max s.entries[s.head] = a if s.size < s.max { diff --git a/terminal_test.go b/terminal_test.go index d5c1794..97a5e5f 100644 --- a/terminal_test.go +++ b/terminal_test.go @@ -6,8 +6,10 @@ package term import ( "bytes" + "errors" "io" "os" + "reflect" "runtime" "testing" ) @@ -435,3 +437,29 @@ func TestOutputNewlines(t *testing.T) { t.Errorf("incorrect output: was %q, expected %q", output, expected) } } + +func TestHistoryNoDuplicates(t *testing.T) { + c := &MockTerminal{ + toSend: []byte("a\rb\rb\rb\rc\r"), // 5 with 3 duplicate "b" + bytesPerRead: 1, + } + ss := NewTerminal(c, "> ") + count := 0 + for { + _, err := ss.ReadLine() + if errors.Is(err, io.EOF) { + break + } + count++ + } + if count != 5 { + t.Errorf("expected 5 lines, got %d", count) + } + h := ss.History() + if len(h) != 3 { + t.Errorf("history length should be 3, got %d", len(h)) + } + if !reflect.DeepEqual(h, []string{"c", "b", "a"}) { + t.Errorf("history unexpected: %v", h) + } +}