mirror of
https://github.com/Maks1mS/bubbles.git
synced 2025-10-18 08:29:17 +03:00
Compare commits
2 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
d3192a3e70 | ||
|
74cc86fce5 |
1
go.mod
1
go.mod
@@ -4,6 +4,7 @@ go 1.13
|
||||
|
||||
require (
|
||||
github.com/charmbracelet/bubbletea v0.8.0
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/muesli/termenv v0.5.3-0.20200526053627-d728968dcf83
|
||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
|
||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // indirect
|
||||
|
2
go.sum
2
go.sum
@@ -6,6 +6,8 @@ github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tW
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
|
||||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/muesli/termenv v0.5.3-0.20200526053627-d728968dcf83 h1:AfshZBlqAwhCZ27NJ1aPlMcPBihF1squ1GpaollhLQk=
|
||||
github.com/muesli/termenv v0.5.3-0.20200526053627-d728968dcf83/go.mod h1:O1/I6sw+6KcrgAmcs6uiUVr7Lui+DNVbHTzt9Lm/PlI=
|
||||
github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03 h1:pd4YKIqCB0U7O2I4gWHgEUA2mCEOENmco0l/bM957bU=
|
||||
|
@@ -6,6 +6,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
rw "github.com/mattn/go-runewidth"
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
@@ -45,7 +46,7 @@ type Model struct {
|
||||
Width int
|
||||
|
||||
// Underlying text value
|
||||
value string
|
||||
value []rune
|
||||
|
||||
// Focus indicates whether user input focus should be on this input
|
||||
// component. When false, don't blink and ignore keyboard input.
|
||||
@@ -60,14 +61,16 @@ type Model struct {
|
||||
// Used to emulate a viewport when width is set and the content is
|
||||
// overflowing
|
||||
offset int
|
||||
offsetRight int
|
||||
}
|
||||
|
||||
// SetValue sets the value of the text input.
|
||||
func (m *Model) SetValue(s string) {
|
||||
if m.CharLimit > 0 && len(s) > m.CharLimit {
|
||||
m.value = s[:m.CharLimit]
|
||||
runes := []rune(s)
|
||||
if m.CharLimit > 0 && len(runes) > m.CharLimit {
|
||||
m.value = runes[:m.CharLimit]
|
||||
} else {
|
||||
m.value = s
|
||||
m.value = runes
|
||||
}
|
||||
if m.pos > len(m.value) {
|
||||
m.pos = len(m.value)
|
||||
@@ -77,13 +80,13 @@ func (m *Model) SetValue(s string) {
|
||||
|
||||
// Value returns the value of the text input.
|
||||
func (m Model) Value() string {
|
||||
return m.value
|
||||
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.
|
||||
func (m *Model) SetCursor(pos int) {
|
||||
m.pos = max(0, min(len(m.value), pos))
|
||||
m.pos = clamp(pos, 0, len(m.value))
|
||||
m.handleOverflow()
|
||||
}
|
||||
|
||||
@@ -118,8 +121,7 @@ func (m *Model) Blur() {
|
||||
|
||||
// Reset sets the input to its default state with no input.
|
||||
func (m *Model) Reset() {
|
||||
m.value = ""
|
||||
m.offset = 0
|
||||
m.value = nil
|
||||
m.pos = 0
|
||||
m.blink = false
|
||||
}
|
||||
@@ -127,14 +129,46 @@ func (m *Model) Reset() {
|
||||
// If a max width is defined, perform some logic to treat the visible area
|
||||
// as a horizontally scrolling viewport.
|
||||
func (m *Model) handleOverflow() {
|
||||
if m.Width > 0 {
|
||||
overflow := max(0, len(m.value)-m.Width)
|
||||
|
||||
if overflow > 0 && m.pos < m.offset {
|
||||
m.offset = max(0, min(len(m.value), m.pos))
|
||||
} else if overflow > 0 && m.pos >= m.offset+m.Width {
|
||||
m.offset = max(0, m.pos-m.Width)
|
||||
if m.Width <= 0 || rw.StringWidth(string(m.value)) <= m.Width {
|
||||
m.offset = 0
|
||||
m.offsetRight = len(m.value)
|
||||
return
|
||||
}
|
||||
|
||||
if m.pos < m.offset {
|
||||
|
||||
m.offset = m.pos
|
||||
|
||||
w := 0
|
||||
i := 0
|
||||
runes := m.value[m.offset:]
|
||||
|
||||
for i < len(runes) && w <= m.Width {
|
||||
w += rw.RuneWidth(runes[i])
|
||||
if w <= m.Width+1 {
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
m.offsetRight = m.offset + i
|
||||
|
||||
} else if m.pos >= m.offsetRight {
|
||||
|
||||
m.offsetRight = m.pos
|
||||
|
||||
w := 0
|
||||
runes := m.value[:m.offsetRight]
|
||||
i := len(runes) - 1
|
||||
|
||||
for i > 0 && w < m.Width {
|
||||
w += rw.RuneWidth(runes[i])
|
||||
if w <= m.Width {
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
m.offset = m.offsetRight - (len(runes) - 1 - i)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +200,7 @@ func (m *Model) wordLeft() {
|
||||
i := m.pos - 1
|
||||
|
||||
for i >= 0 {
|
||||
if unicode.IsSpace(rune(m.value[i])) {
|
||||
if unicode.IsSpace(m.value[i]) {
|
||||
m.pos--
|
||||
i--
|
||||
} else {
|
||||
@@ -175,7 +209,7 @@ func (m *Model) wordLeft() {
|
||||
}
|
||||
|
||||
for i >= 0 {
|
||||
if !unicode.IsSpace(rune(m.value[i])) {
|
||||
if !unicode.IsSpace(m.value[i]) {
|
||||
m.pos--
|
||||
i--
|
||||
} else {
|
||||
@@ -224,7 +258,7 @@ func NewModel() Model {
|
||||
CursorColor: "",
|
||||
CharLimit: 0,
|
||||
|
||||
value: "",
|
||||
value: nil,
|
||||
focus: false,
|
||||
blink: true,
|
||||
pos: 0,
|
||||
@@ -245,7 +279,7 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
||||
fallthrough
|
||||
case tea.KeyDelete:
|
||||
if len(m.value) > 0 {
|
||||
m.value = m.value[:m.pos-1] + m.value[m.pos:]
|
||||
m.value = append(m.value[:m.pos-1], m.value[m.pos:]...)
|
||||
m.pos--
|
||||
}
|
||||
case tea.KeyLeft:
|
||||
@@ -272,7 +306,7 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
||||
m.CursorStart()
|
||||
case tea.KeyCtrlD: // ^D, delete char under cursor
|
||||
if len(m.value) > 0 && m.pos < len(m.value) {
|
||||
m.value = m.value[:m.pos] + m.value[m.pos+1:]
|
||||
m.value = append(m.value[:m.pos], m.value[m.pos+1:]...)
|
||||
}
|
||||
case tea.KeyCtrlE: // ^E, go to end
|
||||
m.CursorEnd()
|
||||
@@ -298,7 +332,7 @@ func Update(msg tea.Msg, m Model) (Model, tea.Cmd) {
|
||||
|
||||
// Input a regular character
|
||||
if m.CharLimit <= 0 || len(m.value) < m.CharLimit {
|
||||
m.value = m.value[:m.pos] + string(msg.Rune) + m.value[m.pos:]
|
||||
m.value = append(m.value[:m.pos], append([]rune{msg.Rune}, m.value[m.pos:]...)...)
|
||||
m.pos++
|
||||
}
|
||||
}
|
||||
@@ -324,34 +358,28 @@ func View(model tea.Model) string {
|
||||
}
|
||||
|
||||
// Placeholder text
|
||||
if m.value == "" && m.Placeholder != "" {
|
||||
if m.value == nil && m.Placeholder != "" {
|
||||
return placeholderView(m)
|
||||
}
|
||||
|
||||
left := m.offset
|
||||
right := 0
|
||||
if m.Width > 0 {
|
||||
right = min(len(m.value), m.offset+m.Width+1)
|
||||
} else {
|
||||
right = len(m.value)
|
||||
}
|
||||
value := m.value[left:right]
|
||||
value := m.value[m.offset:m.offsetRight]
|
||||
pos := m.pos - m.offset
|
||||
|
||||
v := m.colorText(value[:pos])
|
||||
v := m.colorText(string(value[:pos]))
|
||||
|
||||
if pos < len(value) {
|
||||
v += cursorView(string(value[pos]), m) // cursor and text under it
|
||||
v += m.colorText(value[pos+1:]) // text after cursor
|
||||
v += m.colorText(string(value[pos+1:])) // text after cursor
|
||||
} else {
|
||||
v += cursorView(" ", m)
|
||||
}
|
||||
|
||||
// If a max width and background color were set fill the empty spaces with
|
||||
// the background color.
|
||||
if m.Width > 0 && len(m.BackgroundColor) > 0 && len(value) <= m.Width {
|
||||
padding := m.Width - len(value)
|
||||
if len(value)+padding <= m.Width && pos < len(value) {
|
||||
valWidth := rw.StringWidth(string(value))
|
||||
if m.Width > 0 && len(m.BackgroundColor) > 0 && valWidth <= m.Width {
|
||||
padding := max(0, m.Width-valWidth)
|
||||
if valWidth+padding <= m.Width && pos < len(value) {
|
||||
padding++
|
||||
}
|
||||
v += strings.Repeat(
|
||||
@@ -412,6 +440,10 @@ func Blink(model Model) tea.Cmd {
|
||||
}
|
||||
}
|
||||
|
||||
func clamp(v, low, high int) int {
|
||||
return min(high, max(low, v))
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
|
Reference in New Issue
Block a user