background.go

 1package styles
 2
 3import (
 4	"fmt"
 5	"regexp"
 6	"strconv"
 7	"strings"
 8
 9	"github.com/charmbracelet/lipgloss"
10)
11
12var ansiEscape = regexp.MustCompile("\x1b\\[[0-9;]*m")
13
14func getColorRGB(c lipgloss.TerminalColor) (uint8, uint8, uint8) {
15	r, g, b, a := c.RGBA()
16
17	// Un-premultiply alpha if needed
18	if a > 0 && a < 0xffff {
19		r = (r * 0xffff) / a
20		g = (g * 0xffff) / a
21		b = (b * 0xffff) / a
22	}
23
24	// Convert from 16-bit to 8-bit color
25	return uint8(r >> 8), uint8(g >> 8), uint8(b >> 8)
26}
27
28func ForceReplaceBackgroundWithLipgloss(input string, newBgColor lipgloss.TerminalColor) string {
29	r, g, b := getColorRGB(newBgColor)
30
31	newBg := fmt.Sprintf("48;2;%d;%d;%d", r, g, b)
32
33	return ansiEscape.ReplaceAllStringFunc(input, func(seq string) string {
34		// Extract content between "\x1b[" and "m"
35		content := seq[2 : len(seq)-1]
36		tokens := strings.Split(content, ";")
37		var newTokens []string
38
39		// Skip background color tokens
40		for i := 0; i < len(tokens); i++ {
41			if tokens[i] == "" {
42				continue
43			}
44
45			val, err := strconv.Atoi(tokens[i])
46			if err != nil {
47				newTokens = append(newTokens, tokens[i])
48				continue
49			}
50
51			// Skip background color tokens
52			if val == 48 {
53				// Skip "48;5;N" or "48;2;R;G;B" sequences
54				if i+1 < len(tokens) {
55					if nextVal, err := strconv.Atoi(tokens[i+1]); err == nil {
56						switch nextVal {
57						case 5:
58							i += 2 // Skip "5" and color index
59						case 2:
60							i += 4 // Skip "2" and RGB components
61						}
62					}
63				}
64			} else if (val < 40 || val > 47) && (val < 100 || val > 107) && val != 49 {
65				// Keep non-background tokens
66				newTokens = append(newTokens, tokens[i])
67			}
68		}
69
70		// Add new background if provided
71		if newBg != "" {
72			newTokens = append(newTokens, strings.Split(newBg, ";")...)
73		}
74
75		if len(newTokens) == 0 {
76			return ""
77		}
78
79		return "\x1b[" + strings.Join(newTokens, ";") + "m"
80	})
81}