Skip to content

Commit bc95ab4

Browse files
committed
text: support 256 colors with same interface
1 parent b9f2816 commit bc95ab4

File tree

3 files changed

+776
-69
lines changed

3 files changed

+776
-69
lines changed

text/256_COLOR_ANALYSIS.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
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

Comments
 (0)