diff --git a/inputfield.go b/inputfield.go index fef7840..9bda7cc 100644 --- a/inputfield.go +++ b/inputfield.go @@ -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 diff --git a/list.go b/list.go index a446c62..3d94fce 100644 --- a/list.go +++ b/list.go @@ -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) diff --git a/util.go b/util.go index c60c49e..ea7f2ee 100644 --- a/util.go +++ b/util.go @@ -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: