charmbracelet / charmbracelet/bubbles
viewport wrapping strings unnecessarily with UTF-8 characters
- Dominant language
- Go
- Stars
- 8.9k
- Forks
- 457
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 5
Description
**Describe the bug**
When a string has a character with a value larger than 0x7F, it gets encoded into multiple bytes in UTF-8 encoding. When using the viewport component of bubbles, text that should fit within the width of one screen gets wrapped to the next line I suspect because it's counting bytes rather than Runes.
**Setup**
macOS Sonoma
ghostty or iterm2
`locale`:
```text
LANG="en_US.UTF-8"
LC_COLLATE="en_US.UTF-8"
LC_CTYPE="en_US.UTF-8"
LC_MESSAGES="en_US.UTF-8"
LC_MONETARY="en_US.UTF-8"
LC_NUMERIC="en_US.UTF-8"
LC_TIME="en_US.UTF-8"
LC_ALL="en_US.UTF-8"
```
**To Reproduce**
1. Run wrap
```sh
go mod wrap
go build
./wrap
```
2. Change the width of the terminal. "B" should move to the next line only after the width of the terminal can't fit it on the same line.
3. Compare against behavior when string has no utf8 chars.
**Source Code**
main.go:
```go
package main
import (
"log"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
)
func main() {
p := tea.NewProgram(initialModel(), tea.WithAltScreen())
if _, err := p.Run(); err != nil {
log.Fatal(err)
}
}
type (
errMsg error
)
type model struct {
viewport viewport.Model
messages []string
err error
}
func initialModel() model {
vp := viewport.New(30, 5)
// No UTF-8 Chars
// vp.SetContent(`A B`)
// UTF-8 Chars
vp.SetContent(`A B`)
return model{
messages: []string{},
viewport: vp,
err: nil,
}
}
func (m model) Init() tea.Cmd {
return nil
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var (
tiCmd tea.Cmd
vpCmd tea.Cmd
)
m.viewport, vpCmd = m.viewport.Update(msg)
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.viewport.Width = msg.Width
m.viewport.Height = msg.Height
case tea.KeyMsg:
switch msg.Type {
case tea.KeyCtrlC, tea.KeyEsc:
return m, tea.Quit
}
// We handle errors just like any other message
case errMsg:
m.err = msg
return m, nil
}
return m, tea.Batch(tiCmd, vpCmd)
}
func (m model) View() string {
return m.viewport.View()
}
```
*MY* go.mod:
```text
module wrap
go 1.24.0
require (
github.com/charmbracelet/bubbles v0.20.0
github.com/charmbracelet/bubbletea v1.3.4
)
require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/lipgloss v1.0.0 // indirect
github.com/charmbracelet/x/ansi v0.8.0 // indirect
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
golang.org/x/sync v0.11.0 // indirect
golang.org/x/sys v0.30.0 // indirect
golang.org/x/text v0.3.8 // indirect
)
```
**Expected behavior**
Would expect text to fit on one line when the terminal is wide enough.
Contributor guide
Assessment
This issue has not been assessed yet.