Store InputField text data as []byte instead of string

This commit is contained in:
Trevor Slocum 2020-10-06 18:43:34 -07:00
parent d5edb975b6
commit 2763609e05
3 changed files with 72 additions and 62 deletions

View File

@ -1,9 +1,9 @@
package cview
import (
"bytes"
"math"
"regexp"
"strings"
"sync"
"unicode/utf8"
@ -33,13 +33,13 @@ type InputField struct {
*Box
// The text that was entered.
text string
text []byte
// The text to be displayed before the input area.
label string
label []byte
// The text to be displayed in the input area when "text" is empty.
placeholder string
placeholder []byte
// The label color.
labelColor tcell.Color
@ -84,7 +84,7 @@ type InputField struct {
fieldNoteTextColor tcell.Color
// The note to show below the input field.
fieldNote string
fieldNote []byte
// The screen width of the label area. A value of 0 means use the width of
// the label text.
@ -113,7 +113,7 @@ type InputField struct {
autocompleteList *List
// The suggested completion of the current autocomplete ListItem.
autocompleteListSuggestion string
autocompleteListSuggestion []byte
// An optional function which may reject the last character that was entered.
accept func(text string, ch rune) bool
@ -164,7 +164,7 @@ func NewInputField() *InputField {
func (i *InputField) SetText(text string) *InputField {
i.Lock()
i.text = text
i.text = []byte(text)
i.cursorPos = len(text)
if i.changed != nil {
i.Unlock()
@ -181,7 +181,7 @@ func (i *InputField) GetText() string {
i.RLock()
defer i.RUnlock()
return i.text
return string(i.text)
}
// SetLabel sets the text to be displayed before the input area.
@ -189,7 +189,7 @@ func (i *InputField) SetLabel(label string) *InputField {
i.Lock()
defer i.Unlock()
i.label = label
i.label = []byte(label)
return i
}
@ -198,7 +198,7 @@ func (i *InputField) GetLabel() string {
i.RLock()
defer i.RUnlock()
return i.label
return string(i.label)
}
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
@ -216,7 +216,7 @@ func (i *InputField) SetPlaceholder(text string) *InputField {
i.Lock()
defer i.Unlock()
i.placeholder = text
i.placeholder = []byte(text)
return i
}
@ -358,7 +358,7 @@ func (i *InputField) SetFieldNote(note string) *InputField {
i.Lock()
defer i.Unlock()
i.fieldNote = note
i.fieldNote = []byte(note)
return i
}
@ -367,7 +367,7 @@ func (i *InputField) ResetFieldNote() *InputField {
i.Lock()
defer i.Unlock()
i.fieldNote = ""
i.fieldNote = nil
return i
}
@ -393,7 +393,7 @@ func (i *InputField) GetFieldWidth() int {
func (i *InputField) GetFieldHeight() int {
i.RLock()
defer i.RUnlock()
if i.fieldNote == "" {
if len(i.fieldNote) == 0 {
return 1
}
return 2
@ -457,12 +457,12 @@ func (i *InputField) Autocomplete() *InputField {
i.Unlock()
// Do we have any autocomplete entries?
entries := i.autocomplete(i.text)
entries := i.autocomplete(string(i.text))
if len(entries) == 0 {
// No entries, no list.
i.Lock()
i.autocompleteList = nil
i.autocompleteListSuggestion = ""
i.autocompleteListSuggestion = nil
i.Unlock()
return i
}
@ -488,7 +488,7 @@ func (i *InputField) Autocomplete() *InputField {
for index, entry := range entries {
entry.enabled = true
i.autocompleteList.AddItem(entry)
if currentEntry < 0 && entry.GetMainText() == i.text {
if currentEntry < 0 && entry.GetMainText() == string(i.text) {
currentEntry = index
}
}
@ -505,14 +505,14 @@ func (i *InputField) Autocomplete() *InputField {
// autocompleteChanged gets called when another item in the
// autocomplete list has been selected.
func (i *InputField) autocompleteChanged(_ int, item *ListItem) {
mainText := item.GetMainText()
secondaryText := item.GetSecondaryText()
if len(secondaryText) > 0 && len(i.text) < len(secondaryText) {
mainText := item.GetMainBytes()
secondaryText := item.GetSecondaryBytes()
if len(i.text) < len(secondaryText) {
i.autocompleteListSuggestion = secondaryText[len(i.text):]
} else if len(mainText) > len(i.text)+1 {
i.autocompleteListSuggestion = mainText[len(i.text)+1:]
} else if len(i.text) < len(mainText) {
i.autocompleteListSuggestion = mainText[len(i.text):]
} else {
i.autocompleteListSuggestion = ""
i.autocompleteListSuggestion = nil
}
}
@ -616,10 +616,10 @@ func (i *InputField) Draw(screen tcell.Screen) {
if labelWidth > rightLimit-x {
labelWidth = rightLimit - x
}
Print(screen, []byte(i.label), x, y, labelWidth, AlignLeft, labelColor)
Print(screen, i.label, x, y, labelWidth, AlignLeft, labelColor)
x += labelWidth
} else {
_, drawnWidth := Print(screen, []byte(i.label), x, y, rightLimit-x, AlignLeft, labelColor)
_, drawnWidth := Print(screen, i.label, x, y, rightLimit-x, AlignLeft, labelColor)
x += drawnWidth
}
@ -640,26 +640,26 @@ func (i *InputField) Draw(screen tcell.Screen) {
// Text.
var cursorScreenPos int
text := i.text
if text == "" && i.placeholder != "" {
if len(text) == 0 && len(i.placeholder) > 0 {
// Draw placeholder text.
placeholderTextColor := i.placeholderTextColor
if i.GetFocusable().HasFocus() && i.placeholderTextColorFocused != ColorUnset {
placeholderTextColor = i.placeholderTextColorFocused
}
Print(screen, []byte(Escape(i.placeholder)), x, y, fieldWidth, AlignLeft, placeholderTextColor)
Print(screen, EscapeBytes(i.placeholder), x, y, fieldWidth, AlignLeft, placeholderTextColor)
i.offset = 0
} else {
// Draw entered text.
if i.maskCharacter > 0 {
text = strings.Repeat(string(i.maskCharacter), utf8.RuneCountInString(i.text))
text = bytes.Repeat([]byte(string(i.maskCharacter)), utf8.RuneCount(i.text))
}
drawnText := ""
if fieldWidth >= runewidth.StringWidth(text) {
var drawnText []byte
if fieldWidth >= runewidth.StringWidth(string(text)) {
// We have enough space for the full text.
drawnText = Escape(text)
Print(screen, []byte(drawnText), x, y, fieldWidth, AlignLeft, fieldTextColor)
drawnText = EscapeBytes(text)
Print(screen, drawnText, x, y, fieldWidth, AlignLeft, fieldTextColor)
i.offset = 0
iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
iterateString(string(text), func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
if textPos >= i.cursorPos {
return true
}
@ -677,11 +677,11 @@ func (i *InputField) Draw(screen tcell.Screen) {
var shiftLeft int
if i.offset > i.cursorPos {
i.offset = i.cursorPos
} else if subWidth := runewidth.StringWidth(text[i.offset:i.cursorPos]); subWidth > fieldWidth-1 {
} else if subWidth := runewidth.StringWidth(string(text[i.offset:i.cursorPos])); subWidth > fieldWidth-1 {
shiftLeft = subWidth - fieldWidth + 1
}
currentOffset := i.offset
iterateString(text, func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
iterateString(string(text), func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
if textPos >= currentOffset {
if shiftLeft > 0 {
i.offset = textPos + textWidth
@ -695,18 +695,18 @@ func (i *InputField) Draw(screen tcell.Screen) {
}
return false
})
drawnText = Escape(text[i.offset:])
Print(screen, []byte(drawnText), x, y, fieldWidth, AlignLeft, fieldTextColor)
drawnText = EscapeBytes(text[i.offset:])
Print(screen, drawnText, x, y, fieldWidth, AlignLeft, fieldTextColor)
}
// Draw suggestion
if i.maskCharacter == 0 && i.autocompleteListSuggestion != "" {
Print(screen, []byte(i.autocompleteListSuggestion), x+runewidth.StringWidth(drawnText), y, fieldWidth-runewidth.StringWidth(drawnText), AlignLeft, i.autocompleteSuggestionTextColor)
if i.maskCharacter == 0 && len(i.autocompleteListSuggestion) > 0 {
Print(screen, i.autocompleteListSuggestion, x+runewidth.StringWidth(string(drawnText)), y, fieldWidth-runewidth.StringWidth(string(drawnText)), AlignLeft, i.autocompleteSuggestionTextColor)
}
}
// Draw field note
if i.fieldNote != "" {
Print(screen, []byte(i.fieldNote), x, y+1, fieldWidth, AlignLeft, i.fieldNoteTextColor)
if len(i.fieldNote) > 0 {
Print(screen, i.fieldNote, x, y+1, fieldWidth, AlignLeft, i.fieldNoteTextColor)
}
// Draw autocomplete list.
@ -760,10 +760,10 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
newText := i.text
i.Unlock()
if newText != currentText {
if !bytes.Equal(newText, currentText) {
i.Autocomplete()
if i.changed != nil {
i.changed(i.text)
i.changed(string(i.text))
}
}
}()
@ -772,29 +772,29 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
home := func() { i.cursorPos = 0 }
end := func() { i.cursorPos = len(i.text) }
moveLeft := func() {
iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
iterateStringReverse(string(i.text[:i.cursorPos]), func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
i.cursorPos -= textWidth
return true
})
}
moveRight := func() {
iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
iterateString(string(i.text[i.cursorPos:]), func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
i.cursorPos += textWidth
return true
})
}
moveWordLeft := func() {
i.cursorPos = len(regexRightWord.ReplaceAllString(i.text[:i.cursorPos], ""))
i.cursorPos = len(regexRightWord.ReplaceAll(i.text[:i.cursorPos], nil))
}
moveWordRight := func() {
i.cursorPos = len(i.text) - len(regexLeftWord.ReplaceAllString(i.text[i.cursorPos:], ""))
i.cursorPos = len(i.text) - len(regexLeftWord.ReplaceAll(i.text[i.cursorPos:], nil))
}
// Add character function. Returns whether or not the rune character is
// accepted.
add := func(r rune) bool {
newText := i.text[:i.cursorPos] + string(r) + i.text[i.cursorPos:]
if i.accept != nil && !i.accept(newText, r) {
newText := append(append(i.text[:i.cursorPos], []byte(string(r))...), i.text[i.cursorPos:]...)
if i.accept != nil && !i.accept(string(newText), r) {
return false
}
i.text = newText
@ -840,17 +840,17 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
}
}
case tcell.KeyCtrlU: // Delete all.
i.text = ""
i.text = nil
i.cursorPos = 0
case tcell.KeyCtrlK: // Delete until the end of the line.
i.text = i.text[:i.cursorPos]
case tcell.KeyCtrlW: // Delete last word.
newText := regexRightWord.ReplaceAllString(i.text[:i.cursorPos], "") + i.text[i.cursorPos:]
newText := append(regexRightWord.ReplaceAll(i.text[:i.cursorPos], nil), i.text[i.cursorPos:]...)
i.cursorPos -= len(i.text) - len(newText)
i.text = newText
case tcell.KeyBackspace, tcell.KeyBackspace2: // Delete character before the cursor.
iterateStringReverse(i.text[:i.cursorPos], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
i.text = i.text[:textPos] + i.text[textPos+textWidth:]
iterateStringReverse(string(i.text[:i.cursorPos]), func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
i.text = append(i.text[:textPos], i.text[textPos+textWidth:]...)
i.cursorPos -= textWidth
return true
})
@ -858,8 +858,8 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
i.offset = 0
}
case tcell.KeyDelete: // Delete character after the cursor.
iterateString(i.text[i.cursorPos:], func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
i.text = i.text[:i.cursorPos] + i.text[i.cursorPos+textWidth:]
iterateString(string(i.text[i.cursorPos:]), func(main rune, comb []rune, textPos, textWidth, screenPos, screenWidth int) bool {
i.text = append(i.text[:i.cursorPos], i.text[i.cursorPos+textWidth:]...)
return true
})
case tcell.KeyLeft:
@ -889,7 +889,7 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
i.SetText(selectionText)
i.Lock()
i.autocompleteList = nil
i.autocompleteListSuggestion = ""
i.autocompleteListSuggestion = nil
i.Unlock()
} else {
i.Unlock()
@ -899,7 +899,7 @@ func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p
case tcell.KeyEscape:
if i.autocompleteList != nil {
i.autocompleteList = nil
i.autocompleteListSuggestion = ""
i.autocompleteListSuggestion = nil
i.Unlock()
} else {
i.Unlock()
@ -952,7 +952,7 @@ func (i *InputField) MouseHandler() func(action MouseAction, event *tcell.EventM
if action == MouseLeftClick && y == rectY {
// Determine where to place the cursor.
if x >= i.fieldX {
if !iterateString(i.text, func(main rune, comb []rune, textPos int, textWidth int, screenPos int, screenWidth int) bool {
if !iterateString(string(i.text), func(main rune, comb []rune, textPos int, textWidth int, screenPos int, screenWidth int) bool {
if x-i.fieldX < screenPos+screenWidth {
i.cursorPos = textPos
return true

View File

@ -909,7 +909,7 @@ func (l *List) Draw(screen tcell.Screen) {
}
// Main text.
Print(screen, []byte(item.mainText), x, y, width, AlignLeft, tcell.ColorGray.TrueColor())
Print(screen, item.mainText, x, y, width, AlignLeft, tcell.ColorGray.TrueColor())
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.offset, l.hasFocus, l.scrollBarColor)
y++
@ -922,7 +922,7 @@ func (l *List) Draw(screen tcell.Screen) {
}
// Main text.
Print(screen, []byte(item.mainText), x, y, width, AlignLeft, l.mainTextColor)
Print(screen, item.mainText, x, y, width, AlignLeft, l.mainTextColor)
// Background color of selected text.
if index == l.currentItem && (!l.selectedFocusOnly || hasFocus) {
@ -954,7 +954,7 @@ func (l *List) Draw(screen tcell.Screen) {
// Secondary text.
if l.showSecondaryText {
Print(screen, []byte(item.secondaryText), x, y, width, AlignLeft, l.secondaryTextColor)
Print(screen, item.secondaryText, x, y, width, AlignLeft, l.secondaryTextColor)
RenderScrollBar(screen, l.scrollBarVisibility, scrollBarX, y, scrollBarHeight, len(l.items), scrollBarCursor, index-l.offset, l.hasFocus, l.scrollBarColor)

12
util.go
View File

@ -253,7 +253,7 @@ func decomposeText(text []byte, findColors, findRegions bool) (colorIndices [][]
var from int
buf := make([]byte, 0, len(text))
for _, indices := range allIndices {
buf = append(buf, []byte(text[from:indices[0]])...)
buf = append(buf, text[from:indices[0]]...)
from = indices[1]
}
buf = append(buf, text[from:]...)
@ -566,6 +566,16 @@ func WordWrap(text []byte, width int) (lines [][]byte) {
return
}
// EscapeBytes escapes the given text such that color and/or region tags are not
// recognized and substituted by the print functions of this package. For
// example, to include a tag-like string in a box title or in a TextView:
//
// box.SetTitle(cview.Escape("[squarebrackets]"))
// fmt.Fprint(textView, cview.EscapeBytes(`["quoted"]`))
func EscapeBytes(text []byte) []byte {
return nonEscapePattern.ReplaceAll(text, []byte("$1[]"))
}
// Escape escapes the given text such that color and/or region tags are not
// recognized and substituted by the print functions of this package. For
// example, to include a tag-like string in a box title or in a TextView: