Skip to content

Commit ccb1fc9

Browse files
committed
support output of github.com/fatih/color
1 parent da20de3 commit ccb1fc9

2 files changed

Lines changed: 183 additions & 2 deletions

File tree

render.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,17 @@ var TableConfig = struct {
3333
TabWriterTruncate: false,
3434
}
3535

36+
// ignoredPatterns matches ANSI escape sequences produced by fatih/color and similar libraries.
37+
// Covers: single codes (\033[31m), multiple codes (\033[1;31m), 256-color (\033[38;5;196m),
38+
// 24-bit RGB (\033[38;2;255;128;0m), and reset sequences (\033[0m, \033[22m, etc.)
3639
var ignoredPatterns = []*regexp.Regexp{
37-
regexp.MustCompile("\033\\[\\d+;\\d+;\\d+m"),
40+
// matches any SGR sequence with at least one non-zero digit (colors, bold, etc., but not pure \033[0m)
41+
regexp.MustCompile("\033\\[[0-9;]*[1-9][0-9;]*m"),
42+
// matches reset sequence specifically
3843
regexp.MustCompile("\033\\[0m"),
3944
}
4045

41-
var ignoredPattern = regexp.MustCompile("\033\\[\\d+;\\d+;\\d+m|\033\\[0m")
46+
var ignoredPattern = regexp.MustCompile("\033\\[\\d+(;\\d+)*m")
4247

4348
type Table struct {
4449
Headers Row

render_test.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,182 @@ func TestColoredString(t *testing.T) {
383383
assert.Equal(t, expected, table.String())
384384
}
385385

386+
// TestRuneLenWithFatihColorFormats tests that runeLen correctly handles
387+
// all ANSI escape sequences produced by github.com/fatih/color library.
388+
// This prevents regression when handling colored output.
389+
func TestRuneLenWithFatihColorFormats(t *testing.T) {
390+
tests := []struct {
391+
name string
392+
input string
393+
expected int
394+
}{
395+
// Basic colors (single number): \x1b[31m (red), \x1b[32m (green), etc.
396+
{
397+
name: "fatih/color basic red",
398+
input: "\x1b[31mhello\x1b[0m",
399+
expected: 5,
400+
},
401+
{
402+
name: "fatih/color basic green",
403+
input: "\x1b[32mworld\x1b[0m",
404+
expected: 5,
405+
},
406+
// Bold attribute (single number): \x1b[1m
407+
{
408+
name: "fatih/color bold",
409+
input: "\x1b[1mbold text\x1b[0m",
410+
expected: 9,
411+
},
412+
// Hi-intensity colors: \x1b[90m to \x1b[97m
413+
{
414+
name: "fatih/color hi-intensity",
415+
input: "\x1b[91mhi-red\x1b[0m",
416+
expected: 6,
417+
},
418+
// Combined attributes (two numbers): \x1b[1;31m (bold red)
419+
{
420+
name: "fatih/color bold+red",
421+
input: "\x1b[1;31mbold red\x1b[0m",
422+
expected: 8,
423+
},
424+
// Background colors: \x1b[41m (red bg)
425+
{
426+
name: "fatih/color background",
427+
input: "\x1b[41mred bg\x1b[0m",
428+
expected: 6,
429+
},
430+
// Foreground + Background: \x1b[31;47m
431+
{
432+
name: "fatih/color fg+bg",
433+
input: "\x1b[31;47mred on white\x1b[0m",
434+
expected: 12,
435+
},
436+
// 256-color mode: \x1b[38;5;196m (foreground) or \x1b[48;5;196m (background)
437+
{
438+
name: "fatih/color 256-color foreground",
439+
input: "\x1b[38;5;196m256 color\x1b[0m",
440+
expected: 9,
441+
},
442+
{
443+
name: "fatih/color 256-color background",
444+
input: "\x1b[48;5;21mblue bg\x1b[0m",
445+
expected: 7,
446+
},
447+
// 24-bit RGB: \x1b[38;2;255;128;0m (foreground orange)
448+
{
449+
name: "fatih/color RGB foreground",
450+
input: "\x1b[38;2;255;128;0morange\x1b[0m",
451+
expected: 6,
452+
},
453+
{
454+
name: "fatih/color RGB background",
455+
input: "\x1b[48;2;0;0;255mblue bg\x1b[0m",
456+
expected: 7,
457+
},
458+
// Mixed RGB foreground + background
459+
{
460+
name: "fatih/color RGB fg+bg",
461+
input: "\x1b[38;2;255;255;255m\x1b[48;2;0;0;0mwhite on black\x1b[0m",
462+
expected: 14,
463+
},
464+
// Underline and other attributes: \x1b[4m
465+
{
466+
name: "fatih/color underline",
467+
input: "\x1b[4munderlined\x1b[0m",
468+
expected: 10,
469+
},
470+
// Nested/chained attributes
471+
{
472+
name: "fatih/color nested styles",
473+
input: "\x1b[1m\x1b[4m\x1b[31mbold underline red\x1b[0m",
474+
expected: 18,
475+
},
476+
// Specific reset codes: \x1b[22m (reset bold), \x1b[24m (reset underline)
477+
{
478+
name: "fatih/color specific resets",
479+
input: "\x1b[1mbold\x1b[22m normal\x1b[0m",
480+
expected: 11,
481+
},
482+
// Multiple resets in sequence
483+
{
484+
name: "fatih/color multiple resets",
485+
input: "\x1b[1m\x1b[4mtext\x1b[22m\x1b[24m\x1b[0m",
486+
expected: 4,
487+
},
488+
// Empty string with just escape codes
489+
{
490+
name: "fatih/color only escapes",
491+
input: "\x1b[31m\x1b[0m",
492+
expected: 0,
493+
},
494+
// Real-world example: colored status output
495+
{
496+
name: "fatih/color real status",
497+
input: "\x1b[32m✓\x1b[0m \x1b[1mSuccess\x1b[0m",
498+
expected: 9, // "✓ Success" = 1 + 1 + 7
499+
},
500+
}
501+
502+
for _, tt := range tests {
503+
t.Run(tt.name, func(t *testing.T) {
504+
got := runeLen(tt.input)
505+
assert.Equal(t, tt.expected, got, "runeLen(%q) = %d, want %d", tt.input, got, tt.expected)
506+
})
507+
}
508+
}
509+
510+
// TestTableColumnWidthWithFatihColor ensures table column width calculation
511+
// works correctly with various fatih/color escape sequences.
512+
func TestTableColumnWidthWithFatihColor(t *testing.T) {
513+
tests := []struct {
514+
name string
515+
rows []Row
516+
expectedSizes []int
517+
}{
518+
{
519+
name: "basic colors",
520+
rows: []Row{
521+
{"\x1b[31mred\x1b[0m", "normal"},
522+
{"plain", "\x1b[32mgreen\x1b[0m"},
523+
},
524+
expectedSizes: []int{5, 6},
525+
},
526+
{
527+
name: "256 colors",
528+
rows: []Row{
529+
{"\x1b[38;5;196mcolor256\x1b[0m", "test"},
530+
},
531+
expectedSizes: []int{8, 4},
532+
},
533+
{
534+
name: "RGB colors",
535+
rows: []Row{
536+
{"\x1b[38;2;255;128;0mRGB orange\x1b[0m", "data"},
537+
},
538+
expectedSizes: []int{10, 4},
539+
},
540+
{
541+
name: "mixed styles",
542+
rows: []Row{
543+
{"\x1b[1;31mbold red\x1b[0m", "\x1b[4munderline\x1b[0m"},
544+
{"\x1b[38;2;0;255;0mRGB green\x1b[0m", "plain"},
545+
},
546+
expectedSizes: []int{9, 9},
547+
},
548+
}
549+
550+
for _, tt := range tests {
551+
t.Run(tt.name, func(t *testing.T) {
552+
table := NewTable()
553+
for _, row := range tt.rows {
554+
table.AddRow(row)
555+
}
556+
sizes := table.columnsSize()
557+
assert.Equal(t, tt.expectedSizes, sizes)
558+
})
559+
}
560+
}
561+
386562
func TestResizeLargestColumnOnWhitespace(t *testing.T) {
387563
tb := NewTable()
388564
tb.AddRow(Row{"1", "abc def ghi jk"})

0 commit comments

Comments
 (0)