lipgloss icon indicating copy to clipboard operation
lipgloss copied to clipboard

Background style is not rendered when wrapping other rendered styles

Open drakenstar opened this issue 2 years ago • 4 comments

Describe the bug When rendering strings with backgrounds already applied, the background from our rendering style has issues. In this test case, I've joined 3 other strings using JoinHorizontal that each have their own background. Note the missing background beneath the "right text" section:

Note the area beneath "right text" that does not have a background style applied.

Screen Shot 2023-07-26 at 12 07 59 am

Setup

  • macOS 11.5.2
  • zsh and bash
  • iTerm2 and Terminal.app
  • Locale: en_US.UTF-8

Source Code Simple reproduce:

	redStyle := lipgloss.NewStyle().
		Background(lipgloss.Color("#FF0000")).
		Width(10)

	greenStyle := lipgloss.NewStyle().
		Background(lipgloss.Color("#00FF00")).
		Width(10)

	outerStyle := lipgloss.NewStyle().
		Width(40).
		Background(lipgloss.Color("#0000FF"))

	fmt.Print(outerStyle.Render(lipgloss.JoinHorizontal(0,
		greenStyle.Render("left text"),
		redStyle.Render("multi\nline\ncenter\ntext"),
		greenStyle.Render("right text"),
	)))

Expected behavior In this case I would have expected the blue background from style3 to be rendered in all spaces where there is no background applied.

drakenstar avatar Jul 25 '23 14:07 drakenstar

Ok I dug into this and I think found the reason for this output. wordwrap.String called by Style.Render will not output spaces at the end of a line unless it finds a \n character before the limit. For example:

s := wordwrap.String("test  test  ", 6)
fmt.Print(s)
// "test\ntest"

s := wordwrap.String("test  \ntest  ", 6)
fmt.Print(s)
// "test  \ntest"

This is arguably surprising from their end, however lipgloss could workaround this by trimming whitespace this output if it is not styled by seeking backwards per line for \x1b[0m and trimming spaces after it.

I'm happy to PR this if there's agreement it should be fixed.

drakenstar avatar Jul 26 '23 04:07 drakenstar

Okay yep, this is indeed a bug. I suspect the solution may be may complex than this, though we'll need to look into it further before we can have an opinion on the fix.

Here's a centered use case to further illustrate the issue.

image
package main

import (
	"fmt"

	"github.com/charmbracelet/lipgloss"
)

func main() {
	redStyle := lipgloss.NewStyle().
		Background(lipgloss.Color("#FF0000")).
		Width(10)

	greenStyle := lipgloss.NewStyle().
		Background(lipgloss.Color("#00FF00")).
		Width(10)

	outerStyle := lipgloss.NewStyle().
		Width(40).
		Background(lipgloss.Color("#0000FF"))

	fmt.Println(outerStyle.Render(lipgloss.JoinHorizontal(lipgloss.Center,
		greenStyle.Render("left text"),
		redStyle.Render("multi\nline\ncenter\ntext\nwow"),
		greenStyle.Render("right text"),
	)))
}

meowgorithm avatar Jul 26 '23 14:07 meowgorithm

I've worked around this locally doing as I described above and trimming space characters from the end of a line if they are immediately preceded by a reset sequence.

However there's another harder case that workaround still doesn't solve:

redStyle := lipgloss.NewStyle().
		Background(lipgloss.Color("#FF0000")).
		Width(10)

	greenStyle := lipgloss.NewStyle().
		Background(lipgloss.Color("#00FF00")).
		Width(10)

	outerStyle := lipgloss.NewStyle().
		Width(40).
		Background(lipgloss.Color("#0000FF"))

	fmt.Println(outerStyle.Render(lipgloss.JoinHorizontal(0,
		redStyle.Render("multi\nline"),
		greenStyle.Render("left text"),
		redStyle.Render("multi\nline"),
	)))

Resulting in:

image

A more complete solve for this is probably inspecting each line for segments that have no styling any applying the current style to them.

drakenstar avatar Jul 26 '23 23:07 drakenstar

For now, I'd probably workaround this by simply placing the short column (or all columns) over the appropriate background color with lipgloss.Place (see example and docs).

Here’s how I'd fix my above example (in a real scenario I'd abstract the Place stuff and generalize it to work for all columns):

package main

import (
	"fmt"

	"github.com/charmbracelet/lipgloss"
)

func main() {
	const blue = lipgloss.Color("#0000FF")

	redStyle := lipgloss.NewStyle().
		Background(lipgloss.Color("#FF0000")).
		Width(10)

	greenStyle := lipgloss.NewStyle().
		Background(lipgloss.Color("#00FF00")).
		Width(10)

	outerStyle := lipgloss.NewStyle().
		Width(40).
		Background(blue)

	leftContent := greenStyle.Render("left text")
	middleContent := redStyle.Render("multi\nline\ncenter\ntext\nwow")
	rightContent := greenStyle.Render("right text")

	fmt.Println(outerStyle.Render(
		lipgloss.JoinHorizontal(
			lipgloss.Center,
			leftContent,
			middleContent,
			lipgloss.Place(
				lipgloss.Width(rightContent),            // width
				lipgloss.Height(middleContent),          // height
				lipgloss.Left,                           // x
				lipgloss.Center,                         // y
				rightContent,                            // content
				lipgloss.WithWhitespaceBackground(blue), // background
			),
		)))
}

Output:

image

meowgorithm avatar Jul 27 '23 15:07 meowgorithm