Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(textinput): store and recover history #631

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 75 additions & 2 deletions textinput/textinput.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@ type KeyMap struct {
AcceptSuggestion key.Binding
NextSuggestion key.Binding
PrevSuggestion key.Binding
NextHistory key.Binding
PrevHistory key.Binding
}

// DefaultKeyMap is the default set of key bindings for navigating and acting
Expand All @@ -78,8 +80,11 @@ var DefaultKeyMap = KeyMap{
LineEnd: key.NewBinding(key.WithKeys("end", "ctrl+e")),
Paste: key.NewBinding(key.WithKeys("ctrl+v")),
AcceptSuggestion: key.NewBinding(key.WithKeys("tab")),
NextSuggestion: key.NewBinding(key.WithKeys("down", "ctrl+n")),
PrevSuggestion: key.NewBinding(key.WithKeys("up", "ctrl+p")),
NextSuggestion: key.NewBinding(key.WithKeys("ctrl+n")),
PrevSuggestion: key.NewBinding(key.WithKeys("ctrl+p")),
// TODO: discuss moving `down` and `up` to this instead of Next/PrevSuggestion
NextHistory: key.NewBinding(key.WithKeys("down")),
PrevHistory: key.NewBinding(key.WithKeys("up")),
Comment on lines +85 to +87
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up and Down feel more natural to me, as a shell/vim user than updating completion. However this might break existing applications and change the default behavior, so I'd be happy not to change it.

Which keys would you recommend?

}

// Model is the Bubble Tea model for this text input element.
Expand Down Expand Up @@ -152,6 +157,10 @@ type Model struct {
suggestions [][]rune
matchedSuggestions [][]rune
currentSuggestionIndex int

// history of inputs
history [][]rune
historyIndex int
}

// New creates a new model with default settings.
Expand All @@ -170,6 +179,9 @@ func New() Model {
value: nil,
focus: false,
pos: 0,

history: [][]rune{},
historyIndex: 0,
}
}

Expand All @@ -184,6 +196,7 @@ func (m *Model) SetValue(s string) {
// caller. This avoids bugs due to e.g. tab characters and whatnot.
runes := m.san().Sanitize([]rune(s))
err := m.validate(runes)
m.SaveHistory()
m.setValueInternal(runes, err)
}

Expand Down Expand Up @@ -245,12 +258,14 @@ func (m *Model) Focus() tea.Cmd {
// Blur removes the focus state on the model. When the model is blurred it can
// not receive keyboard input and the cursor will be hidden.
func (m *Model) Blur() {
m.SaveHistory()
m.focus = false
m.Cursor.Blur()
}

// Reset sets the input to its default state with no input.
func (m *Model) Reset() {
m.SaveHistory()
m.value = nil
m.SetCursor(0)
}
Expand Down Expand Up @@ -614,6 +629,10 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.nextSuggestion()
case key.Matches(msg, m.KeyMap.PrevSuggestion):
m.previousSuggestion()
case key.Matches(msg, m.KeyMap.NextHistory):
m.nextHistory()
case key.Matches(msg, m.KeyMap.PrevHistory):
m.prevHistory()
default:
// Input one or more regular characters.
m.insertRunesFromUserInput(msg.Runes)
Expand All @@ -624,6 +643,7 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
m.updateSuggestions()

case pasteMsg:
m.SaveHistory()
m.insertRunesFromUserInput([]rune(msg))

case pasteErrMsg:
Expand Down Expand Up @@ -899,3 +919,56 @@ func (m Model) validate(v []rune) error {
}
return nil
}

func (m *Model) SaveHistory() {
if len(m.value) == 0 {
return
}

if m.EchoMode != EchoNormal {
return
}

// XXX: It seems that doing `append(m.history, m.value)` copies a reference
// to the value, which leads to all history items being similar.
// This is quite surprising given this other test:
// https://go.dev/play/p/MZGpcUzSVch
newRecord := make([]rune, len(m.value))
for i, r := range m.value {
newRecord[i] = r
}
Comment on lines +932 to +939
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels suboptimal, but I didn't find any alternative solution.


m.history = append(m.history, newRecord)
m.historyIndex = len(m.history) - 1
}

func (m Model) History() []string {
// TODO: rename getSuggestions to getStrings
return m.getSuggestions(m.history)
Comment on lines +946 to +947
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The m.getSuggestions really just transforms a [][]rune into a []string, so the name could be updated to reflect this.

}

func (m *Model) nextHistory() {
if len(m.history) == 0 {
return
}

m.historyIndex = (m.historyIndex + 1)
if m.historyIndex > len(m.history)-1 {
m.historyIndex = 0
}

m.setValueInternal(m.history[m.historyIndex], nil)
}

func (m *Model) prevHistory() {
if len(m.history) == 0 {
return
}

m.historyIndex = (m.historyIndex - 1)
if m.historyIndex < 0 {
m.historyIndex = len(m.history) - 1
}

m.setValueInternal(m.history[m.historyIndex], nil)
}