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; { // Measure the width of the current rune, 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 } // 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 j++ if j >= len(r) { j = 0 } } // 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. 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 } }