color.go 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115
  1. package util
  2. import (
  3. "regexp"
  4. "strings"
  5. "github.com/charmbracelet/lipgloss/v2/compat"
  6. "github.com/sst/opencode/internal/theme"
  7. )
  8. var csiRE *regexp.Regexp
  9. func init() {
  10. csiRE = regexp.MustCompile(`\x1b\[([0-9;]+)m`)
  11. }
  12. var targetFGMap = map[string]string{
  13. "0;0;0": "\x1b[30m", // Black
  14. "128;0;0": "\x1b[31m", // Red
  15. "0;128;0": "\x1b[32m", // Green
  16. "128;128;0": "\x1b[33m", // Yellow
  17. "0;0;128": "\x1b[34m", // Blue
  18. "128;0;128": "\x1b[35m", // Magenta
  19. "0;128;128": "\x1b[36m", // Cyan
  20. "192;192;192": "\x1b[37m", // White (light grey)
  21. "128;128;128": "\x1b[90m", // Bright Black (dark grey)
  22. "255;0;0": "\x1b[91m", // Bright Red
  23. "0;255;0": "\x1b[92m", // Bright Green
  24. "255;255;0": "\x1b[93m", // Bright Yellow
  25. "0;0;255": "\x1b[94m", // Bright Blue
  26. "255;0;255": "\x1b[95m", // Bright Magenta
  27. "0;255;255": "\x1b[96m", // Bright Cyan
  28. "255;255;255": "\x1b[97m", // Bright White
  29. }
  30. var targetBGMap = map[string]string{
  31. "0;0;0": "\x1b[40m",
  32. "128;0;0": "\x1b[41m",
  33. "0;128;0": "\x1b[42m",
  34. "128;128;0": "\x1b[43m",
  35. "0;0;128": "\x1b[44m",
  36. "128;0;128": "\x1b[45m",
  37. "0;128;128": "\x1b[46m",
  38. "192;192;192": "\x1b[47m",
  39. "128;128;128": "\x1b[100m",
  40. "255;0;0": "\x1b[101m",
  41. "0;255;0": "\x1b[102m",
  42. "255;255;0": "\x1b[103m",
  43. "0;0;255": "\x1b[104m",
  44. "255;0;255": "\x1b[105m",
  45. "0;255;255": "\x1b[106m",
  46. "255;255;255": "\x1b[107m",
  47. }
  48. func ConvertRGBToAnsi16Colors(s string) string {
  49. return csiRE.ReplaceAllStringFunc(s, func(seq string) string {
  50. params := strings.Split(csiRE.FindStringSubmatch(seq)[1], ";")
  51. out := make([]string, 0, len(params))
  52. for i := 0; i < len(params); {
  53. // Detect “38 | 48 ; 2 ; r ; g ; b ( ; alpha? )”
  54. if (params[i] == "38" || params[i] == "48") &&
  55. i+4 < len(params) &&
  56. params[i+1] == "2" {
  57. key := strings.Join(params[i+2:i+5], ";")
  58. var repl string
  59. if params[i] == "38" {
  60. repl = targetFGMap[key]
  61. } else {
  62. repl = targetBGMap[key]
  63. }
  64. if repl != "" { // exact RGB hit
  65. out = append(out, repl[2:len(repl)-1])
  66. i += 5 // skip 38/48;2;r;g;b
  67. // if i == len(params)-1 && looksLikeByte(params[i]) {
  68. // i++ // swallow the alpha byte
  69. // }
  70. continue
  71. }
  72. }
  73. // Normal token — keep verbatim.
  74. out = append(out, params[i])
  75. i++
  76. }
  77. return "\x1b[" + strings.Join(out, ";") + "m"
  78. })
  79. }
  80. // func looksLikeByte(tok string) bool {
  81. // v, err := strconv.Atoi(tok)
  82. // return err == nil && v >= 0 && v <= 255
  83. // }
  84. // GetAgentColor returns the color for a given agent index, matching the status bar colors
  85. func GetAgentColor(agentIndex int) compat.AdaptiveColor {
  86. t := theme.CurrentTheme()
  87. agentColors := []compat.AdaptiveColor{
  88. t.TextMuted(),
  89. t.Secondary(),
  90. t.Accent(),
  91. t.Success(),
  92. t.Warning(),
  93. t.Primary(),
  94. t.Error(),
  95. }
  96. if agentIndex >= 0 && agentIndex < len(agentColors) {
  97. return agentColors[agentIndex]
  98. }
  99. return t.Secondary() // default fallback
  100. }