mirror of
https://github.com/Maks1mS/bubbles.git
synced 2024-12-24 14:44:38 +03:00
Update text input viewport scrolling to handle unicode characters
This commit is contained in:
parent
74cc86fce5
commit
d3192a3e70
1
go.mod
1
go.mod
@ -4,6 +4,7 @@ go 1.13
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/charmbracelet/bubbletea v0.8.0
|
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
|
github.com/muesli/termenv v0.5.3-0.20200526053627-d728968dcf83
|
||||||
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
|
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
|
||||||
golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980 // 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/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 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
|
||||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
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 h1:AfshZBlqAwhCZ27NJ1aPlMcPBihF1squ1GpaollhLQk=
|
||||||
github.com/muesli/termenv v0.5.3-0.20200526053627-d728968dcf83/go.mod h1:O1/I6sw+6KcrgAmcs6uiUVr7Lui+DNVbHTzt9Lm/PlI=
|
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=
|
github.com/pkg/term v0.0.0-20200520122047-c3ffed290a03 h1:pd4YKIqCB0U7O2I4gWHgEUA2mCEOENmco0l/bM957bU=
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
rw "github.com/mattn/go-runewidth"
|
||||||
"github.com/muesli/termenv"
|
"github.com/muesli/termenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -59,7 +60,8 @@ type Model struct {
|
|||||||
|
|
||||||
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetValue sets the value of the text input.
|
// SetValue sets the value of the text input.
|
||||||
@ -84,7 +86,7 @@ func (m Model) Value() string {
|
|||||||
// Cursor start moves the cursor to the given position. If the position is out
|
// 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.
|
// 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 = max(0, min(len(m.value), pos))
|
m.pos = clamp(pos, 0, len(m.value))
|
||||||
m.handleOverflow()
|
m.handleOverflow()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +122,6 @@ func (m *Model) Blur() {
|
|||||||
// Reset sets the input to its default state with no input.
|
// Reset sets the input to its default state with no input.
|
||||||
func (m *Model) Reset() {
|
func (m *Model) Reset() {
|
||||||
m.value = nil
|
m.value = nil
|
||||||
m.offset = 0
|
|
||||||
m.pos = 0
|
m.pos = 0
|
||||||
m.blink = false
|
m.blink = false
|
||||||
}
|
}
|
||||||
@ -128,14 +129,46 @@ func (m *Model) Reset() {
|
|||||||
// If a max width is defined, perform some logic to treat the visible area
|
// If a max width is defined, perform some logic to treat the visible area
|
||||||
// as a horizontally scrolling viewport.
|
// as a horizontally scrolling viewport.
|
||||||
func (m *Model) handleOverflow() {
|
func (m *Model) handleOverflow() {
|
||||||
if m.Width > 0 {
|
if m.Width <= 0 || rw.StringWidth(string(m.value)) <= m.Width {
|
||||||
overflow := max(0, len(m.value)-m.Width)
|
m.offset = 0
|
||||||
|
m.offsetRight = len(m.value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if overflow > 0 && m.pos < m.offset {
|
if 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 = m.pos
|
||||||
m.offset = max(0, m.pos-m.Width)
|
|
||||||
|
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)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,14 +362,7 @@ func View(model tea.Model) string {
|
|||||||
return placeholderView(m)
|
return placeholderView(m)
|
||||||
}
|
}
|
||||||
|
|
||||||
left := m.offset
|
value := m.value[m.offset:m.offsetRight]
|
||||||
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]
|
|
||||||
pos := m.pos - m.offset
|
pos := m.pos - m.offset
|
||||||
|
|
||||||
v := m.colorText(string(value[:pos]))
|
v := m.colorText(string(value[:pos]))
|
||||||
@ -350,9 +376,10 @@ func View(model tea.Model) string {
|
|||||||
|
|
||||||
// If a max width and background color were set fill the empty spaces with
|
// If a max width and background color were set fill the empty spaces with
|
||||||
// the background color.
|
// the background color.
|
||||||
if m.Width > 0 && len(m.BackgroundColor) > 0 && len(value) <= m.Width {
|
valWidth := rw.StringWidth(string(value))
|
||||||
padding := m.Width - len(value)
|
if m.Width > 0 && len(m.BackgroundColor) > 0 && valWidth <= m.Width {
|
||||||
if len(value)+padding <= m.Width && pos < len(value) {
|
padding := max(0, m.Width-valWidth)
|
||||||
|
if valWidth+padding <= m.Width && pos < len(value) {
|
||||||
padding++
|
padding++
|
||||||
}
|
}
|
||||||
v += strings.Repeat(
|
v += strings.Repeat(
|
||||||
@ -413,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 {
|
func min(a, b int) int {
|
||||||
if a < b {
|
if a < b {
|
||||||
return a
|
return a
|
||||||
|
Loading…
Reference in New Issue
Block a user