summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-04 08:08:13 +0200
committerPaul Buetow <paul@buetow.org>2026-03-04 08:08:13 +0200
commitc3c8282a7989016d4a7a763d8cfc5e8f25117a08 (patch)
treec29ab3b5d260cad277778f6370e778a2551bf869
parent790ed02ed9d1bae8624c30831d4114187463abd9 (diff)
tui: make entry input backspace rune-safe
-rw-r--r--internal/tui/entries.go22
-rw-r--r--internal/tui/entries_test.go22
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)