diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-04 08:08:13 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-04 08:08:13 +0200 |
| commit | c3c8282a7989016d4a7a763d8cfc5e8f25117a08 (patch) | |
| tree | c29ab3b5d260cad277778f6370e778a2551bf869 | |
| parent | 790ed02ed9d1bae8624c30831d4114187463abd9 (diff) | |
tui: make entry input backspace rune-safe
| -rw-r--r-- | internal/tui/entries.go | 22 | ||||
| -rw-r--r-- | internal/tui/entries_test.go | 22 |
2 files changed, 38 insertions, 6 deletions
diff --git a/internal/tui/entries.go b/internal/tui/entries.go index 512e9d2..ca84300 100644 --- a/internal/tui/entries.go +++ b/internal/tui/entries.go @@ -7,6 +7,7 @@ import ( "strconv" "strings" "time" + "unicode/utf8" "codeberg.org/snonux/timr/internal/duration" "codeberg.org/snonux/timr/internal/worktime" @@ -150,9 +151,7 @@ func (m *EntriesModel) updateEditMode(keyMsg tea.KeyMsg) bool { m.editMode = false m.input = "" case "backspace": - if len(m.input) > 0 { - m.input = m.input[:len(m.input)-1] - } + m.input = trimLastRune(m.input) default: if keyMsg.Type == tea.KeyRunes { m.input += string(keyMsg.Runes) @@ -183,9 +182,7 @@ func (m *EntriesModel) updateSearchFilterMode(keyMsg tea.KeyMsg) bool { m.filterMode = false m.input = "" case "backspace": - if len(m.input) > 0 { - m.input = m.input[:len(m.input)-1] - } + m.input = trimLastRune(m.input) default: if keyMsg.Type == tea.KeyRunes { m.input += string(keyMsg.Runes) @@ -695,3 +692,16 @@ func insertEntryAt(entries []worktime.Entry, idx int, entry worktime.Entry) []wo entries[idx] = entry return entries } + +func trimLastRune(value string) string { + if value == "" { + return "" + } + + _, size := utf8.DecodeLastRuneInString(value) + if size <= 0 { + return "" + } + + return value[:len(value)-size] +} diff --git a/internal/tui/entries_test.go b/internal/tui/entries_test.go index 9377f30..da5a5c4 100644 --- a/internal/tui/entries_test.go +++ b/internal/tui/entries_test.go @@ -141,6 +141,28 @@ func TestEntriesValueEditFlow(t *testing.T) { } } +func TestEntriesBackspaceIsRuneSafeInEditMode(t *testing.T) { + model := NewEntriesModel(sampleEntries(1)) + model.editMode = true + model.input = "aй" + + model, _ = model.Update(tea.KeyMsg{Type: tea.KeyBackspace}) + if model.input != "a" { + t.Fatalf("input after backspace = %q, want %q", model.input, "a") + } +} + +func TestEntriesBackspaceIsRuneSafeInSearchMode(t *testing.T) { + model := NewEntriesModel(sampleEntries(1)) + model.searchMode = true + model.input = "тест" + + model, _ = model.Update(tea.KeyMsg{Type: tea.KeyBackspace}) + if model.input != "тес" { + t.Fatalf("input after backspace = %q, want %q", model.input, "тес") + } +} + func TestEntriesDeleteWithConfirmation(t *testing.T) { model := NewEntriesModel(sampleEntries(3)) model.SetSize(120, 12) |
