summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcitrons <citrons@mondecitronne.com>2025-06-01 06:16:44 -0500
committercitrons <citrons@mondecitronne.com>2025-06-01 06:16:44 -0500
commitf36a37c8e2acb91b69979f2f3d07b19f54725cca (patch)
tree7d5a5f49514fa2f7351e5ae8e7618dedfc53d01b
parentb11c892158772f508e494b2726a5d4db1bb74d23 (diff)
movement modes and selection
-rw-r--r--tui/text_input.go126
1 files changed, 106 insertions, 20 deletions
diff --git a/tui/text_input.go b/tui/text_input.go
index 3bceb24..ba99f48 100644
--- a/tui/text_input.go
+++ b/tui/text_input.go
@@ -4,16 +4,15 @@ import (
"github.com/rivo/uniseg"
"zgo.at/termfo/keys"
"strings"
+ "unicode"
)
type TextInput struct {
id string
- linesBefore []string
beforeCursor string
selection string
selectionBefore bool
afterCursor string
- linesAfter []string
}
func (t *TextInput) Text() string {
@@ -34,43 +33,119 @@ func toGraphemes(s string) []string {
return result
}
-func splitRight(s string) (string, string) {
+func splitRight(s string, word bool) (string, string) {
if s == "" {
return "", ""
}
gs := toGraphemes(s)
- return strings.Join(gs[:len(gs) - 1], ""), gs[len(gs) - 1]
+ if !word {
+ return strings.Join(gs[:len(gs) - 1], ""), gs[len(gs) - 1]
+ } else {
+ i := len(gs)
+ for unicode.IsSpace([]rune(gs[i - 1])[0]) {
+ i--
+ if i < 0 {
+ return "", s
+ }
+ }
+ for !unicode.IsSpace([]rune(gs[i - 1])[0]) {
+ i--
+ if i < 1 {
+ break
+ }
+ }
+ return strings.Join(gs[:i], ""), strings.Join(gs[i:], "")
+ }
}
-func splitLeft(s string) (string, string) {
+func splitLeft(s string, word bool) (string, string) {
if s == "" {
return "", ""
}
- cluster, rest, _, _ := uniseg.FirstGraphemeCluster([]byte(s), 0)
- return string(cluster), string(rest)
+ gs := toGraphemes(s)
+ if !word {
+ return gs[0], strings.Join(gs[1:], "")
+ } else {
+ i := 0
+ for unicode.IsSpace([]rune(gs[i])[0]) {
+ i++
+ if i >= len(gs) {
+ return "", s
+ }
+ }
+ for !unicode.IsSpace([]rune(gs[i])[0]) {
+ i++
+ if i >= len(gs) {
+ break
+ }
+ }
+ return strings.Join(gs[:i], ""), strings.Join(gs[i:], "")
+ }
}
func (t *TextInput) Left(selection bool, word bool) {
- t.Deselect()
+ if !selection {
+ t.Deselect()
+ }
+ if selection && t.selection == "" {
+ t.selectionBefore = false
+ }
var right string
- t.beforeCursor, right = splitRight(t.beforeCursor)
- t.afterCursor = right + t.afterCursor
+ if selection && t.selectionBefore {
+ t.selection, right = splitRight(t.selection, word)
+ } else {
+ t.beforeCursor, right = splitRight(t.beforeCursor, word)
+ }
+ if !selection || t.selectionBefore {
+ t.afterCursor = right + t.afterCursor
+ } else {
+ t.selection = right + t.selection
+ }
}
func (t *TextInput) Right(selection bool, word bool) {
- t.Deselect()
+ if !selection {
+ t.Deselect()
+ }
+ if selection && t.selection == "" {
+ t.selectionBefore = true
+ }
var left string
- left, t.afterCursor = splitLeft(t.afterCursor)
- t.beforeCursor += left
+ if selection && !t.selectionBefore {
+ left, t.selection = splitLeft(t.selection, word)
+ } else {
+ left, t.afterCursor = splitLeft(t.afterCursor, word)
+ }
+ if !selection || !t.selectionBefore {
+ t.beforeCursor += left
+ } else {
+ t.selection += left
+ }
}
func (t *TextInput) Start(selection bool) {
- t.afterCursor = t.beforeCursor + t.afterCursor
+ if !selection {
+ t.Deselect()
+ }
+ if selection {
+ t.selection = t.beforeCursor + t.selection
+ t.selectionBefore = false
+ } else {
+ t.afterCursor = t.beforeCursor + t.afterCursor
+ }
t.beforeCursor = ""
}
func (t *TextInput) End(selection bool) {
- t.beforeCursor = t.beforeCursor + t.afterCursor
+ if !selection {
+ t.Deselect()
+ }
+ if selection {
+ t.selection = t.selection + t.afterCursor
+ t.selectionBefore = true
+ } else {
+ t.beforeCursor = t.beforeCursor + t.afterCursor
+ }
t.afterCursor = ""
}
@@ -94,6 +169,7 @@ func (t *TextInput) Write(text string) {
func (t *TextInput) Update(ev Event) (usedKeybind bool) {
if Selected != t.id {
+ t.Deselect()
return
}
if ev.TextInput != 0 {
@@ -102,24 +178,32 @@ func (t *TextInput) Update(ev Event) (usedKeybind bool) {
selection := ev.Key & keys.Shift != 0
word := ev.Key & keys.Ctrl != 0
+ if ev.Key & keys.Alt != 0 {
+ selection = true
+ word = true
+ }
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 {
+ if ev.Key & (keys.Ctrl | keys.Alt) != 0 {
t.Start(selection)
}
case 'e':
- if ev.Key & keys.Ctrl != 0 {
+ if ev.Key & (keys.Ctrl | keys.Alt) != 0 {
t.End(selection)
}
case keys.Backspace:
if t.selection != "" {
t.selection = ""
} else {
- t.beforeCursor, _ = splitRight(t.beforeCursor)
+ t.beforeCursor, _ = splitRight(t.beforeCursor, word)
+ }
+ case 'j':
+ if ev.Key & keys.Ctrl != 0 {
+ t.Write("\n")
}
default:
return false
@@ -129,12 +213,14 @@ func (t *TextInput) Update(ev Event) (usedKeybind bool) {
func (t *TextInput) Show(id string) {
t.id = id
- Push(id, Box {Width: Fill, Height: 4})//TextSize})
+ Push(id, Box {Width: Fill, Height: TextSize})
Text(t.beforeCursor, nil)
if t.selectionBefore {
Text(t.selection, &Style {Bg: Blue, Fg: White, selected: true})
}
- Cursor()
+ if t.selection == "" {
+ Cursor()
+ }
if !t.selectionBefore {
Text(t.selection, &Style {Bg: Blue, Fg: White, selected: true})
}