Skip to content

Commit

Permalink
feat: use inhouse wcwidth package instead of go-runewidth
Browse files Browse the repository at this point in the history
This change replaces the `go-runewidth` package with our in-house
x/wcwidth package. This package is a simplified version of go-runewidth
that uses the `golang.org/x/text` package for Unicode character width
calculations.

We also can't be using both go-runewidth and uniseg to calculate
character widths, so we've removed the uniseg package as well.
Otherwise, we might end up with inconsistent character width
in terminals that don't support grapheme clusters.
  • Loading branch information
aymanbagabas committed Oct 21, 2024
1 parent 178590b commit 7623152
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 32 deletions.
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,10 @@ require (
github.com/charmbracelet/lipgloss v0.13.0
github.com/charmbracelet/x/ansi v0.3.2
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91
github.com/charmbracelet/x/wcwidth v0.0.0-20241021033131-8140f283f8ec
github.com/dustin/go-humanize v1.0.1
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-runewidth v0.0.16
github.com/muesli/termenv v0.15.2
github.com/rivo/uniseg v0.4.7
github.com/sahilm/fuzzy v0.1.1
)

Expand All @@ -26,9 +25,11 @@ require (
github.com/kylelemons/godebug v1.1.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/rivo/uniseg v0.4.7 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/text v0.3.8 // indirect
golang.org/x/text v0.19.0 // indirect
)
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payR
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
github.com/charmbracelet/x/term v0.2.0 h1:cNB9Ot9q8I711MyZ7myUR5HFWL/lc3OpU8jZ4hwm0x0=
github.com/charmbracelet/x/term v0.2.0/go.mod h1:GVxgxAbjUrmpvIINHIQnJJKpMlHiZ4cktEQCN6GWyF0=
github.com/charmbracelet/x/wcwidth v0.0.0-20241021033131-8140f283f8ec h1:uzb9WgyFdLApCiC41+GWjIWY1wNZ6MrS2NRp0U/0hCI=
github.com/charmbracelet/x/wcwidth v0.0.0-20241021033131-8140f283f8ec/go.mod h1:Ey8PFmYwH+/td9bpiEx07Fdx9ZVkxfIjWXxBluxF4Nw=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
Expand Down Expand Up @@ -49,5 +51,5 @@ golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
6 changes: 3 additions & 3 deletions table/table.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/mattn/go-runewidth"
"github.com/charmbracelet/x/ansi"
)

// Model defines a state for the table widget.
Expand Down Expand Up @@ -412,7 +412,7 @@ func (m Model) headersView() string {
continue
}
style := lipgloss.NewStyle().Width(col.Width).MaxWidth(col.Width).Inline(true)
renderedCell := style.Render(runewidth.Truncate(col.Title, col.Width, "…"))
renderedCell := style.Render(ansi.Truncate(col.Title, col.Width, "…"))
s = append(s, m.styles.Header.Render(renderedCell))
}
return lipgloss.JoinHorizontal(lipgloss.Top, s...)
Expand All @@ -425,7 +425,7 @@ func (m *Model) renderRow(r int) string {
continue
}
style := lipgloss.NewStyle().Width(m.cols[i].Width).MaxWidth(m.cols[i].Width).Inline(true)
renderedCell := m.styles.Cell.Render(style.Render(runewidth.Truncate(value, m.cols[i].Width, "…")))
renderedCell := m.styles.Cell.Render(style.Render(ansi.Truncate(value, m.cols[i].Width, "…")))
s = append(s, renderedCell)
}

Expand Down
33 changes: 16 additions & 17 deletions textarea/textarea.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi"
rw "github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
"github.com/charmbracelet/x/wcwidth"
)

const (
Expand Down Expand Up @@ -462,7 +461,7 @@ func (m Model) Value() string {
func (m *Model) Length() int {
var l int
for _, row := range m.value {
l += uniseg.StringWidth(string(row))
l += wcwidth.StringWidth(string(row))
}
// We add len(m.value) to include the newline characters.
return l + len(m.value) - 1
Expand Down Expand Up @@ -507,7 +506,7 @@ func (m *Model) CursorDown() {
if m.row >= len(m.value) || m.col >= len(m.value[m.row]) || offset >= nli.CharWidth-1 {
break
}
offset += rw.RuneWidth(m.value[m.row][m.col])
offset += wcwidth.RuneWidth(m.value[m.row][m.col])
m.col++
}
}
Expand Down Expand Up @@ -541,7 +540,7 @@ func (m *Model) CursorUp() {
if m.col >= len(m.value[m.row]) || offset >= nli.CharWidth-1 {
break
}
offset += rw.RuneWidth(m.value[m.row][m.col])
offset += wcwidth.RuneWidth(m.value[m.row][m.col])
m.col++
}
}
Expand Down Expand Up @@ -829,19 +828,19 @@ func (m Model) LineInfo() LineInfo {
RowOffset: i + 1,
StartColumn: m.col,
Width: len(grid[i+1]),
CharWidth: uniseg.StringWidth(string(line)),
CharWidth: wcwidth.StringWidth(string(line)),
}
}

if counter+len(line) >= m.col {
return LineInfo{
CharOffset: uniseg.StringWidth(string(line[:max(0, m.col-counter)])),
CharOffset: wcwidth.StringWidth(string(line[:max(0, m.col-counter)])),
ColumnOffset: m.col - counter,
Height: len(grid),
RowOffset: i,
StartColumn: counter,
Width: len(line),
CharWidth: uniseg.StringWidth(string(line)),
CharWidth: wcwidth.StringWidth(string(line)),
}
}

Expand Down Expand Up @@ -891,7 +890,7 @@ func (m *Model) SetWidth(w int) {
// Update prompt width only if there is no prompt function as SetPromptFunc
// updates the prompt width when it is called.
if m.promptFunc == nil {
m.promptWidth = uniseg.StringWidth(m.Prompt)
m.promptWidth = wcwidth.StringWidth(m.Prompt)
}

// Add base style borders and padding to reserved outer width.
Expand Down Expand Up @@ -1145,7 +1144,7 @@ func (m Model) View() string {
widestLineNumber = lnw
}

strwidth := uniseg.StringWidth(string(wrappedLine))
strwidth := wcwidth.StringWidth(string(wrappedLine))
padding := m.width - strwidth
// If the trailing space causes the line to be wider than the
// width, we should not draw it to the screen since it will result
Expand Down Expand Up @@ -1212,7 +1211,7 @@ func (m Model) getPromptString(displayLine int) (prompt string) {
return prompt
}
prompt = m.promptFunc(displayLine)
pl := uniseg.StringWidth(prompt)
pl := wcwidth.StringWidth(prompt)
if pl < m.promptWidth {
prompt = fmt.Sprintf("%*s%s", m.promptWidth-pl, "", prompt)
}
Expand Down Expand Up @@ -1273,12 +1272,12 @@ func (m Model) placeholderView() string {
s.WriteString(lineStyle.Render(m.Cursor.View()))

// the rest of the first line
s.WriteString(lineStyle.Render(style.Render(plines[0][1:] + strings.Repeat(" ", max(0, m.width-uniseg.StringWidth(plines[0]))))))
s.WriteString(lineStyle.Render(style.Render(plines[0][1:] + strings.Repeat(" ", max(0, m.width-wcwidth.StringWidth(plines[0]))))))
// remaining lines
case len(plines) > i:
// current line placeholder text
if len(plines) > i {
s.WriteString(lineStyle.Render(style.Render(plines[i] + strings.Repeat(" ", max(0, m.width-uniseg.StringWidth(plines[i]))))))
s.WriteString(lineStyle.Render(style.Render(plines[i] + strings.Repeat(" ", max(0, m.width-wcwidth.StringWidth(plines[i]))))))
}
default:
// end of line buffer character
Expand Down Expand Up @@ -1408,7 +1407,7 @@ func wrap(runes []rune, width int) [][]rune {
}

if spaces > 0 {

Check failure on line 1409 in textarea/textarea.go

View workflow job for this annotation

GitHub Actions / lint-soft

`if spaces > 0` has complex nested blocks (complexity: 6) (nestif)
if uniseg.StringWidth(string(lines[row]))+uniseg.StringWidth(string(word))+spaces > width {
if wcwidth.StringWidth(string(lines[row]))+wcwidth.StringWidth(string(word))+spaces > width {
row++
lines = append(lines, []rune{})
lines[row] = append(lines[row], word...)
Expand All @@ -1424,8 +1423,8 @@ func wrap(runes []rune, width int) [][]rune {
} else {
// If the last character is a double-width rune, then we may not be able to add it to this line
// as it might cause us to go past the width.
lastCharLen := rw.RuneWidth(word[len(word)-1])
if uniseg.StringWidth(string(word))+lastCharLen > width {
lastCharLen := wcwidth.RuneWidth(word[len(word)-1])
if wcwidth.StringWidth(string(word))+lastCharLen > width {
// If the current line has any content, let's move to the next
// line because the current word fills up the entire line.
if len(lines[row]) > 0 {
Expand All @@ -1438,7 +1437,7 @@ func wrap(runes []rune, width int) [][]rune {
}
}

if uniseg.StringWidth(string(lines[row]))+uniseg.StringWidth(string(word))+spaces >= width {
if wcwidth.StringWidth(string(lines[row]))+wcwidth.StringWidth(string(word))+spaces >= width {
lines = append(lines, []rune{})
lines[row+1] = append(lines[row+1], word...)
// We add an extra space at the end of the line to account for the
Expand Down
13 changes: 6 additions & 7 deletions textinput/textinput.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ import (
"github.com/charmbracelet/bubbles/runeutil"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
rw "github.com/mattn/go-runewidth"
"github.com/rivo/uniseg"
"github.com/charmbracelet/x/wcwidth"
)

// Internal messages for clipboard operations.
Expand Down Expand Up @@ -325,7 +324,7 @@ func (m *Model) insertRunesFromUserInput(v []rune) {
// 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 || uniseg.StringWidth(string(m.value)) <= m.Width {
if m.Width <= 0 || wcwidth.StringWidth(string(m.value)) <= m.Width {
m.offset = 0
m.offsetRight = len(m.value)
return
Expand All @@ -342,7 +341,7 @@ func (m *Model) handleOverflow() {
runes := m.value[m.offset:]

for i < len(runes) && w <= m.Width {
w += rw.RuneWidth(runes[i])
w += wcwidth.RuneWidth(runes[i])
if w <= m.Width+1 {
i++
}
Expand All @@ -357,7 +356,7 @@ func (m *Model) handleOverflow() {
i := len(runes) - 1

for i > 0 && w < m.Width {
w += rw.RuneWidth(runes[i])
w += wcwidth.RuneWidth(runes[i])
if w <= m.Width {
i--
}
Expand Down Expand Up @@ -538,7 +537,7 @@ func (m *Model) wordForward() {
func (m Model) echoTransform(v string) string {
switch m.EchoMode {
case EchoPassword:
return strings.Repeat(string(m.EchoCharacter), uniseg.StringWidth(v))
return strings.Repeat(string(m.EchoCharacter), wcwidth.StringWidth(v))
case EchoNone:
return ""
case EchoNormal:
Expand Down Expand Up @@ -684,7 +683,7 @@ func (m Model) View() string {

// If a max width and background color were set fill the empty spaces with
// the background color.
valWidth := uniseg.StringWidth(string(value))
valWidth := wcwidth.StringWidth(string(value))
if m.Width > 0 && valWidth <= m.Width {
padding := max(0, m.Width-valWidth)
if valWidth+padding <= m.Width && pos < len(value) {
Expand Down

0 comments on commit 7623152

Please sign in to comment.