|
| 1 | +# Analysis: 256-Color Support for color.go |
| 2 | + |
| 3 | +## Current Implementation |
| 4 | + |
| 5 | +The current `color.go` implementation supports: |
| 6 | +- **16 basic colors**: 8 standard + 8 hi-intensity (foreground and background) |
| 7 | +- **10 text attributes**: Reset, Bold, Faint, Italic, Underline, Blink, Reverse, Concealed, CrossedOut |
| 8 | +- **Color type**: `Color int` - simple integer type |
| 9 | +- **Escape sequence format**: `\x1b[<code>m` where `<code>` is the Color value directly |
| 10 | + |
| 11 | +Current color ranges: |
| 12 | +- Attributes: 0-9 |
| 13 | +- Foreground: 30-37 (standard), 90-97 (hi-intensity) |
| 14 | +- Background: 40-47 (standard), 100-107 (hi-intensity) |
| 15 | + |
| 16 | +## 256-Color ANSI Specification |
| 17 | + |
| 18 | +256-color mode uses different escape sequences: |
| 19 | +- **Foreground 256-color**: `\x1b[38;5;<n>m` where `n` is 0-255 |
| 20 | +- **Background 256-color**: `\x1b[48;5;<n>m` where `n` is 0-255 |
| 21 | + |
| 22 | +The 256-color palette consists of: |
| 23 | +- 0-15: Standard 16 colors (matches current 8 basic + 8 hi-intensity) |
| 24 | +- 16-231: 216 colors arranged in a 6x6x6 RGB cube |
| 25 | +- 232-255: 24 grayscale colors |
| 26 | + |
| 27 | +## Required Changes |
| 28 | + |
| 29 | +### 1. Color Type and Constants |
| 30 | + |
| 31 | +**Option A: Extend existing Color type (Recommended)** |
| 32 | +- Keep `Color int` as-is (backward compatible) |
| 33 | +- Define new constants for 256-color indices |
| 34 | +- Use a reserved range or sentinel values to identify 256-color mode |
| 35 | + |
| 36 | +**Option B: Add new types** |
| 37 | +- Create `Color256` type (breaks existing interfaces) |
| 38 | +- Not recommended as it would require changes throughout codebase |
| 39 | + |
| 40 | +**Recommended approach:** |
| 41 | +```go |
| 42 | +// Reserve a high range for 256-color indices |
| 43 | +// Use negative values or values > 1000 to distinguish 256-color mode |
| 44 | +const ( |
| 45 | + // 256-color foreground: use 1000-1255 range |
| 46 | + Fg256Start Color = 1000 |
| 47 | + Fg256 Color = 1000 // Base for 256-color foreground |
| 48 | + // Helper functions: Fg256Color(n int) Color { return Fg256Start + Color(n) } |
| 49 | + |
| 50 | + // 256-color background: use 2000-2255 range |
| 51 | + Bg256Start Color = 2000 |
| 52 | + Bg256 Color = 2000 // Base for 256-color background |
| 53 | + // Helper functions: Bg256Color(n int) Color { return Bg256Start + Color(n) } |
| 54 | +) |
| 55 | +``` |
| 56 | + |
| 57 | +### 2. EscapeSeq() Method Changes |
| 58 | + |
| 59 | +**Current implementation:** |
| 60 | +```go |
| 61 | +func (c Color) EscapeSeq() string { |
| 62 | + return EscapeStart + strconv.Itoa(int(c)) + EscapeStop |
| 63 | +} |
| 64 | +``` |
| 65 | + |
| 66 | +**Required changes:** |
| 67 | +- Detect if Color is in 256-color range |
| 68 | +- Generate appropriate escape sequence format |
| 69 | +- Handle mixing with regular colors |
| 70 | + |
| 71 | +**New implementation:** |
| 72 | +```go |
| 73 | +func (c Color) EscapeSeq() string { |
| 74 | + // Check if it's a 256-color foreground (1000-1255) |
| 75 | + if c >= Fg256Start && c < Fg256Start+256 { |
| 76 | + colorIndex := int(c - Fg256Start) |
| 77 | + return fmt.Sprintf("%s38;5;%d%s", EscapeStart, colorIndex, EscapeStop) |
| 78 | + } |
| 79 | + // Check if it's a 256-color background (2000-2255) |
| 80 | + if c >= Bg256Start && c < Bg256Start+256 { |
| 81 | + colorIndex := int(c - Bg256Start) |
| 82 | + return fmt.Sprintf("%s48;5;%d%s", EscapeStart, colorIndex, EscapeStop) |
| 83 | + } |
| 84 | + // Regular color (existing behavior) |
| 85 | + return EscapeStart + strconv.Itoa(int(c)) + EscapeStop |
| 86 | +} |
| 87 | +``` |
| 88 | + |
| 89 | +### 3. Colors.EscapeSeq() Method Changes |
| 90 | + |
| 91 | +**Current implementation:** |
| 92 | +- Joins all color codes with semicolons: `\x1b[30;40m` |
| 93 | + |
| 94 | +**Required changes:** |
| 95 | +- Handle mixed regular and 256-color codes |
| 96 | +- 256-color codes need their full sequence (e.g., `38;5;123`) not just the index |
| 97 | +- Need to properly combine sequences |
| 98 | + |
| 99 | +**New implementation:** |
| 100 | +```go |
| 101 | +func (c Colors) EscapeSeq() string { |
| 102 | + if len(c) == 0 { |
| 103 | + return "" |
| 104 | + } |
| 105 | + |
| 106 | + colorsKey := fmt.Sprintf("%#v", c) |
| 107 | + escapeSeq, ok := colorsSeqMap.Load(colorsKey) |
| 108 | + if !ok || escapeSeq == "" { |
| 109 | + var codes []string |
| 110 | + for _, color := range c { |
| 111 | + codes = append(codes, c.colorToCode(color)) |
| 112 | + } |
| 113 | + escapeSeq = EscapeStart + strings.Join(codes, ";") + EscapeStop |
| 114 | + colorsSeqMap.Store(colorsKey, escapeSeq) |
| 115 | + } |
| 116 | + return escapeSeq.(string) |
| 117 | +} |
| 118 | + |
| 119 | +func (c Colors) colorToCode(color Color) string { |
| 120 | + if color >= Fg256Start && color < Fg256Start+256 { |
| 121 | + colorIndex := int(color - Fg256Start) |
| 122 | + return fmt.Sprintf("38;5;%d", colorIndex) |
| 123 | + } |
| 124 | + if color >= Bg256Start && c < Bg256Start+256 { |
| 125 | + colorIndex := int(color - Bg256Start) |
| 126 | + return fmt.Sprintf("48;5;%d", colorIndex) |
| 127 | + } |
| 128 | + return strconv.Itoa(int(color)) |
| 129 | +} |
| 130 | +``` |
| 131 | + |
| 132 | +### 4. CSS Classes Support |
| 133 | + |
| 134 | +**Current implementation:** |
| 135 | +- `colorCSSClassMap` maps Color values to CSS class names |
| 136 | +- Returns empty string for unmapped colors |
| 137 | + |
| 138 | +**Required changes:** |
| 139 | +- Add CSS class mappings for 256 colors OR |
| 140 | +- Generate CSS with RGB values for 256-color indices |
| 141 | +- Option: Use CSS custom properties or inline styles |
| 142 | + |
| 143 | +**Options:** |
| 144 | +1. **Map to CSS classes**: Create 256 entries in `colorCSSClassMap` |
| 145 | + - Pros: Consistent with current approach |
| 146 | + - Cons: Large map, need to define 256 class names |
| 147 | + |
| 148 | +2. **Generate RGB-based CSS**: Convert 256-color index to RGB |
| 149 | + - Pros: More flexible, no large map needed |
| 150 | + - Cons: Different approach from current implementation |
| 151 | + |
| 152 | +3. **Hybrid**: Use CSS classes for standard colors, RGB for 256 colors |
| 153 | + - Pros: Best of both worlds |
| 154 | + - Cons: Inconsistent return format |
| 155 | + |
| 156 | +**Recommended:** |
| 157 | +```go |
| 158 | +func (c Color) CSSClasses() string { |
| 159 | + // Check for 256-color and convert to RGB-based class |
| 160 | + if c >= Fg256Start && c < Fg256Start+256 { |
| 161 | + r, g, b := color256ToRGB(int(c - Fg256Start)) |
| 162 | + return fmt.Sprintf("fg-256-%d-%d-%d", r, g, b) |
| 163 | + } |
| 164 | + if c >= Bg256Start && c < Bg256Start+256 { |
| 165 | + r, g, b := color256ToRGB(int(c - Bg256Start)) |
| 166 | + return fmt.Sprintf("bg-256-%d-%d-%d", r, g, b) |
| 167 | + } |
| 168 | + // Existing behavior |
| 169 | + if class, ok := colorCSSClassMap[c]; ok { |
| 170 | + return class |
| 171 | + } |
| 172 | + return "" |
| 173 | +} |
| 174 | + |
| 175 | +func color256ToRGB(index int) (r, g, b int) { |
| 176 | + if index < 16 { |
| 177 | + // Standard 16 colors - map to predefined RGB |
| 178 | + return standardColorRGB[index] |
| 179 | + } else if index < 232 { |
| 180 | + // 216-color RGB cube |
| 181 | + index -= 16 |
| 182 | + r = (index / 36) * 51 |
| 183 | + g = ((index / 6) % 6) * 51 |
| 184 | + b = (index % 6) * 51 |
| 185 | + } else { |
| 186 | + // 24 grayscale colors |
| 187 | + gray := 8 + (index-232)*10 |
| 188 | + r, g, b = gray, gray, gray |
| 189 | + } |
| 190 | + return |
| 191 | +} |
| 192 | +``` |
| 193 | + |
| 194 | +### 5. Helper Functions |
| 195 | + |
| 196 | +Add convenience functions for creating 256-color values: |
| 197 | + |
| 198 | +```go |
| 199 | +// Fg256Color returns a foreground 256-color Color value |
| 200 | +func Fg256Color(index int) Color { |
| 201 | + if index < 0 || index > 255 { |
| 202 | + return Reset // or panic |
| 203 | + } |
| 204 | + return Fg256Start + Color(index) |
| 205 | +} |
| 206 | + |
| 207 | +// Bg256Color returns a background 256-color Color value |
| 208 | +func Bg256Color(index int) Color { |
| 209 | + if index < 0 || index > 255 { |
| 210 | + return Reset // or panic |
| 211 | + } |
| 212 | + return Bg256Start + Color(index) |
| 213 | +} |
| 214 | + |
| 215 | +// Fg256RGB returns a foreground 256-color from RGB values (0-5 each) |
| 216 | +func Fg256RGB(r, g, b int) Color { |
| 217 | + if r < 0 || r > 5 || g < 0 || g > 5 || b < 0 || b > 5 { |
| 218 | + return Reset |
| 219 | + } |
| 220 | + index := 16 + (r*36 + g*6 + b) |
| 221 | + return Fg256Color(index) |
| 222 | +} |
| 223 | + |
| 224 | +// Bg256RGB returns a background 256-color from RGB values (0-5 each) |
| 225 | +func Bg256RGB(r, g, b int) Color { |
| 226 | + if r < 0 || r > 5 || g < 0 || g > 5 || b < 0 || b > 5 { |
| 227 | + return Reset |
| 228 | + } |
| 229 | + index := 16 + (r*36 + g*6 + b) |
| 230 | + return Bg256Color(index) |
| 231 | +} |
| 232 | +``` |
| 233 | + |
| 234 | +### 6. Testing Requirements |
| 235 | + |
| 236 | +- Test 256-color escape sequence generation |
| 237 | +- Test mixing 256-colors with regular colors |
| 238 | +- Test CSS class generation for 256 colors |
| 239 | +- Test edge cases (invalid indices, boundary values) |
| 240 | +- Test backward compatibility (existing colors still work) |
| 241 | + |
| 242 | +### 7. Documentation Updates |
| 243 | + |
| 244 | +- Update README.md to document 256-color support |
| 245 | +- Add examples showing 256-color usage |
| 246 | +- Document the color index ranges and RGB cube structure |
| 247 | + |
| 248 | +## Backward Compatibility |
| 249 | + |
| 250 | +✅ **Maintained**: All existing code will continue to work unchanged |
| 251 | +- Existing Color constants remain valid |
| 252 | +- Existing EscapeSeq() behavior preserved for standard colors |
| 253 | +- No breaking changes to public interfaces |
| 254 | + |
| 255 | +## Implementation Complexity |
| 256 | + |
| 257 | +**Low to Medium**: |
| 258 | +- Core logic is straightforward |
| 259 | +- Main complexity is in CSS mapping strategy |
| 260 | +- Testing required for edge cases |
| 261 | +- Need to ensure escape sequence parser handles 256-color codes correctly |
| 262 | + |
| 263 | +## Potential Issues |
| 264 | + |
| 265 | +1. **Escape Sequence Parser**: The `EscSeqParser` in `escape_seq_parser.go` currently parses sequences by splitting on semicolons and storing individual codes. For 256-color sequences like `38;5;123`, it would store codes `38`, `5`, and `123` separately. This is **not a blocker** for basic 256-color support, but if you want the parser to recognize 256-color sequences as semantic units (e.g., for proper reset handling), it would need enhancement. For now, the parser will work but won't treat `38;5;123` as a single color code. |
| 266 | + |
| 267 | +2. **Color Validation**: Need to validate 256-color indices are in valid range (0-255) in helper functions |
| 268 | + |
| 269 | +3. **CSS Support**: HTML rendering may need additional CSS definitions for 256-color classes. The CSS class generation strategy needs to be decided (see section 4 above). |
| 270 | + |
| 271 | +4. **Terminal Support**: Not all terminals support 256 colors - may need detection logic (though current `areANSICodesSupported()` might handle this). The escape sequences will be generated correctly regardless, and unsupported terminals will simply ignore them. |
| 272 | + |
| 273 | +## Summary |
| 274 | + |
| 275 | +The changes required are **minimal and backward-compatible**: |
| 276 | +1. Extend `EscapeSeq()` to detect and format 256-color codes |
| 277 | +2. Update `Colors.EscapeSeq()` to handle mixed color types |
| 278 | +3. Add helper functions for creating 256-color values |
| 279 | +4. Extend CSS class support (strategy TBD) |
| 280 | +5. Add tests and documentation |
| 281 | + |
| 282 | +The existing `Color int` type can be reused without breaking changes by using reserved value ranges to distinguish 256-color mode from standard colors. |
| 283 | + |
| 284 | +## Quick Reference: Implementation Checklist |
| 285 | + |
| 286 | +- [ ] Define reserved value ranges for 256-color indices (e.g., 1000-1255 for foreground, 2000-2255 for background) |
| 287 | +- [ ] Update `Color.EscapeSeq()` to detect 256-color range and generate `38;5;n` or `48;5;n` format |
| 288 | +- [ ] Update `Colors.EscapeSeq()` to handle mixed regular and 256-color codes |
| 289 | +- [ ] Add helper functions: `Fg256Color()`, `Bg256Color()`, `Fg256RGB()`, `Bg256RGB()` |
| 290 | +- [ ] Implement `color256ToRGB()` conversion function |
| 291 | +- [ ] Update `Color.CSSClasses()` to handle 256-color indices (choose strategy: map, RGB-based, or hybrid) |
| 292 | +- [ ] Update `Colors.CSSClasses()` to handle 256-color indices |
| 293 | +- [ ] Add comprehensive tests for 256-color functionality |
| 294 | +- [ ] Update documentation (README.md, examples) |
| 295 | +- [ ] Verify backward compatibility with existing code |
| 296 | + |
0 commit comments