fix(whitespace): prevent wide runes from overflowing render width
whitespace.render checked the loop bound only before writing a rune, then advanced by the rune width. A rune wider than the remaining space was still written, overshooting the requested width by up to runeWidth-1 cells, and this made the trailing space-fill unreachable. Stop before a wide rune would overflow and let the existing space-fill pad the remaining cells, so the result is always exactly the requested width. This also fixes overflow surfaced through Place/PlaceHorizontal with WithWhitespaceChars for multi-cell whitespace characters.
This commit is contained in:
1 parent
080e6dc737
commit
4e196d82d8
2 files changed
+34
-5
No files matched your search
+10
-5
@@ -33,14 +33,19 @@ func (w whitespace) render(width int) string {
|
|||||||
|
|
||||||
// Cycle through runes and print them into the whitespace.
|
// Cycle through runes and print them into the whitespace.
|
||||||
for i := 0; i < width; {
|
for i := 0; i < width; {
|
||||||
b.WriteRune(r[j])
|
// Measure the width of the current rune, ensuring we always make
|
||||||
// Measure the width of the rune we just wrote, ensuring we always
|
// progress to avoid infinite loops with zero-width characters like
|
||||||
// make progress to avoid infinite loops with zero-width characters
|
// tabs.
|
||||||
// like tabs.
|
|
||||||
runeWidth := ansi.StringWidth(string(r[j]))
|
runeWidth := ansi.StringWidth(string(r[j]))
|
||||||
if runeWidth < 1 {
|
if runeWidth < 1 {
|
||||||
runeWidth = 1
|
runeWidth = 1
|
||||||
}
|
}
|
||||||
|
// Stop before a wide rune would overflow the requested width; the
|
||||||
|
// remaining cells are filled with spaces below.
|
||||||
|
if i+runeWidth > width {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
b.WriteRune(r[j])
|
||||||
i += runeWidth
|
i += runeWidth
|
||||||
j++
|
j++
|
||||||
if j >= len(r) {
|
if j >= len(r) {
|
||||||
@@ -48,7 +53,7 @@ func (w whitespace) render(width int) string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fill any extra gaps white spaces. This might be necessary if any runes
|
// Fill any extra gaps with spaces. This might be necessary if any runes
|
||||||
// are more than one cell wide, which could leave a one-rune gap.
|
// are more than one cell wide, which could leave a one-rune gap.
|
||||||
short := width - ansi.StringWidth(b.String())
|
short := width - ansi.StringWidth(b.String())
|
||||||
if short > 0 {
|
if short > 0 {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package lipgloss
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/x/ansi"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestWhitespaceRenderWithTab(t *testing.T) {
|
func TestWhitespaceRenderWithTab(t *testing.T) {
|
||||||
@@ -50,3 +52,25 @@ func TestWhitespaceRenderNormal(t *testing.T) {
|
|||||||
t.Errorf("expected 5 characters, got %d", len(result))
|
t.Errorf("expected 5 characters, got %d", len(result))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWhitespaceRenderWideRune(t *testing.T) {
|
||||||
|
// A whitespace char that is more than one cell wide must never render
|
||||||
|
// wider than the requested width; the trailing cells are padded with
|
||||||
|
// spaces instead of overflowing.
|
||||||
|
ws := newWhitespace(WithWhitespaceChars("東")) // width 2
|
||||||
|
for _, width := range []int{0, 1, 2, 3, 4, 5} {
|
||||||
|
got := ansi.StringWidth(ws.render(width))
|
||||||
|
if got != width {
|
||||||
|
t.Errorf("render(%d): expected rendered width %d, got %d", width, width, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPlaceHorizontalWideWhitespace(t *testing.T) {
|
||||||
|
// Placing content in a box with a wide whitespace char must keep the
|
||||||
|
// box exactly the requested width.
|
||||||
|
got := PlaceHorizontal(5, Left, "ab", WithWhitespaceChars("東"))
|
||||||
|
if w := ansi.StringWidth(got); w != 5 {
|
||||||
|
t.Errorf("PlaceHorizontal width: expected 5, got %d (%q)", w, got)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user