Skip to content

Border manual coloring problem #498

@jcbasso

Description

@jcbasso

Describe the bug
When adding a string as a border that contains ANSI colors, the border is not rendered correctly (specifically its usually taking less width and can contain invalid chars, see screenshots).

I found that fixing this problem would help a lot to the implementation of the top borders, since it would already calculate correctly the margins and use the expected top corners (in contrast of removing top border). And it might also make the border interface more powerful.

Setup

  • OS macOS (intel)
  • Shell: bash & zsh
  • Terminal Emulator: macos terminal & kitty

To Reproduce
Steps to reproduce the behavior:

go run .

Source Code

package main

import (
	tea "github.com/charmbracelet/bubbletea"
	"github.com/charmbracelet/lipgloss"
	"log"
)

type model struct {
	exiting    bool
	style      lipgloss.Style
	colorStyle lipgloss.Style
}

func initial(style lipgloss.Style) model {
	return model{
		style:      style,
		colorStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("10")),
	}
}

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

func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
	return m, tea.Quit
}

func (m model) View() string {
	borderStyle := m.style.GetBorderStyle()
	borderStyle.Top = m.colorStyle.Render(borderStyle.Top)
	m.style = m.style.BorderStyle(borderStyle)
	return m.style.Render("") + "\n"
}

func main() {
	width := 10

	style := lipgloss.NewStyle().
		BorderStyle(lipgloss.RoundedBorder()).
		Width(width).
		Margin(1, 2).
		MarginBackground(lipgloss.Color("#000000")).
		BorderForeground(lipgloss.Color("12"))

	model := initial(style)

	p := tea.NewProgram(model)
	if _, err := p.Run(); err != nil {
		log.Fatal(err)
	}
}

Expected behavior

  1. I would expect that the text is rendered to the full length (omiting ANSI chars).
  2. I would also expect the full ANSI colorization is added (probably overriding the topRight, but that should be a problem for the user to fix). Though a behavior that I would also find acceptable is that the border deletes the ANSI chars and is colorized only by the color style.

Screenshots
Image
Image
*Black background displays the margins

Additional context
The execution of the function ansi.StringWidth, works correctly calculating the width submitting ANSI chars when processing the full string, though when processing iteratively rune by rune it fails.
One solution could be to do something similar to StringWidth by parsing the string and finish when either you process the full string or you have achieved your max length, the following function could be an example of it:

package main

import (
	"fmt"
	"github.com/charmbracelet/lipgloss"
	"github.com/charmbracelet/x/ansi/parser"
	"github.com/rivo/uniseg"
	"strings"
)

func ProcessString(s string, maxLen int) string {
	if s == "" {
		return ""
	}

	var (
		pstate  = parser.GroundState // initial state
		cluster string
		width   int
		out     = strings.Builder{}
	)

	for i := 0; i < len(s); i++ {
		state, action := parser.Table.Transition(pstate, s[i])
		if state == parser.Utf8State {
			var w int
			cluster, _, w, _ = uniseg.FirstGraphemeClusterInString(s[i:], -1)
			width += w
			if width > maxLen {
				// Break if we will exceed the max length, and return string up to here
				break
			}
			i += len(cluster) - 1
			out.WriteString(cluster)
			pstate = parser.GroundState
			continue
		}

		if action == parser.PrintAction {
			// TODO: Not sure if something should be done here
		}

		out.Write([]byte{s[i]})
		pstate = state
	}

	return out.String()
}

func main() {
	s := lipgloss.NewStyle().Foreground(lipgloss.Color("10")).Render("──────")
	fmt.Println(ProcessString(s, 2))
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions