From ed77b0f4ebd133e4c497f0246f07b368bef71967 Mon Sep 17 00:00:00 2001 From: Laurent Demailly Date: Fri, 9 Aug 2024 11:29:25 -0700 Subject: [PATCH] Add ReplaceLatest() to mutate top of history. Add comments. Expose DefaultHistoryEntries as const (#5) * Add ReplaceLatest() to mutate top of history. Add comments. Expose DefaultHistoryEntries as const * Correct godoc for ReplaceLatest --- terminal.go | 32 +++++++++++++++++++++++++------- terminal_test.go | 5 +++++ 2 files changed, 30 insertions(+), 7 deletions(-) diff --git a/terminal.go b/terminal.go index 0b2e0c3..1c35c6c 100644 --- a/terminal.go +++ b/terminal.go @@ -113,7 +113,7 @@ func NewTerminal(c io.ReadWriter, prompt string) *Terminal { termHeight: 24, echo: true, historyIndex: -1, - history: NewHistory(defaultNumEntries), + history: NewHistory(DefaultHistoryEntries), autoHistory: true, } } @@ -844,6 +844,15 @@ func (t *Terminal) AutoHistory(onOff bool) { t.lock.Unlock() } +// ReplaceLatest replaces the most recent history entry with the given string. +// Enables to add invalid commands to the history for editing purpose and +// replace them with the corrected version. Returns the replaced entry. +func (t *Terminal) ReplaceLatest(entry string) string { + t.lock.Lock() + defer t.lock.Unlock() + return t.history.Replace(entry) +} + // SetPrompt sets the prompt to be used when reading subsequent lines. func (t *Terminal) SetPrompt(prompt string) { t.lock.Lock() @@ -962,6 +971,7 @@ type stRingBuffer struct { size int } +// Creates a new ring buffer of strings with the given capacity. func NewHistory(capacity int) *stRingBuffer { return &stRingBuffer{ entries: make([]string, capacity), @@ -969,15 +979,15 @@ func NewHistory(capacity int) *stRingBuffer { } } -const defaultNumEntries = 100 +// DefaultHistoryEntries is the default number of entries in the history. +const DefaultHistoryEntries = 100 func (s *stRingBuffer) Add(a string) { - if s.entries == nil { - s.entries = make([]string, defaultNumEntries) - s.max = defaultNumEntries - } if s.entries[s.head] == a { - return // already there at the top + // Already there at the top, so don't add. + // Also has the nice side effect of ignoring empty strings, + // no s.size check on purpose. + return } s.head = (s.head + 1) % s.max s.entries[s.head] = a @@ -986,6 +996,14 @@ func (s *stRingBuffer) Add(a string) { } } +// Replace theoretically could panic on an empty ring buffer but +// it's harmless on strings. +func (s *stRingBuffer) Replace(a string) string { + previous := s.entries[s.head] + s.entries[s.head] = a + return previous +} + // NthPreviousEntry returns the value passed to the nth previous call to Add. // If n is zero then the immediately prior value is returned, if one, then the // next most recent, and so on. If such an element doesn't exist then ok is diff --git a/terminal_test.go b/terminal_test.go index 97a5e5f..4008cdf 100644 --- a/terminal_test.go +++ b/terminal_test.go @@ -462,4 +462,9 @@ func TestHistoryNoDuplicates(t *testing.T) { if !reflect.DeepEqual(h, []string{"c", "b", "a"}) { t.Errorf("history unexpected: %v", h) } + ss.ReplaceLatest("x") + h = ss.History() + if !reflect.DeepEqual(h, []string{"x", "b", "a"}) { + t.Errorf("history unexpected: %v", h) + } }