|
|
|
@ -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 |
|
|
|
|