Add an EchoMode and EchoCharacter property to textinputs (#11)

EchoModes are:

- EchoNormal, displays text as is
- EchoPassword, displays EchoCharacter mask instead of actual characters
- EchoNone, displays nothing

Note that EchoOnEdit, which should displays character as they are entered and is otherwise identical to EchoPassword, is not yet implemented.
This commit is contained in:
Christian Muehlhaeuser 2020-10-22 00:17:41 +02:00 committed by GitHub
parent c06af8962d
commit 83b6a2205f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -11,27 +11,49 @@ import (
"github.com/muesli/termenv" "github.com/muesli/termenv"
) )
const defaultBlinkSpeed = time.Millisecond * 600
const ( const (
defaultBlinkSpeed = time.Millisecond * 600 // EchoNormal displays text as is. This is the default behavior.
EchoNormal EchoMode = iota
// EchoPassword displays the EchoCharacter mask instead of actual
// characters. This is commonly used for password fields.
EchoPassword
// EchoNone displays nothing as characters are entered. This is commonly
// seen for password fields on the command line.
EchoNone
// EchoOnEdit
) )
// EchoMode sets the input behavior of the text input field.
type EchoMode int
var ( var (
// color is a helper for returning colors. // color is a helper for returning colors.
color func(s string) termenv.Color = termenv.ColorProfile().Color color func(s string) termenv.Color = termenv.ColorProfile().Color
) )
// Model is the Tea model for this text input element. // Model is the Bubble Tea model for this text input element.
type Model struct { type Model struct {
Err error Err error
Prompt string
Cursor string Prompt string
BlinkSpeed time.Duration Placeholder string
Placeholder string
Cursor string
BlinkSpeed time.Duration
TextColor string TextColor string
BackgroundColor string BackgroundColor string
PlaceholderColor string PlaceholderColor string
CursorColor string CursorColor string
EchoMode EchoMode
EchoCharacter rune
// CharLimit is the maximum amount of characters this input element will // CharLimit is the maximum amount of characters this input element will
// accept. If 0 or less, there's no limit. // accept. If 0 or less, there's no limit.
CharLimit int CharLimit int
@ -41,21 +63,21 @@ type Model struct {
// viewport. If 0 or less this setting is ignored. // viewport. If 0 or less this setting is ignored.
Width int Width int
// Underlying text value // Underlying text value.
value []rune value []rune
// Focus indicates whether user input focus should be on this input // Focus indicates whether user input focus should be on this input
// component. When false, don't blink and ignore keyboard input. // component. When false, don't blink and ignore keyboard input.
focus bool focus bool
// Cursor blink state // Cursor blink state.
blink bool blink bool
// Cursor position // Cursor position.
pos int pos int
// Used to emulate a viewport when width is set and the content is // Used to emulate a viewport when width is set and the content is
// overflowing // overflowing.
offset int offset int
offsetRight int offsetRight int
} }
@ -79,8 +101,8 @@ func (m Model) Value() string {
return string(m.value) return string(m.value)
} }
// Cursor start moves the cursor to the given position. If the position is out // SetCursor start moves the cursor to the given position. If the position is
// of bounds the cursor will be moved to the start or end accordingly. // out of bounds the cursor will be moved to the start or end accordingly.
func (m *Model) SetCursor(pos int) { func (m *Model) SetCursor(pos int) {
m.pos = clamp(pos, 0, len(m.value)) m.pos = clamp(pos, 0, len(m.value))
m.handleOverflow() m.handleOverflow()
@ -280,19 +302,36 @@ func (m *Model) wordRight() {
} }
} }
func (m Model) echoTransform(v string) string {
switch m.EchoMode {
case EchoPassword:
return strings.Repeat(string(m.EchoCharacter), rw.StringWidth(v))
case EchoNone:
return ""
default:
return v
}
}
// BlinkMsg is sent when the cursor should alternate it's blinking state. // BlinkMsg is sent when the cursor should alternate it's blinking state.
type BlinkMsg struct{} type BlinkMsg struct{}
// NewModel creates a new model with default settings. // NewModel creates a new model with default settings.
func NewModel() Model { func NewModel() Model {
return Model{ return Model{
Prompt: "> ", Prompt: "> ",
BlinkSpeed: defaultBlinkSpeed, Placeholder: "",
Placeholder: "",
BlinkSpeed: defaultBlinkSpeed,
TextColor: "", TextColor: "",
PlaceholderColor: "240", PlaceholderColor: "240",
CursorColor: "", CursorColor: "",
CharLimit: 0,
EchoCharacter: '*',
CharLimit: 0,
value: nil, value: nil,
focus: false, focus: false,
@ -396,12 +435,11 @@ func View(m Model) string {
value := m.value[m.offset:m.offsetRight] value := m.value[m.offset:m.offsetRight]
pos := max(0, m.pos-m.offset) pos := max(0, m.pos-m.offset)
v := m.colorText(m.echoTransform(string(value[:pos])))
v := m.colorText(string(value[:pos]))
if pos < len(value) { if pos < len(value) {
v += cursorView(string(value[pos]), m) // cursor and text under it v += cursorView(m.echoTransform(string(value[pos])), m) // cursor and text under it
v += m.colorText(string(value[pos+1:])) // text after cursor v += m.colorText(m.echoTransform(string(value[pos+1:]))) // text after cursor
} else { } else {
v += cursorView(" ", m) v += cursorView(" ", m)
} }