Skip to content

Commit 35f0200

Browse files
feat(textinput): do not block input on validation (charmbracelet#185)
* feat(textinput): do not block input on validation This PR builds upon the excellent work in charmbracelet#167 and charmbracelet#114 and makes a breaking change to the validation API. Currently, validation will completely block text input if the Validate function returns an error. This is now changed so the function no longer blocks input if this is the case, thus handing this responsibility to the clients. This is helpful for cases where the user is requested to type an existing system path, and the Validate function keeps asserting the existence of the path. With the current implementation such a validation is not possible. For example: > / Err: nil > /t Err: /t: No such file or directory > /tm Err: /tm: No such file or directory > /tmp Err: nil * fix: change name --------- Co-authored-by: Maas Lalani <[email protected]>
1 parent 089b4db commit 35f0200

File tree

1 file changed

+19
-19
lines changed

1 file changed

+19
-19
lines changed

textinput/textinput.go

+19-19
Original file line numberDiff line numberDiff line change
@@ -181,19 +181,14 @@ func (m *Model) SetValue(s string) {
181181
// Clean up any special characters in the input provided by the
182182
// caller. This avoids bugs due to e.g. tab characters and whatnot.
183183
runes := m.san().Sanitize([]rune(s))
184-
m.setValueInternal(runes)
184+
err := m.validate(runes)
185+
m.setValueInternal(runes, err)
185186
}
186187

187-
func (m *Model) setValueInternal(runes []rune) {
188-
if m.Validate != nil {
189-
if err := m.Validate(string(runes)); err != nil {
190-
m.Err = err
191-
return
192-
}
193-
}
188+
func (m *Model) setValueInternal(runes []rune, err error) {
189+
m.Err = err
194190

195191
empty := len(m.value) == 0
196-
m.Err = nil
197192

198193
if m.CharLimit > 0 && len(runes) > m.CharLimit {
199194
m.value = runes[:m.CharLimit]
@@ -307,8 +302,6 @@ func (m *Model) insertRunesFromUserInput(v []rune) {
307302
tail := make([]rune, len(tailSrc))
308303
copy(tail, tailSrc)
309304

310-
oldPos := m.pos
311-
312305
// Insert pasted runes
313306
for _, r := range paste {
314307
head = append(head, r)
@@ -323,11 +316,8 @@ func (m *Model) insertRunesFromUserInput(v []rune) {
323316

324317
// Put it all back together
325318
value := append(head, tail...)
326-
m.setValueInternal(value)
327-
328-
if m.Err != nil {
329-
m.pos = oldPos
330-
}
319+
inputErr := m.validate(value)
320+
m.setValueInternal(value, inputErr)
331321
}
332322

333323
// If a max width is defined, perform some logic to treat the visible area
@@ -378,6 +368,7 @@ func (m *Model) handleOverflow() {
378368
// deleteBeforeCursor deletes all text before the cursor.
379369
func (m *Model) deleteBeforeCursor() {
380370
m.value = m.value[m.pos:]
371+
m.Err = m.validate(m.value)
381372
m.offset = 0
382373
m.SetCursor(0)
383374
}
@@ -387,6 +378,7 @@ func (m *Model) deleteBeforeCursor() {
387378
// masked input.
388379
func (m *Model) deleteAfterCursor() {
389380
m.value = m.value[:m.pos]
381+
m.Err = m.validate(m.value)
390382
m.SetCursor(len(m.value))
391383
}
392384

@@ -432,6 +424,7 @@ func (m *Model) deleteWordBackward() {
432424
} else {
433425
m.value = append(m.value[:m.pos], m.value[oldPos:]...)
434426
}
427+
m.Err = m.validate(m.value)
435428
}
436429

437430
// deleteWordForward deletes the word right to the cursor. If input is masked
@@ -471,6 +464,7 @@ func (m *Model) deleteWordForward() {
471464
} else {
472465
m.value = append(m.value[:oldPos], m.value[m.pos:]...)
473466
}
467+
m.Err = m.validate(m.value)
474468

475469
m.SetCursor(oldPos)
476470
}
@@ -575,12 +569,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
575569
case tea.KeyMsg:
576570
switch {
577571
case key.Matches(msg, m.KeyMap.DeleteWordBackward):
578-
m.Err = nil
579572
m.deleteWordBackward()
580573
case key.Matches(msg, m.KeyMap.DeleteCharacterBackward):
581574
m.Err = nil
582575
if len(m.value) > 0 {
583576
m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...)
577+
m.Err = m.validate(m.value)
584578
if m.pos > 0 {
585579
m.SetCursor(m.pos - 1)
586580
}
@@ -597,13 +591,12 @@ func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
597591
if m.pos < len(m.value) {
598592
m.SetCursor(m.pos + 1)
599593
}
600-
case key.Matches(msg, m.KeyMap.DeleteWordBackward):
601-
m.deleteWordBackward()
602594
case key.Matches(msg, m.KeyMap.LineStart):
603595
m.CursorStart()
604596
case key.Matches(msg, m.KeyMap.DeleteCharacterForward):
605597
if len(m.value) > 0 && m.pos < len(m.value) {
606598
m.value = append(m.value[:m.pos], m.value[m.pos+1:]...)
599+
m.Err = m.validate(m.value)
607600
}
608601
case key.Matches(msg, m.KeyMap.LineEnd):
609602
m.CursorEnd()
@@ -884,3 +877,10 @@ func (m *Model) previousSuggestion() {
884877
m.currentSuggestionIndex = len(m.matchedSuggestions) - 1
885878
}
886879
}
880+
881+
func (m Model) validate(v []rune) error {
882+
if m.Validate != nil {
883+
return m.Validate(string(v))
884+
}
885+
return nil
886+
}

0 commit comments

Comments
 (0)