bubbles icon indicating copy to clipboard operation
bubbles copied to clipboard

How to define the color of a specified cell in a table

Open JJ-H opened this issue 1 year ago • 2 comments

Describe the bug When I specified the color of cell text,I got a unexpected table style, cell text disappear

Setup Please complete the following information along with version numbers, if applicable.

  • OS [ macOS]
  • Shell [ zsh]
  • Terminal Emulator [ iterm]
  • Terminal Multiplexer [tmux]
  • Locale [ zh_CN.UTF-8.]

To Reproduce Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Source Code

acceptMsg := lipgloss.NewStyle().Inline(true).Foreground(lipgloss.Color("#BBFFFF")).Render("Accept")
rows = append(rows, table.Row{pr.Title, pr.Number, pr.Creator.Name, acceptMsg, conflictMsg, mergeCheckMsg})

Expected behavior AcceptMsg display with color and table style is correct.

Screenshots Accept disappeared ! image

Additional context Add any other context about the problem here.

JJ-H avatar Mar 29 '24 16:03 JJ-H

For others that may be trying to recreate this:

I used the table example from bubbletea (examples/table/main.go). This git diff shows the minimal required changes to reproduce this issue:

diff --git a/examples/table/main.go b/examples/table/main.go
index 76736f3..ca0bf4d 100644
--- a/examples/table/main.go
+++ b/examples/table/main.go
@@ -54,8 +54,13 @@ func main() {
                {Title: "Population", Width: 10},
        }

+       japanStr := lipgloss.NewStyle().
+               Foreground(lipgloss.Color("#BBFFFF")).
+               Render("Japan")
+       //japanStr = ansi.Strip(japanStr)
+
        rows := []table.Row{
-               {"1", "Tokyo", "Japan", "37,274,000"},
+               {"1", "Tokyo", japanStr, "37,274,000"},
                {"2", "Delhi", "India", "32,065,760"},
                {"3", "Shanghai", "China", "28,516,904"},
                {"4", "Dhaka", "Bangladesh", "22,478,116"},

This produces output where "Japan" is omitted: image

By uncommenting the ansi.Strip line (shown in the git diff) the output changes to include "Japan", however without the desired foreground color: image

This suggests that color ANSI escape sequences are to blame for this behaviour. I'm continuing to look into the cause.

Broderick-Westrope avatar Aug 26 '24 04:08 Broderick-Westrope

I believe I've found the source of the issue. If anything I say is wrong please correct me, I'm not a Go pro ;)

The contents of every cell is truncated to be no greater than the assigned column width. This is done using the Truncate function from the mattn/go-runewidth package. When we use ligploss to color a cells foreground we are writing escape sequences on either side of the cell "content". These escape sequences are not normally visible when printed but there are ways to see them like this:

japanStr := lipgloss.NewStyle().Foreground(lipgloss.Color("#BBFFFF")).Render("Japan")
fmt.Sprintf("%#v", japanStr)
// prints: "\x1b[38;2;187;255;255mJapan\x1b[0m"

Even though the escape sequences are not visible when printing normally, they are composed of runes which are modified during the runewidth.Truncate call.

I've thrown together a (hopefully) simple example to demonstrate this, see below. Currently this prints colors that overflow into other columns and cells, but by commenting the runewidth.Truncate line (near the bottom) the issues go away. This is because the "closing" escape sequence is removed during the truncation. I've included the original color code as row 1 in my example below. I am uncertain why this is the only line that completely disappears.

package main

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/charmbracelet/bubbles/table"
	"github.com/charmbracelet/bubbles/viewport"
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	"github.com/mattn/go-runewidth"
)

func main() {
	m := newModel()

	_, err := tea.NewProgram(m).Run()
	if err != nil {
		panic(err)
	}
}

type model struct {
	viewport    viewport.Model
	borderStyle lipgloss.Style

	cols []table.Column
	rows []table.Row
}

func newModel() model {
	columns := []table.Column{
		{Title: "Rank", Width: 4},
		{Title: "City", Width: 10},
		{Title: "Country", Width: 10},
		{Title: "Population", Width: 10},
		{Title: "Country Escaped", Width: 50},
	}

	rows := []table.Row{
		{"1", "Tokyo", "Japan", "37,274,000"},
		{"2", "Delhi", "India", "32,065,760"},
		{"3", "Shanghai", "China", "28,516,904"},
		{"4", "Dhaka", "Bangladesh", "22,478,116"},
		{"5", "São Paulo", "Brazil", "22,429,800"},
		{"6", "Mexico City", "Mexico", "22,085,140"},
		{"7", "Cairo", "Egypt", "21,750,020"},
		{"8", "Beijing", "China", "21,333,332"},
		{"9", "Mumbai", "India", "20,961,472"},
		{"10", "Osaka", "Japan", "19,059,856"},
		{"11", "Chongqing", "China", "16,874,740"},
		{"12", "Karachi", "Pakistan", "16,839,950"},
		{"13", "Istanbul", "Turkey", "15,636,243"},
		{"14", "Kinshasa", "DR Congo", "15,628,085"},
		{"15", "Lagos", "Nigeria", "15,387,639"},
		{"16", "Buenos Aires", "Argentina", "15,369,919"},
		{"17", "Kolkata", "India", "15,133,888"},
		{"18", "Manila", "Philippines", "14,406,059"},
		{"19", "Tianjin", "China", "14,011,828"},
		{"20", "Guangzhou", "China", "13,964,637"},
		{"21", "Rio De Janeiro", "Brazil", "13,634,274"},
		{"22", "Lahore", "Pakistan", "13,541,764"},
		{"23", "Bangalore", "India", "13,193,035"},
		{"24", "Shenzhen", "China", "12,831,330"},
		{"25", "Moscow", "Russia", "12,640,818"},
		{"26", "Chennai", "India", "11,503,293"},
		{"27", "Bogota", "Colombia", "11,344,312"},
		{"28", "Paris", "France", "11,142,303"},
		{"29", "Jakarta", "Indonesia", "11,074,811"},
		{"30", "Lima", "Peru", "11,044,607"},
	}

	for i := range rows {
		color := lipgloss.Color(strconv.Itoa(i))
		if i == 0 {
			color = "#BBFFFF"
		}

		rows[i][2] = lipgloss.NewStyle().Foreground(color).Render(rows[i][2])
		rows[i] = append(rows[i], fmt.Sprintf("%#v", rows[i][2]))
	}

	m := model{
		viewport:    viewport.New(100, 30),
		borderStyle: lipgloss.NewStyle().BorderStyle(lipgloss.NormalBorder()),
		cols:        columns,
		rows:        rows,
	}

	m.UpdateViewport()

	return m
}

func (m model) Init() tea.Cmd {
	return nil
}

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	switch msg := msg.(type) {
	case tea.KeyMsg:
		switch msg.String() {
		case "q":
			return m, tea.Quit
		}
	}

	var cmd tea.Cmd
	m.viewport, cmd = m.viewport.Update(msg)
	return m, cmd
}

func (m model) View() string {
	var output string

	output += m.viewport.View()
	output = m.borderStyle.Render(output)

	return output
}

func (m *model) UpdateViewport() {
	// Assumption: cols will not contain a large number of elements
	renderedRows := make([]string, 0, len(m.rows))
	for i := 0; i < len(m.rows); i++ {
		renderedRows = append(renderedRows, m.renderRow(i))
	}

	m.viewport.SetContent(
		strings.Join(renderedRows, "\n"),
	)
}

func (m *model) renderRow(rowID int) string {
	var rowCells = make([]string, 0, len(m.cols))

	for i, value := range m.rows[rowID] {
		// COMMENT THIS LINE TO FIX THE BUG
		value = runewidth.Truncate(value, m.cols[i].Width, "…")

		style := lipgloss.NewStyle().Width(m.cols[i].Width).MaxWidth(m.cols[i].Width).Inline(true)
		value = style.Render(value)
		rowCells = append(rowCells, value)
	}

	return lipgloss.JoinHorizontal(lipgloss.Left, rowCells...)
}

Broderick-Westrope avatar Aug 28 '24 05:08 Broderick-Westrope