2017-12-15 14:29:21 +00:00
|
|
|
package tview
|
|
|
|
|
|
|
|
import (
|
2023-10-24 21:15:18 +00:00
|
|
|
"strconv"
|
2018-01-11 15:13:01 +00:00
|
|
|
"strings"
|
2019-07-08 08:34:06 +00:00
|
|
|
"sync"
|
2017-12-15 14:29:21 +00:00
|
|
|
|
2020-10-18 12:15:57 +00:00
|
|
|
"github.com/gdamore/tcell/v2"
|
2022-09-11 19:02:40 +00:00
|
|
|
"github.com/rivo/uniseg"
|
2017-12-15 14:29:21 +00:00
|
|
|
)
|
|
|
|
|
2022-12-29 18:07:33 +00:00
|
|
|
const (
|
|
|
|
AutocompletedNavigate = iota // The user navigated the autocomplete list (using the errow keys).
|
|
|
|
AutocompletedTab // The user selected an autocomplete entry using the tab key.
|
|
|
|
AutocompletedEnter // The user selected an autocomplete entry using the enter key.
|
|
|
|
AutocompletedClick // The user selected an autocomplete entry by clicking the mouse button on it.
|
|
|
|
)
|
|
|
|
|
2023-08-26 14:19:31 +00:00
|
|
|
// Predefined InputField acceptance functions.
|
|
|
|
var (
|
|
|
|
// InputFieldInteger accepts integers.
|
2023-10-24 21:15:18 +00:00
|
|
|
InputFieldInteger = func(text string, ch rune) bool {
|
|
|
|
if text == "-" {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
_, err := strconv.Atoi(text)
|
|
|
|
return err == nil
|
|
|
|
}
|
2023-08-26 14:19:31 +00:00
|
|
|
|
|
|
|
// InputFieldFloat accepts floating-point numbers.
|
2023-10-24 21:15:18 +00:00
|
|
|
InputFieldFloat = func(text string, ch rune) bool {
|
|
|
|
if text == "-" || text == "." || text == "-." {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
_, err := strconv.ParseFloat(text, 64)
|
|
|
|
return err == nil
|
|
|
|
}
|
2023-08-26 14:19:31 +00:00
|
|
|
|
|
|
|
// InputFieldMaxLength returns an input field accept handler which accepts
|
|
|
|
// input strings up to a given length. Use it like this:
|
|
|
|
//
|
|
|
|
// inputField.SetAcceptanceFunc(InputFieldMaxLength(10)) // Accept up to 10 characters.
|
2023-10-24 21:15:18 +00:00
|
|
|
InputFieldMaxLength = func(maxLength int) func(text string, ch rune) bool {
|
|
|
|
return func(text string, ch rune) bool {
|
|
|
|
return len([]rune(text)) <= maxLength
|
|
|
|
}
|
|
|
|
}
|
2023-08-26 14:19:31 +00:00
|
|
|
)
|
|
|
|
|
2023-10-24 21:15:18 +00:00
|
|
|
// InputField is a one-line box into which the user can enter text. Use
|
|
|
|
// [InputField.SetAcceptanceFunc] to accept or reject input,
|
|
|
|
// [InputField.SetChangedFunc] to listen for changes, and
|
2022-12-29 18:07:33 +00:00
|
|
|
// [InputField.SetMaskCharacter] to hide input from onlookers (e.g. for password
|
|
|
|
// input).
|
|
|
|
//
|
|
|
|
// The input field also has an optional autocomplete feature. It is initialized
|
|
|
|
// by the [InputField.SetAutocompleteFunc] function. For more control over the
|
|
|
|
// autocomplete drop-down's behavior, you can also set the
|
|
|
|
// [InputField.SetAutocompletedFunc].
|
2018-01-07 15:39:06 +00:00
|
|
|
//
|
2023-10-24 21:15:18 +00:00
|
|
|
// Navigation and editing is the same as for a [TextArea], with the following
|
|
|
|
// exceptions:
|
|
|
|
//
|
|
|
|
// - Tab, BackTab, Enter, Escape: Finish editing.
|
|
|
|
//
|
|
|
|
// If autocomplete functionality is configured:
|
2018-10-18 06:34:08 +00:00
|
|
|
//
|
2022-12-29 18:07:33 +00:00
|
|
|
// - Down arrow: Open the autocomplete drop-down.
|
|
|
|
// - Tab, Enter: Select the current autocomplete entry.
|
2018-01-11 15:13:01 +00:00
|
|
|
//
|
2018-01-07 15:39:06 +00:00
|
|
|
// See https://github.com/rivo/tview/wiki/InputField for an example.
|
2017-12-15 14:29:21 +00:00
|
|
|
type InputField struct {
|
2017-12-16 21:48:26 +00:00
|
|
|
*Box
|
2017-12-15 14:29:21 +00:00
|
|
|
|
2023-10-24 21:15:18 +00:00
|
|
|
// The text area providing the core functionality of the input field.
|
|
|
|
textArea *TextArea
|
2018-04-09 19:07:03 +00:00
|
|
|
|
2018-01-17 20:17:59 +00:00
|
|
|
// The screen width of the input area. A value of 0 means extend as much as
|
2017-12-15 14:29:21 +00:00
|
|
|
// possible.
|
2018-01-17 20:17:59 +00:00
|
|
|
fieldWidth int
|
2017-12-15 14:29:21 +00:00
|
|
|
|
2019-07-08 08:34:06 +00:00
|
|
|
// An optional autocomplete function which receives the current text of the
|
|
|
|
// input field and returns a slice of strings to be displayed in a drop-down
|
|
|
|
// selection.
|
|
|
|
autocomplete func(text string) []string
|
|
|
|
|
|
|
|
// The List object which shows the selectable autocomplete entries. If not
|
|
|
|
// nil, the list's main texts represent the current autocomplete entries.
|
|
|
|
autocompleteList *List
|
|
|
|
autocompleteListMutex sync.Mutex
|
|
|
|
|
2022-02-15 17:00:49 +00:00
|
|
|
// The styles of the autocomplete entries.
|
|
|
|
autocompleteStyles struct {
|
|
|
|
main tcell.Style
|
|
|
|
selected tcell.Style
|
|
|
|
background tcell.Color
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:07:33 +00:00
|
|
|
// An optional function which is called when the user selects an
|
|
|
|
// autocomplete entry. The text and index of the selected entry (within the
|
|
|
|
// list) is provided, as well as the user action causing the selection (one
|
|
|
|
// of the "Autocompleted" values). The function should return true if the
|
|
|
|
// autocomplete list should be closed. If nil, the input field will be
|
|
|
|
// updated automatically when the user navigates the autocomplete list.
|
|
|
|
autocompleted func(text string, index int, source int) bool
|
|
|
|
|
2017-12-15 14:29:21 +00:00
|
|
|
// An optional function which may reject the last character that was entered.
|
|
|
|
accept func(text string, ch rune) bool
|
|
|
|
|
2018-01-10 09:40:51 +00:00
|
|
|
// An optional function which is called when the input has changed.
|
|
|
|
changed func(text string)
|
|
|
|
|
2017-12-15 14:29:21 +00:00
|
|
|
// An optional function which is called when the user indicated that they
|
2017-12-15 22:03:01 +00:00
|
|
|
// are done entering text. The key which was pressed is provided (tab,
|
|
|
|
// shift-tab, enter, or escape).
|
2017-12-15 14:29:21 +00:00
|
|
|
done func(tcell.Key)
|
2018-04-19 19:34:03 +00:00
|
|
|
|
|
|
|
// A callback function set by the Form class and called when the user leaves
|
|
|
|
// this form item.
|
|
|
|
finished func(tcell.Key)
|
2017-12-15 14:29:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewInputField returns a new input field.
|
|
|
|
func NewInputField() *InputField {
|
2022-02-15 17:00:49 +00:00
|
|
|
i := &InputField{
|
2023-10-24 21:15:18 +00:00
|
|
|
Box: NewBox(),
|
|
|
|
textArea: NewTextArea().SetWrap(false),
|
2017-12-15 14:29:21 +00:00
|
|
|
}
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetChangedFunc(func() {
|
|
|
|
if i.changed != nil {
|
|
|
|
i.changed(i.textArea.GetText())
|
|
|
|
}
|
|
|
|
})
|
|
|
|
i.textArea.textStyle = tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.PrimaryTextColor)
|
|
|
|
i.textArea.placeholderStyle = tcell.StyleDefault.Background(Styles.ContrastBackgroundColor).Foreground(Styles.ContrastSecondaryTextColor)
|
2022-02-15 17:00:49 +00:00
|
|
|
i.autocompleteStyles.main = tcell.StyleDefault.Foreground(Styles.PrimitiveBackgroundColor)
|
|
|
|
i.autocompleteStyles.selected = tcell.StyleDefault.Background(Styles.PrimaryTextColor).Foreground(Styles.PrimitiveBackgroundColor)
|
|
|
|
i.autocompleteStyles.background = Styles.MoreContrastBackgroundColor
|
|
|
|
return i
|
2017-12-15 14:29:21 +00:00
|
|
|
}
|
|
|
|
|
2023-10-24 21:15:18 +00:00
|
|
|
// SetText sets the current text of the input field. This can be undone by the
|
|
|
|
// user.
|
2017-12-15 14:29:21 +00:00
|
|
|
func (i *InputField) SetText(text string) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.Replace(0, i.textArea.GetTextLength(), text)
|
2018-01-10 09:40:51 +00:00
|
|
|
if i.changed != nil {
|
|
|
|
i.changed(text)
|
|
|
|
}
|
2017-12-15 14:29:21 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetText returns the current text of the input field.
|
|
|
|
func (i *InputField) GetText() string {
|
2023-10-24 21:15:18 +00:00
|
|
|
return i.textArea.GetText()
|
2017-12-15 14:29:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// SetLabel sets the text to be displayed before the input area.
|
|
|
|
func (i *InputField) SetLabel(label string) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetLabel(label)
|
2017-12-15 14:29:21 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetLabel returns the text to be displayed before the input area.
|
|
|
|
func (i *InputField) GetLabel() string {
|
2023-10-24 21:15:18 +00:00
|
|
|
return i.textArea.GetLabel()
|
2017-12-15 14:29:21 +00:00
|
|
|
}
|
|
|
|
|
2018-04-09 19:07:03 +00:00
|
|
|
// SetLabelWidth sets the screen width of the label. A value of 0 will cause the
|
|
|
|
// primitive to use the width of the label string.
|
|
|
|
func (i *InputField) SetLabelWidth(width int) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetLabelWidth(width)
|
2018-04-09 19:07:03 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2018-03-15 16:14:14 +00:00
|
|
|
// SetPlaceholder sets the text to be displayed when the input text is empty.
|
|
|
|
func (i *InputField) SetPlaceholder(text string) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetPlaceholder(text)
|
2018-03-15 16:14:14 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2021-11-09 14:42:28 +00:00
|
|
|
// SetLabelColor sets the text color of the label.
|
2017-12-15 14:29:21 +00:00
|
|
|
func (i *InputField) SetLabelColor(color tcell.Color) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetLabelStyle(i.textArea.GetLabelStyle().Foreground(color))
|
2017-12-15 14:29:21 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2021-11-09 14:42:28 +00:00
|
|
|
// SetLabelStyle sets the style of the label.
|
|
|
|
func (i *InputField) SetLabelStyle(style tcell.Style) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetLabelStyle(style)
|
2021-11-09 14:42:28 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetLabelStyle returns the style of the label.
|
|
|
|
func (i *InputField) GetLabelStyle() tcell.Style {
|
2023-10-24 21:15:18 +00:00
|
|
|
return i.textArea.GetLabelStyle()
|
2021-11-09 14:42:28 +00:00
|
|
|
}
|
|
|
|
|
2017-12-15 14:29:21 +00:00
|
|
|
// SetFieldBackgroundColor sets the background color of the input area.
|
|
|
|
func (i *InputField) SetFieldBackgroundColor(color tcell.Color) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetTextStyle(i.textArea.GetTextStyle().Background(color))
|
2017-12-15 14:29:21 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetFieldTextColor sets the text color of the input area.
|
|
|
|
func (i *InputField) SetFieldTextColor(color tcell.Color) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetTextStyle(i.textArea.GetTextStyle().Foreground(color))
|
2017-12-15 14:29:21 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2021-11-09 14:42:28 +00:00
|
|
|
// SetFieldStyle sets the style of the input area (when no placeholder is
|
|
|
|
// shown).
|
|
|
|
func (i *InputField) SetFieldStyle(style tcell.Style) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetTextStyle(style)
|
2021-11-09 14:42:28 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetFieldStyle returns the style of the input area (when no placeholder is
|
|
|
|
// shown).
|
|
|
|
func (i *InputField) GetFieldStyle() tcell.Style {
|
2023-10-24 21:15:18 +00:00
|
|
|
return i.textArea.GetTextStyle()
|
2021-11-09 14:42:28 +00:00
|
|
|
}
|
|
|
|
|
2018-04-09 19:07:03 +00:00
|
|
|
// SetPlaceholderTextColor sets the text color of placeholder text.
|
|
|
|
func (i *InputField) SetPlaceholderTextColor(color tcell.Color) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetPlaceholderStyle(i.textArea.GetPlaceholderStyle().Foreground(color))
|
2018-03-15 16:14:14 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2021-11-09 14:42:28 +00:00
|
|
|
// SetPlaceholderStyle sets the style of the input area (when a placeholder is
|
|
|
|
// shown).
|
|
|
|
func (i *InputField) SetPlaceholderStyle(style tcell.Style) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetPlaceholderStyle(style)
|
2021-11-09 14:42:28 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2021-11-09 16:26:30 +00:00
|
|
|
// GetPlaceholderStyle returns the style of the input area (when a placeholder
|
2021-11-09 14:42:28 +00:00
|
|
|
// is shown).
|
|
|
|
func (i *InputField) GetPlaceholderStyle() tcell.Style {
|
2023-10-24 21:15:18 +00:00
|
|
|
return i.textArea.GetPlaceholderStyle()
|
2021-11-09 14:42:28 +00:00
|
|
|
}
|
|
|
|
|
2022-02-15 17:00:49 +00:00
|
|
|
// SetAutocompleteStyles sets the colors and style of the autocomplete entries.
|
|
|
|
// For details, see List.SetMainTextStyle(), List.SetSelectedStyle(), and
|
|
|
|
// Box.SetBackgroundColor().
|
|
|
|
func (i *InputField) SetAutocompleteStyles(background tcell.Color, main, selected tcell.Style) *InputField {
|
|
|
|
i.autocompleteStyles.background = background
|
|
|
|
i.autocompleteStyles.main = main
|
|
|
|
i.autocompleteStyles.selected = selected
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2017-12-18 19:04:52 +00:00
|
|
|
// SetFormAttributes sets attributes shared by all form items.
|
2018-04-09 19:07:03 +00:00
|
|
|
func (i *InputField) SetFormAttributes(labelWidth int, labelColor, bgColor, fieldTextColor, fieldBgColor tcell.Color) FormItem {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetFormAttributes(labelWidth, labelColor, bgColor, fieldTextColor, fieldBgColor)
|
2017-12-18 19:04:52 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2018-01-17 20:17:59 +00:00
|
|
|
// SetFieldWidth sets the screen width of the input area. A value of 0 means
|
|
|
|
// extend as much as possible.
|
|
|
|
func (i *InputField) SetFieldWidth(width int) *InputField {
|
|
|
|
i.fieldWidth = width
|
2017-12-15 14:29:21 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2018-01-17 20:17:59 +00:00
|
|
|
// GetFieldWidth returns this primitive's field width.
|
|
|
|
func (i *InputField) GetFieldWidth() int {
|
|
|
|
return i.fieldWidth
|
2018-01-16 19:45:54 +00:00
|
|
|
}
|
|
|
|
|
2022-11-15 14:33:49 +00:00
|
|
|
// GetFieldHeight returns this primitive's field height.
|
|
|
|
func (i *InputField) GetFieldHeight() int {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2023-03-20 06:26:01 +00:00
|
|
|
// SetDisabled sets whether or not the item is disabled / read-only.
|
|
|
|
func (i *InputField) SetDisabled(disabled bool) FormItem {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetDisabled(disabled)
|
2023-03-20 06:26:01 +00:00
|
|
|
if i.finished != nil {
|
|
|
|
i.finished(-1)
|
|
|
|
}
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2018-01-11 15:13:01 +00:00
|
|
|
// SetMaskCharacter sets a character that masks user input on a screen. A value
|
|
|
|
// of 0 disables masking.
|
|
|
|
func (i *InputField) SetMaskCharacter(mask rune) *InputField {
|
2023-10-24 21:15:18 +00:00
|
|
|
if mask == 0 {
|
|
|
|
i.textArea.setTransform(nil)
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
maskStr := string(mask)
|
|
|
|
maskWidth := uniseg.StringWidth(maskStr)
|
|
|
|
i.textArea.setTransform(func(cluster, rest string, boundaries int) (newCluster string, newBoundaries int) {
|
|
|
|
return maskStr, maskWidth << uniseg.ShiftWidth
|
|
|
|
})
|
2018-01-11 15:13:01 +00:00
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2019-07-08 08:34:06 +00:00
|
|
|
// SetAutocompleteFunc sets an autocomplete callback function which may return
|
|
|
|
// strings to be selected from a drop-down based on the current text of the
|
|
|
|
// input field. The drop-down appears only if len(entries) > 0. The callback is
|
|
|
|
// invoked in this function and whenever the current text changes or when
|
2023-10-24 21:15:18 +00:00
|
|
|
// [InputField.Autocomplete] is called. Entries are cleared when the user
|
|
|
|
// selects an entry or presses Escape.
|
2019-07-08 08:34:06 +00:00
|
|
|
func (i *InputField) SetAutocompleteFunc(callback func(currentText string) (entries []string)) *InputField {
|
|
|
|
i.autocomplete = callback
|
|
|
|
i.Autocomplete()
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2022-12-29 18:07:33 +00:00
|
|
|
// SetAutocompletedFunc sets a callback function which is invoked when the user
|
|
|
|
// selects an entry from the autocomplete drop-down list. The function is passed
|
2023-08-22 20:16:59 +00:00
|
|
|
// the text of the selected entry (stripped of any style tags), the index of the
|
2023-10-24 21:15:18 +00:00
|
|
|
// entry, and the user action that caused the selection, for example
|
2022-12-29 18:07:33 +00:00
|
|
|
// [AutocompletedNavigate]. It returns true if the autocomplete drop-down should
|
|
|
|
// be closed after the callback returns or false if it should remain open, in
|
|
|
|
// which case [InputField.Autocomplete] is called to update the drop-down's
|
|
|
|
// contents.
|
|
|
|
//
|
|
|
|
// If no such callback is set (or nil is provided), the input field will be
|
|
|
|
// updated with the selection any time the user navigates the autocomplete
|
|
|
|
// drop-down list. So this function essentially gives you more control over the
|
|
|
|
// autocomplete functionality.
|
|
|
|
func (i *InputField) SetAutocompletedFunc(autocompleted func(text string, index int, source int) bool) *InputField {
|
|
|
|
i.autocompleted = autocompleted
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2023-10-24 21:15:18 +00:00
|
|
|
// Autocomplete invokes the autocomplete callback (if there is one, see
|
|
|
|
// [InputField.SetAutocompleteFunc]). If the length of the returned autocomplete
|
|
|
|
// entries slice is greater than 0, the input field will present the user with a
|
|
|
|
// corresponding drop-down list the next time the input field is drawn.
|
2019-07-08 08:34:06 +00:00
|
|
|
//
|
|
|
|
// It is safe to call this function from any goroutine. Note that the input
|
|
|
|
// field is not redrawn automatically unless called from the main goroutine
|
|
|
|
// (e.g. in response to events).
|
|
|
|
func (i *InputField) Autocomplete() *InputField {
|
|
|
|
i.autocompleteListMutex.Lock()
|
|
|
|
defer i.autocompleteListMutex.Unlock()
|
|
|
|
if i.autocomplete == nil {
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do we have any autocomplete entries?
|
2023-10-24 21:15:18 +00:00
|
|
|
text := i.textArea.GetText()
|
|
|
|
entries := i.autocomplete(text)
|
2019-07-08 08:34:06 +00:00
|
|
|
if len(entries) == 0 {
|
|
|
|
// No entries, no list.
|
|
|
|
i.autocompleteList = nil
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make a list if we have none.
|
|
|
|
if i.autocompleteList == nil {
|
|
|
|
i.autocompleteList = NewList()
|
|
|
|
i.autocompleteList.ShowSecondaryText(false).
|
2022-02-15 17:00:49 +00:00
|
|
|
SetMainTextStyle(i.autocompleteStyles.main).
|
|
|
|
SetSelectedStyle(i.autocompleteStyles.selected).
|
2019-07-08 08:34:06 +00:00
|
|
|
SetHighlightFullLine(true).
|
2022-02-15 17:00:49 +00:00
|
|
|
SetBackgroundColor(i.autocompleteStyles.background)
|
2019-07-08 08:34:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Fill it with the entries.
|
|
|
|
currentEntry := -1
|
2021-01-11 18:45:19 +00:00
|
|
|
suffixLength := 9999 // I'm just waiting for the day somebody opens an issue with this number being too small.
|
2019-07-08 08:34:06 +00:00
|
|
|
i.autocompleteList.Clear()
|
|
|
|
for index, entry := range entries {
|
|
|
|
i.autocompleteList.AddItem(entry, "", 0, nil)
|
2023-10-24 21:15:18 +00:00
|
|
|
if strings.HasPrefix(entry, text) && len(entry)-len(text) < suffixLength {
|
2019-07-08 08:34:06 +00:00
|
|
|
currentEntry = index
|
2023-10-24 21:15:18 +00:00
|
|
|
suffixLength = len(text) - len(entry)
|
2019-07-08 08:34:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the selection if we have one.
|
|
|
|
if currentEntry >= 0 {
|
|
|
|
i.autocompleteList.SetCurrentItem(currentEntry)
|
|
|
|
}
|
|
|
|
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2017-12-15 14:29:21 +00:00
|
|
|
// SetAcceptanceFunc sets a handler which may reject the last character that was
|
2023-10-24 21:15:18 +00:00
|
|
|
// entered, by returning false. The handler receives the text as it would be
|
|
|
|
// after the change and the last character entered. If the handler is nil, all
|
|
|
|
// input is accepted. The function is only called when a single rune is inserted
|
|
|
|
// at the current cursor position.
|
2017-12-15 14:29:21 +00:00
|
|
|
//
|
2018-10-18 06:34:08 +00:00
|
|
|
// This package defines a number of variables prefixed with InputField which may
|
2023-10-24 21:15:18 +00:00
|
|
|
// be used for common input (e.g. numbers, maximum text length). See for example
|
|
|
|
// [InputFieldInteger].
|
2017-12-15 14:29:21 +00:00
|
|
|
func (i *InputField) SetAcceptanceFunc(handler func(textToCheck string, lastChar rune) bool) *InputField {
|
|
|
|
i.accept = handler
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2018-01-10 09:40:51 +00:00
|
|
|
// SetChangedFunc sets a handler which is called whenever the text of the input
|
|
|
|
// field has changed. It receives the current text (after the change).
|
|
|
|
func (i *InputField) SetChangedFunc(handler func(text string)) *InputField {
|
|
|
|
i.changed = handler
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2017-12-15 14:29:21 +00:00
|
|
|
// SetDoneFunc sets a handler which is called when the user is done entering
|
|
|
|
// text. The callback function is provided with the key that was pressed, which
|
|
|
|
// is one of the following:
|
|
|
|
//
|
|
|
|
// - KeyEnter: Done entering text.
|
|
|
|
// - KeyEscape: Abort text input.
|
|
|
|
// - KeyTab: Move to the next field.
|
2017-12-15 22:03:01 +00:00
|
|
|
// - KeyBacktab: Move to the previous field.
|
2017-12-15 14:29:21 +00:00
|
|
|
func (i *InputField) SetDoneFunc(handler func(key tcell.Key)) *InputField {
|
|
|
|
i.done = handler
|
|
|
|
return i
|
|
|
|
}
|
|
|
|
|
2018-04-19 19:34:03 +00:00
|
|
|
// SetFinishedFunc sets a callback invoked when the user leaves this form item.
|
2017-12-18 19:04:52 +00:00
|
|
|
func (i *InputField) SetFinishedFunc(handler func(key tcell.Key)) FormItem {
|
2018-04-19 19:34:03 +00:00
|
|
|
i.finished = handler
|
|
|
|
return i
|
2017-12-18 19:04:52 +00:00
|
|
|
}
|
|
|
|
|
2023-03-20 06:26:01 +00:00
|
|
|
// Focus is called when this primitive receives focus.
|
|
|
|
func (i *InputField) Focus(delegate func(p Primitive)) {
|
|
|
|
// If we're part of a form and this item is disabled, there's nothing the
|
|
|
|
// user can do here so we're finished.
|
2023-10-24 21:15:18 +00:00
|
|
|
if i.finished != nil && i.textArea.GetDisabled() {
|
2023-03-20 06:26:01 +00:00
|
|
|
i.finished(-1)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
i.Box.Focus(delegate)
|
|
|
|
}
|
|
|
|
|
2023-10-24 21:15:18 +00:00
|
|
|
// HasFocus returns whether or not this primitive has focus.
|
|
|
|
func (i *InputField) HasFocus() bool {
|
|
|
|
return i.textArea.HasFocus() || i.Box.HasFocus()
|
|
|
|
}
|
|
|
|
|
2023-01-04 12:54:15 +00:00
|
|
|
// Blur is called when this primitive loses focus.
|
|
|
|
func (i *InputField) Blur() {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.Blur()
|
2023-01-04 12:54:15 +00:00
|
|
|
i.Box.Blur()
|
|
|
|
i.autocompleteList = nil // Hide the autocomplete drop-down.
|
|
|
|
}
|
|
|
|
|
2017-12-15 14:29:21 +00:00
|
|
|
// Draw draws this primitive onto the screen.
|
|
|
|
func (i *InputField) Draw(screen tcell.Screen) {
|
2020-11-17 18:33:25 +00:00
|
|
|
i.Box.DrawForSubclass(screen, i)
|
2017-12-15 14:29:21 +00:00
|
|
|
|
|
|
|
// Prepare
|
2017-12-21 17:08:53 +00:00
|
|
|
x, y, width, height := i.GetInnerRect()
|
2023-10-24 21:15:18 +00:00
|
|
|
if height < 1 || width < 1 {
|
2017-12-15 14:29:21 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-10-24 21:15:18 +00:00
|
|
|
// Resize text area.
|
|
|
|
labelWidth := i.textArea.GetLabelWidth()
|
|
|
|
if labelWidth == 0 {
|
|
|
|
labelWidth = TaggedStringWidth(i.textArea.GetLabel())
|
2018-04-09 19:07:03 +00:00
|
|
|
}
|
2018-01-17 20:17:59 +00:00
|
|
|
fieldWidth := i.fieldWidth
|
2018-01-17 16:13:36 +00:00
|
|
|
if fieldWidth == 0 {
|
2023-10-24 21:15:18 +00:00
|
|
|
fieldWidth = width - labelWidth
|
2017-12-15 14:29:21 +00:00
|
|
|
}
|
2023-10-24 21:15:18 +00:00
|
|
|
i.textArea.SetRect(x, y, labelWidth+fieldWidth, 1)
|
|
|
|
i.textArea.setMinCursorPadding(fieldWidth-1, 1)
|
2017-12-15 22:03:01 +00:00
|
|
|
|
2023-10-24 21:15:18 +00:00
|
|
|
// Draw text area.
|
|
|
|
i.textArea.hasFocus = i.HasFocus() // Force cursor positioning.
|
|
|
|
i.textArea.Draw(screen)
|
2017-12-15 14:29:21 +00:00
|
|
|
|
2019-07-08 08:34:06 +00:00
|
|
|
// Draw autocomplete list.
|
|
|
|
i.autocompleteListMutex.Lock()
|
|
|
|
defer i.autocompleteListMutex.Unlock()
|
|
|
|
if i.autocompleteList != nil {
|
|
|
|
// How much space do we need?
|
|
|
|
lheight := i.autocompleteList.GetItemCount()
|
|
|
|
lwidth := 0
|
|
|
|
for index := 0; index < lheight; index++ {
|
|
|
|
entry, _ := i.autocompleteList.GetItemText(index)
|
|
|
|
width := TaggedStringWidth(entry)
|
|
|
|
if width > lwidth {
|
|
|
|
lwidth = width
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We prefer to drop down but if there is no space, maybe drop up?
|
2023-10-24 21:15:18 +00:00
|
|
|
lx := x + labelWidth
|
2019-07-08 08:34:06 +00:00
|
|
|
ly := y + 1
|
|
|
|
_, sheight := screen.Size()
|
|
|
|
if ly+lheight >= sheight && ly-2 > lheight-ly {
|
|
|
|
ly = y - lheight
|
|
|
|
if ly < 0 {
|
|
|
|
ly = 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if ly+lheight >= sheight {
|
|
|
|
lheight = sheight - ly
|
|
|
|
}
|
|
|
|
i.autocompleteList.SetRect(lx, ly, lwidth, lheight)
|
|
|
|
i.autocompleteList.Draw(screen)
|
|
|
|
}
|
2017-12-15 14:29:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// InputHandler returns the handler for this primitive.
|
2017-12-18 19:04:52 +00:00
|
|
|
func (i *InputField) InputHandler() func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
2018-03-19 20:25:30 +00:00
|
|
|
return i.WrapInputHandler(func(event *tcell.EventKey, setFocus func(p Primitive)) {
|
2023-10-24 21:15:18 +00:00
|
|
|
if i.textArea.GetDisabled() {
|
2023-03-20 06:26:01 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-01-10 09:40:51 +00:00
|
|
|
// Trigger changed events.
|
2023-10-24 21:15:18 +00:00
|
|
|
var skipAutocomplete bool
|
|
|
|
currentText := i.textArea.GetText()
|
2018-01-10 09:40:51 +00:00
|
|
|
defer func() {
|
2023-10-24 21:15:18 +00:00
|
|
|
newText := i.textArea.GetText()
|
|
|
|
if newText != currentText {
|
|
|
|
if !skipAutocomplete {
|
|
|
|
i.Autocomplete()
|
|
|
|
}
|
2019-07-08 08:34:06 +00:00
|
|
|
if i.changed != nil {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.changed(newText)
|
2019-07-08 08:34:06 +00:00
|
|
|
}
|
2018-01-10 09:40:51 +00:00
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2022-12-29 18:07:33 +00:00
|
|
|
// If we have an autocomplete list, there are certain keys we will
|
|
|
|
// forward to it.
|
2019-07-08 08:34:06 +00:00
|
|
|
i.autocompleteListMutex.Lock()
|
|
|
|
defer i.autocompleteListMutex.Unlock()
|
2022-12-29 18:07:33 +00:00
|
|
|
if i.autocompleteList != nil {
|
|
|
|
i.autocompleteList.SetChangedFunc(nil)
|
2023-10-24 21:15:18 +00:00
|
|
|
i.autocompleteList.SetSelectedFunc(nil)
|
2022-12-29 18:07:33 +00:00
|
|
|
switch key := event.Key(); key {
|
|
|
|
case tcell.KeyEscape: // Close the list.
|
|
|
|
i.autocompleteList = nil
|
|
|
|
return
|
|
|
|
case tcell.KeyEnter, tcell.KeyTab: // Intentional selection.
|
2023-10-24 21:15:18 +00:00
|
|
|
index := i.autocompleteList.GetCurrentItem()
|
|
|
|
text, _ := i.autocompleteList.GetItemText(index)
|
2022-12-29 18:07:33 +00:00
|
|
|
if i.autocompleted != nil {
|
|
|
|
source := AutocompletedEnter
|
|
|
|
if key == tcell.KeyTab {
|
|
|
|
source = AutocompletedTab
|
|
|
|
}
|
|
|
|
if i.autocompleted(stripTags(text), index, source) {
|
|
|
|
i.autocompleteList = nil
|
|
|
|
currentText = i.GetText()
|
|
|
|
}
|
|
|
|
} else {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.SetText(text)
|
|
|
|
skipAutocomplete = true
|
2022-12-29 18:07:33 +00:00
|
|
|
i.autocompleteList = nil
|
|
|
|
}
|
|
|
|
return
|
|
|
|
case tcell.KeyDown, tcell.KeyUp, tcell.KeyPgDn, tcell.KeyPgUp:
|
|
|
|
i.autocompleteList.SetChangedFunc(func(index int, text, secondaryText string, shortcut rune) {
|
|
|
|
text = stripTags(text)
|
|
|
|
if i.autocompleted != nil {
|
|
|
|
if i.autocompleted(text, index, AutocompletedNavigate) {
|
|
|
|
i.autocompleteList = nil
|
|
|
|
currentText = i.GetText()
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
i.SetText(text)
|
|
|
|
currentText = stripTags(text) // We want to keep the autocomplete list open and unchanged.
|
|
|
|
}
|
|
|
|
})
|
|
|
|
i.autocompleteList.InputHandler()(event, setFocus)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-24 21:15:18 +00:00
|
|
|
// Finish up.
|
|
|
|
finish := func(key tcell.Key) {
|
|
|
|
if i.done != nil {
|
|
|
|
i.done(key)
|
2018-10-18 06:34:08 +00:00
|
|
|
}
|
2023-10-24 21:15:18 +00:00
|
|
|
if i.finished != nil {
|
|
|
|
i.finished(key)
|
2017-12-15 14:29:21 +00:00
|
|
|
}
|
2023-10-24 21:15:18 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Process special key events for the input field.
|
|
|
|
switch key := event.Key(); key {
|
2021-01-11 18:45:19 +00:00
|
|
|
case tcell.KeyDown:
|
2022-12-29 18:07:33 +00:00
|
|
|
i.autocompleteListMutex.Unlock() // We're still holding a lock.
|
|
|
|
i.Autocomplete()
|
|
|
|
i.autocompleteListMutex.Lock()
|
|
|
|
case tcell.KeyEnter, tcell.KeyEscape, tcell.KeyTab, tcell.KeyBacktab:
|
|
|
|
finish(key)
|
2023-10-24 21:15:18 +00:00
|
|
|
case tcell.KeyRune:
|
|
|
|
if event.Modifiers()&tcell.ModAlt == 0 && i.accept != nil {
|
|
|
|
// Check if this rune is accepted.
|
|
|
|
r := event.Rune()
|
|
|
|
if !i.accept(i.textArea.getTextBeforeCursor()+string(r)+i.textArea.getTextAfterCursor(), r) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
fallthrough
|
|
|
|
default:
|
|
|
|
// Forward other key events to the text area.
|
|
|
|
i.textArea.InputHandler()(event, setFocus)
|
2017-12-15 14:29:21 +00:00
|
|
|
}
|
2018-01-14 20:29:34 +00:00
|
|
|
})
|
2017-12-15 14:29:21 +00:00
|
|
|
}
|
2019-11-04 06:30:25 +00:00
|
|
|
|
|
|
|
// MouseHandler returns the mouse handler for this primitive.
|
2020-02-14 02:09:09 +00:00
|
|
|
func (i *InputField) MouseHandler() func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
|
|
|
return i.WrapMouseHandler(func(action MouseAction, event *tcell.EventMouse, setFocus func(p Primitive)) (consumed bool, capture Primitive) {
|
2023-10-24 21:15:18 +00:00
|
|
|
if i.textArea.GetDisabled() {
|
2023-03-20 06:26:01 +00:00
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
|
2023-10-24 21:15:18 +00:00
|
|
|
var skipAutocomplete bool
|
2022-12-29 18:07:33 +00:00
|
|
|
currentText := i.GetText()
|
|
|
|
defer func() {
|
2023-10-24 21:15:18 +00:00
|
|
|
newText := i.GetText()
|
|
|
|
if newText != currentText {
|
|
|
|
if !skipAutocomplete {
|
|
|
|
i.Autocomplete()
|
|
|
|
}
|
2022-12-29 18:07:33 +00:00
|
|
|
if i.changed != nil {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.changed(newText)
|
2022-12-29 18:07:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// If we have an autocomplete list, forward the mouse event to it.
|
|
|
|
i.autocompleteListMutex.Lock()
|
|
|
|
defer i.autocompleteListMutex.Unlock()
|
|
|
|
if i.autocompleteList != nil {
|
2023-10-24 21:15:18 +00:00
|
|
|
i.autocompleteList.SetChangedFunc(nil)
|
|
|
|
i.autocompleteList.SetSelectedFunc(func(index int, text, secondaryText string, shortcut rune) {
|
2022-12-29 18:07:33 +00:00
|
|
|
text = stripTags(text)
|
|
|
|
if i.autocompleted != nil {
|
|
|
|
if i.autocompleted(text, index, AutocompletedClick) {
|
|
|
|
i.autocompleteList = nil
|
|
|
|
currentText = i.GetText()
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
i.SetText(text)
|
2023-10-24 21:15:18 +00:00
|
|
|
skipAutocomplete = true
|
2022-12-29 18:07:33 +00:00
|
|
|
i.autocompleteList = nil
|
|
|
|
})
|
|
|
|
if consumed, _ = i.autocompleteList.MouseHandler()(action, event, setFocus); consumed {
|
|
|
|
setFocus(i)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Is mouse event within the input field?
|
2020-03-27 17:41:44 +00:00
|
|
|
x, y := event.Position()
|
|
|
|
if !i.InRect(x, y) {
|
2020-02-14 02:09:09 +00:00
|
|
|
return false, nil
|
2020-01-24 20:40:34 +00:00
|
|
|
}
|
2020-03-27 17:41:44 +00:00
|
|
|
|
2023-10-24 21:15:18 +00:00
|
|
|
// Forward mouse event to the text area.
|
|
|
|
consumed, capture = i.textArea.MouseHandler()(action, event, setFocus)
|
2020-03-27 17:41:44 +00:00
|
|
|
|
|
|
|
return
|
2019-11-04 06:30:25 +00:00
|
|
|
})
|
|
|
|
}
|