package tui import ( "github.com/rivo/uniseg" "zgo.at/termfo/keys" "strings" ) type TextInput struct { id string linesBefore []string beforeCursor string selection string selectionBefore bool afterCursor string linesAfter []string } func (t *TextInput) Text() string { return t.beforeCursor + t.selection + t.afterCursor } func (t *TextInput) SetText(text string) { t.beforeCursor = "" t.afterCursor = text } func toGraphemes(s string) []string { g := uniseg.NewGraphemes(s) var result []string for g.Next() { result = append(result, g.Str()) } return result } func splitRight(s string) (string, string) { if s == "" { return "", "" } gs := toGraphemes(s) return strings.Join(gs[:len(gs) - 1], ""), gs[len(gs) - 1] } func splitLeft(s string) (string, string) { if s == "" { return "", "" } cluster, rest, _, _ := uniseg.FirstGraphemeCluster([]byte(s), 0) return string(cluster), string(rest) } func (t *TextInput) Left(selection bool, word bool) { t.Deselect() var right string t.beforeCursor, right = splitRight(t.beforeCursor) t.afterCursor = right + t.afterCursor } func (t *TextInput) Right(selection bool, word bool) { t.Deselect() var left string left, t.afterCursor = splitLeft(t.afterCursor) t.beforeCursor += left } func (t *TextInput) Start(selection bool) { t.afterCursor = t.beforeCursor + t.afterCursor t.beforeCursor = "" } func (t *TextInput) End(selection bool) { t.beforeCursor = t.beforeCursor + t.afterCursor t.afterCursor = "" } func (t *TextInput) Selection() string { return t.selection } func (t *TextInput) Deselect() { if t.selectionBefore { t.beforeCursor += t.selection } else { t.afterCursor = t.selection + t.afterCursor } t.selection = "" } func (t *TextInput) Write(text string) { t.selection = "" t.beforeCursor += text } func (t *TextInput) Update(ev Event) (usedKeybind bool) { if Selected != t.id { return } if ev.TextInput != 0 { t.Write(string(ev.TextInput)) } selection := ev.Key & keys.Shift != 0 word := ev.Key & keys.Ctrl != 0 switch ev.Key.WithoutMods() { case keys.Left: t.Left(selection, word) case keys.Right: t.Right(selection, word) case 'a': if ev.Key & keys.Ctrl != 0 { t.Start(selection) } case 'e': if ev.Key & keys.Ctrl != 0 { t.End(selection) } case keys.Backspace: if t.selection != "" { t.selection = "" } else { t.beforeCursor, _ = splitRight(t.beforeCursor) } default: return false } return true } func (t *TextInput) Show(id string) { t.id = id Push(id, Box {Width: Fill, Height: 4})//TextSize}) Text(t.beforeCursor, nil) if t.selectionBefore { Text(t.selection, &Style {Bg: Blue, Fg: White, selected: true}) } Cursor() if !t.selectionBefore { Text(t.selection, &Style {Bg: Blue, Fg: White, selected: true}) } Text(t.afterCursor, nil) Pop() }