diff --git a/textinput/textinput.go b/textinput/textinput.go index 82341b97..229dc5ab 100644 --- a/textinput/textinput.go +++ b/textinput/textinput.go @@ -35,8 +35,9 @@ const ( EchoNone ) -// ValidateFunc is a function that returns an error if the input is invalid. -type ValidateFunc func(string) error +// ValidateFunc is a function that returns an error if the input is invalid and +// a boolean indicating whether text input should be blocked. +type ValidateFunc func(string) (bool, error) // KeyMap is the key bindings for different actions within the textinput. type KeyMap struct { @@ -180,19 +181,17 @@ func (m *Model) SetValue(s string) { // Clean up any special characters in the input provided by the // caller. This avoids bugs due to e.g. tab characters and whatnot. runes := m.san().Sanitize([]rune(s)) - m.setValueInternal(runes) + blockInput, err := m.validateIfDefined(string(runes)) + m.setValueInternal(runes, err, blockInput) } -func (m *Model) setValueInternal(runes []rune) { - if m.Validate != nil { - if err := m.Validate(string(runes)); err != nil { - m.Err = err - return - } +func (m *Model) setValueInternal(runes []rune, err error, blockInput bool) { + m.Err = err + if blockInput { + return } empty := len(m.value) == 0 - m.Err = nil if m.CharLimit > 0 && len(runes) > m.CharLimit { m.value = runes[:m.CharLimit] @@ -323,9 +322,10 @@ func (m *Model) insertRunesFromUserInput(v []rune) { // Put it all back together value := append(head, tail...) - m.setValueInternal(value) + blockInput, inputErr := m.validateIfDefined(string(value)) + m.setValueInternal(value, inputErr, blockInput) - if m.Err != nil { + if blockInput { m.pos = oldPos } } @@ -378,6 +378,7 @@ func (m *Model) handleOverflow() { // deleteBeforeCursor deletes all text before the cursor. func (m *Model) deleteBeforeCursor() { m.value = m.value[m.pos:] + _, m.Err = m.validateIfDefined(string(m.value)) m.offset = 0 m.SetCursor(0) } @@ -387,6 +388,7 @@ func (m *Model) deleteBeforeCursor() { // masked input. func (m *Model) deleteAfterCursor() { m.value = m.value[:m.pos] + _, m.Err = m.validateIfDefined(string(m.value)) m.SetCursor(len(m.value)) } @@ -432,6 +434,7 @@ func (m *Model) deleteWordBackward() { } else { m.value = append(m.value[:m.pos], m.value[oldPos:]...) } + _, m.Err = m.validateIfDefined(string(m.value)) } // deleteWordForward deletes the word right to the cursor. If input is masked @@ -471,6 +474,7 @@ func (m *Model) deleteWordForward() { } else { m.value = append(m.value[:oldPos], m.value[m.pos:]...) } + _, m.Err = m.validateIfDefined(string(m.value)) m.SetCursor(oldPos) } @@ -575,12 +579,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { case tea.KeyMsg: switch { case key.Matches(msg, m.KeyMap.DeleteWordBackward): - m.Err = nil m.deleteWordBackward() case key.Matches(msg, m.KeyMap.DeleteCharacterBackward): m.Err = nil if len(m.value) > 0 { m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...) + _, m.Err = m.validateIfDefined(string(m.value)) if m.pos > 0 { m.SetCursor(m.pos - 1) } @@ -597,13 +601,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { if m.pos < len(m.value) { m.SetCursor(m.pos + 1) } - case key.Matches(msg, m.KeyMap.DeleteWordBackward): - m.deleteWordBackward() case key.Matches(msg, m.KeyMap.LineStart): m.CursorStart() case key.Matches(msg, m.KeyMap.DeleteCharacterForward): if len(m.value) > 0 && m.pos < len(m.value) { m.value = append(m.value[:m.pos], m.value[m.pos+1:]...) + _, m.Err = m.validateIfDefined(string(m.value)) } case key.Matches(msg, m.KeyMap.LineEnd): m.CursorEnd() @@ -859,3 +862,10 @@ func (m *Model) previousSuggestion() { m.currentSuggestionIndex = len(m.matchedSuggestions) - 1 } } + +func (m Model) validateIfDefined(v string) (bool, error) { + if m.Validate != nil { + return m.Validate(v) + } + return false, nil +}