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"
)
const defaultBlinkSpeed = time.Millisecond * 600
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 (
// color is a helper for returning colors.
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 {
Err error
Prompt string
Placeholder string
Cursor string
BlinkSpeed time.Duration
Placeholder string
TextColor string
BackgroundColor string
PlaceholderColor string
CursorColor string
EchoMode EchoMode
EchoCharacter rune
// CharLimit is the maximum amount of characters this input element will
// accept. If 0 or less, there's no limit.
CharLimit int
@ -41,21 +63,21 @@ type Model struct {
// viewport. If 0 or less this setting is ignored.
Width int
// Underlying text value
// Underlying text value.
value []rune
// Focus indicates whether user input focus should be on this input
// component. When false, don't blink and ignore keyboard input.
focus bool
// Cursor blink state
// Cursor blink state.
blink bool
// Cursor position
// Cursor position.
pos int
// Used to emulate a viewport when width is set and the content is
// overflowing
// overflowing.
offset int
offsetRight int
}
@ -79,8 +101,8 @@ func (m Model) Value() string {
return string(m.value)
}
// Cursor start moves the cursor to the given position. If the position is out
// of bounds the cursor will be moved to the start or end accordingly.
// SetCursor start moves the cursor to the given position. If the position is
// out of bounds the cursor will be moved to the start or end accordingly.
func (m *Model) SetCursor(pos int) {
m.pos = clamp(pos, 0, len(m.value))
m.handleOverflow()
@ -280,6 +302,18 @@ 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.
type BlinkMsg struct{}
@ -287,11 +321,16 @@ type BlinkMsg struct{}
func NewModel() Model {
return Model{
Prompt: "> ",
BlinkSpeed: defaultBlinkSpeed,
Placeholder: "",
BlinkSpeed: defaultBlinkSpeed,
TextColor: "",
PlaceholderColor: "240",
CursorColor: "",
EchoCharacter: '*',
CharLimit: 0,
value: nil,
@ -396,12 +435,11 @@ func View(m Model) string {
value := m.value[m.offset:m.offsetRight]
pos := max(0, m.pos-m.offset)
v := m.colorText(string(value[:pos]))
v := m.colorText(m.echoTransform(string(value[:pos])))
if pos < len(value) {
v += cursorView(string(value[pos]), m) // cursor and text under it
v += m.colorText(string(value[pos+1:])) // text after cursor
v += cursorView(m.echoTransform(string(value[pos])), m) // cursor and text under it
v += m.colorText(m.echoTransform(string(value[pos+1:]))) // text after cursor
} else {
v += cursorView(" ", m)
}