How to define the color of a specified cell in a table
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:
- Go to '...'
- Click on '....'
- Scroll down to '....'
- 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 !
Additional context Add any other context about the problem here.
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:
By uncommenting the ansi.Strip line (shown in the git diff) the output changes to include "Japan", however without the desired foreground color:
This suggests that color ANSI escape sequences are to blame for this behaviour. I'm continuing to look into the cause.
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...)
}