Upstream charmbracelet/lipgloss state of the touched files before 1a8841c478
This commit is contained in:
commit
080e6dc737
2 files changed
+128
No files matched your search
@@ -0,0 +1,76 @@
|
|||||||
|
package lipgloss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/x/ansi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// whitespace is a whitespace renderer.
|
||||||
|
type whitespace struct {
|
||||||
|
chars string
|
||||||
|
style Style
|
||||||
|
}
|
||||||
|
|
||||||
|
// newWhitespace creates a new whitespace renderer.
|
||||||
|
func newWhitespace(opts ...WhitespaceOption) *whitespace {
|
||||||
|
w := &whitespace{}
|
||||||
|
for _, opt := range opts {
|
||||||
|
opt(w)
|
||||||
|
}
|
||||||
|
return w
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render whitespaces.
|
||||||
|
func (w whitespace) render(width int) string {
|
||||||
|
if w.chars == "" {
|
||||||
|
w.chars = " "
|
||||||
|
}
|
||||||
|
|
||||||
|
r := []rune(w.chars)
|
||||||
|
j := 0
|
||||||
|
b := strings.Builder{}
|
||||||
|
|
||||||
|
// Cycle through runes and print them into the whitespace.
|
||||||
|
for i := 0; i < width; {
|
||||||
|
b.WriteRune(r[j])
|
||||||
|
// Measure the width of the rune we just wrote, ensuring we always
|
||||||
|
// make progress to avoid infinite loops with zero-width characters
|
||||||
|
// like tabs.
|
||||||
|
runeWidth := ansi.StringWidth(string(r[j]))
|
||||||
|
if runeWidth < 1 {
|
||||||
|
runeWidth = 1
|
||||||
|
}
|
||||||
|
i += runeWidth
|
||||||
|
j++
|
||||||
|
if j >= len(r) {
|
||||||
|
j = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill any extra gaps white spaces. This might be necessary if any runes
|
||||||
|
// are more than one cell wide, which could leave a one-rune gap.
|
||||||
|
short := width - ansi.StringWidth(b.String())
|
||||||
|
if short > 0 {
|
||||||
|
b.WriteString(strings.Repeat(" ", short))
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.style.Render(b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhitespaceOption sets a styling rule for rendering whitespace.
|
||||||
|
type WhitespaceOption func(*whitespace)
|
||||||
|
|
||||||
|
// WithWhitespaceStyle sets the style for the whitespace.
|
||||||
|
func WithWhitespaceStyle(s Style) WhitespaceOption {
|
||||||
|
return func(w *whitespace) {
|
||||||
|
w.style = s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithWhitespaceChars sets the characters to be rendered in the whitespace.
|
||||||
|
func WithWhitespaceChars(s string) WhitespaceOption {
|
||||||
|
return func(w *whitespace) {
|
||||||
|
w.chars = s
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package lipgloss
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWhitespaceRenderWithTab(t *testing.T) {
|
||||||
|
// This test verifies that rendering whitespace with tab characters
|
||||||
|
// doesn't cause an infinite loop (issue #108)
|
||||||
|
done := make(chan bool, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ws := newWhitespace(WithWhitespaceChars("\t"))
|
||||||
|
_ = ws.render(10)
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
// Success - render completed
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("whitespace.render() with tab character caused infinite loop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWhitespaceRenderWithZeroWidthChar(t *testing.T) {
|
||||||
|
// Test with zero-width joiner (another zero-width character)
|
||||||
|
done := make(chan bool, 1)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
ws := newWhitespace(WithWhitespaceChars("\u200d")) // zero-width joiner
|
||||||
|
_ = ws.render(5)
|
||||||
|
done <- true
|
||||||
|
}()
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
// Success
|
||||||
|
case <-time.After(2 * time.Second):
|
||||||
|
t.Fatal("whitespace.render() with zero-width character caused infinite loop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWhitespaceRenderNormal(t *testing.T) {
|
||||||
|
// Verify normal behavior still works
|
||||||
|
ws := newWhitespace(WithWhitespaceChars("*"))
|
||||||
|
result := ws.render(5)
|
||||||
|
if len(result) != 5 {
|
||||||
|
t.Errorf("expected 5 characters, got %d", len(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user