Skip to content

Commit f2d1864

Browse files
authored
Improve performance of maxRuneWidth (#592)
1 parent fabd8ab commit f2d1864

File tree

4 files changed

+69
-13
lines changed

4 files changed

+69
-13
lines changed

borders.go

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import (
55
"unicode/utf8"
66

77
"github.com/charmbracelet/x/ansi"
8+
"github.com/clipperhouse/displaywidth"
89
"github.com/muesli/termenv"
9-
"github.com/rivo/uniseg"
1010
)
1111

1212
// Border contains a series of values which comprise the various parts of a
@@ -57,10 +57,7 @@ func (b Border) GetLeftSize() int {
5757

5858
func getBorderEdgeWidth(borderParts ...string) (maxWidth int) {
5959
for _, piece := range borderParts {
60-
w := maxRuneWidth(piece)
61-
if w > maxWidth {
62-
maxWidth = w
63-
}
60+
maxWidth = max(maxWidth, maxRuneWidth(piece))
6461
}
6562
return maxWidth
6663
}
@@ -468,17 +465,19 @@ func (s Style) styleBorder(border string, fg, bg TerminalColor) string {
468465
}
469466

470467
func maxRuneWidth(str string) int {
468+
switch len(str) {
469+
case 0:
470+
return 0
471+
case 1:
472+
return displaywidth.String(str)
473+
}
474+
471475
var width int
472476

473-
state := -1
474-
for len(str) > 0 {
475-
var w int
476-
_, str, w, state = uniseg.FirstGraphemeClusterInString(str, state)
477-
if w > width {
478-
width = w
479-
}
477+
g := displaywidth.StringGraphemes(str)
478+
for g.Next() {
479+
width = max(width, g.Width())
480480
}
481-
482481
return width
483482
}
484483

borders_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package lipgloss
22

33
import (
44
"testing"
5+
6+
"github.com/rivo/uniseg"
57
)
68

79
func TestStyle_GetBorderSizes(t *testing.T) {
@@ -173,3 +175,49 @@ func BenchmarkGetFirstRuneAsString(b *testing.B) {
173175
}
174176
})
175177
}
178+
179+
func BenchmarkMaxRuneWidth(b *testing.B) {
180+
testCases := []struct {
181+
name string
182+
str string
183+
}{
184+
{"Blank", " "},
185+
{"ASCII", "+"},
186+
{"Markdown", "|"},
187+
{"Normal", "├"},
188+
{"Rounded", "╭"},
189+
{"Block", "█"},
190+
{"Emoji", "😀"},
191+
}
192+
for _, tc := range testCases {
193+
b.Run(tc.name, func(b *testing.B) {
194+
b.Run("Before", func(b *testing.B) {
195+
b.ReportAllocs()
196+
for b.Loop() {
197+
_ = maxRuneWidthOld(tc.str)
198+
}
199+
})
200+
b.Run("After", func(b *testing.B) {
201+
b.ReportAllocs()
202+
for b.Loop() {
203+
_ = maxRuneWidth(tc.str)
204+
}
205+
})
206+
})
207+
}
208+
}
209+
210+
func maxRuneWidthOld(str string) int {
211+
var width int
212+
213+
state := -1
214+
for len(str) > 0 {
215+
var w int
216+
_, str, w, state = uniseg.FirstGraphemeClusterInString(str, state)
217+
if w > width {
218+
width = w
219+
}
220+
}
221+
222+
return width
223+
}

go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ require (
1111
github.com/charmbracelet/x/ansi v0.10.2
1212
github.com/charmbracelet/x/cellbuf v0.0.13
1313
github.com/charmbracelet/x/exp/golden v0.0.0-20250609102027-b60490452b30
14+
github.com/clipperhouse/displaywidth v0.6.2
1415
github.com/muesli/termenv v0.16.0
1516
github.com/rivo/uniseg v0.4.7
1617
)
@@ -19,6 +20,8 @@ require (
1920
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
2021
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
2122
github.com/charmbracelet/x/term v0.2.1 // indirect
23+
github.com/clipperhouse/stringish v0.1.1 // indirect
24+
github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
2225
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
2326
github.com/mattn/go-isatty v0.0.20 // indirect
2427
github.com/mattn/go-runewidth v0.0.17 // indirect

go.sum

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ github.com/charmbracelet/x/exp/golden v0.0.0-20250609102027-b60490452b30 h1:lF42
1212
github.com/charmbracelet/x/exp/golden v0.0.0-20250609102027-b60490452b30/go.mod h1:IfZAMTHB6XkZSeXUqriemErjAWCCzT0LwjKFYCZyw0I=
1313
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
1414
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
15+
github.com/clipperhouse/displaywidth v0.6.2 h1:ZDpTkFfpHOKte4RG5O/BOyf3ysnvFswpyYrV7z2uAKo=
16+
github.com/clipperhouse/displaywidth v0.6.2/go.mod h1:R+kHuzaYWFkTm7xoMmK1lFydbci4X2CicfbGstSGg0o=
17+
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
18+
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
19+
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
20+
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
1521
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
1622
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
1723
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=

0 commit comments

Comments
 (0)