feat(terminal): detect ANSI-16 and monochrome terminals
Classify TERM values without a truecolor signal: dumb, vt52/100/102/220 and -m/-mono names are monochrome; linux, ansi, cons25 and -16color/-color names are 16 colors. Add terminalcap.ParseColorMode for explicit overrides.
- commit
- 9a421ca553513e4f85d18343a360bf4e7369b92b
- author
- brice · · 2026-10-03 12:34 UTC
- committer
- brice · · 2026-10-03 14:14 UTC
- parent
- 4b57b07
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +119 -2
M internal/domain/terminalcap/capabilities.go +52 -2
| @@ -1,7 +1,10 @@ | |||
| 1 | 1 | // Package terminalcap owns pure terminal capability values and detection policy. | |
| 2 | 2 | package terminalcap | |
| 3 | 3 | ||
| 4 | - | import "strings" | |
| 4 | + | import ( | |
| 5 | + | "fmt" | |
| 6 | + | "strings" | |
| 7 | + | ) | |
| 5 | 8 | ||
| 6 | 9 | // ColorMode is the color output mode selected for one attachment. | |
| 7 | 10 | type ColorMode uint8 | |
| @@ -111,12 +114,59 @@ func Detect(env []string) Capabilities { | |||
| 111 | 114 | if caps.Color.Source == SourceDeclared { | |
| 112 | 115 | return caps | |
| 113 | 116 | } | |
| 114 | - | if strings.Contains(term, "256color") || term == "dumb" { | |
| 117 | + | if mode, ok := termColorMode(term); ok { | |
| 118 | + | caps.Color.Mode = mode | |
| 115 | 119 | caps.Color.Source = SourceDeclared | |
| 116 | 120 | } | |
| 117 | 121 | return caps | |
| 118 | 122 | } | |
| 119 | 123 | ||
| 124 | + | // termColorMode classifies a lowercase TERM value that carries no truecolor | |
| 125 | + | // signal. It is deliberately conservative: only well-known terminfo names map | |
| 126 | + | // to a constrained mode, and anything else reports ok=false so the caller keeps | |
| 127 | + | // its unverified 256-color default. The rules apply in this order: | |
| 128 | + | // | |
| 129 | + | // - monochrome: dumb, vt52, vt100, vt102, vt220, or a name ending in -m or -mono; | |
| 130 | + | // - 256 colors: any name containing "256color"; | |
| 131 | + | // - 16 colors: linux, ansi, cons25, or a name ending in -16color or -color. | |
| 132 | + | func termColorMode(term string) (ColorMode, bool) { | |
| 133 | + | switch term { | |
| 134 | + | case "dumb", "vt52", "vt100", "vt102", "vt220": | |
| 135 | + | return Monochrome, true | |
| 136 | + | case "linux", "ansi", "cons25": | |
| 137 | + | return ANSI16, true | |
| 138 | + | } | |
| 139 | + | switch { | |
| 140 | + | case strings.HasSuffix(term, "-m"), strings.HasSuffix(term, "-mono"): | |
| 141 | + | return Monochrome, true | |
| 142 | + | case strings.Contains(term, "256color"): | |
| 143 | + | return ANSI256, true | |
| 144 | + | case strings.HasSuffix(term, "-16color"), strings.HasSuffix(term, "-color"): | |
| 145 | + | return ANSI16, true | |
| 146 | + | } | |
| 147 | + | return 0, false | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | // ParseColorMode parses a user color override: auto, truecolor, 256, 16, or | |
| 151 | + | // mono, case-insensitively and ignoring surrounding space. An empty value is | |
| 152 | + | // auto. When auto is true the caller must use detection and mode is | |
| 153 | + | // meaningless; an unknown value returns an error. | |
| 154 | + | func ParseColorMode(value string) (mode ColorMode, auto bool, err error) { | |
| 155 | + | switch strings.ToLower(strings.TrimSpace(value)) { | |
| 156 | + | case "", "auto": | |
| 157 | + | return TrueColor, true, nil | |
| 158 | + | case "truecolor": | |
| 159 | + | return TrueColor, false, nil | |
| 160 | + | case "256": | |
| 161 | + | return ANSI256, false, nil | |
| 162 | + | case "16": | |
| 163 | + | return ANSI16, false, nil | |
| 164 | + | case "mono": | |
| 165 | + | return Monochrome, false, nil | |
| 166 | + | } | |
| 167 | + | return TrueColor, false, fmt.Errorf("invalid color mode %q (want auto, truecolor, 256, 16, or mono)", value) | |
| 168 | + | } | |
| 169 | + | ||
| 120 | 170 | func environmentValues(env []string) map[string]string { | |
| 121 | 171 | values := make(map[string]string, len(env)) | |
| 122 | 172 | for _, entry := range env { | |
| @@ -1,7 +1,10 @@ | |||||
| 1 | // Package terminalcap owns pure terminal capability values and detection policy. | 1 | // Package terminalcap owns pure terminal capability values and detection policy. | ||
| 2 | package terminalcap | 2 | package terminalcap | ||
| 3 | 3 | ||||
| 4 | - | import "strings" | 4 | + | import ( |
| 5 | + | "fmt" | |||
| 6 | + | "strings" | |||
| 7 | + | ) | |||
| 5 | 8 | ||||
| 6 | // ColorMode is the color output mode selected for one attachment. | 9 | // ColorMode is the color output mode selected for one attachment. | ||
| 7 | type ColorMode uint8 | 10 | type ColorMode uint8 | ||
| @@ -111,12 +114,59 @@ func Detect(env []string) Capabilities { | |||||
| 111 | if caps.Color.Source == SourceDeclared { | 114 | if caps.Color.Source == SourceDeclared { | ||
| 112 | return caps | 115 | return caps | ||
| 113 | } | 116 | } | ||
| 114 | - | if strings.Contains(term, "256color") || term == "dumb" { | 117 | + | if mode, ok := termColorMode(term); ok { |
| 118 | + | caps.Color.Mode = mode | |||
| 115 | caps.Color.Source = SourceDeclared | 119 | caps.Color.Source = SourceDeclared | ||
| 116 | } | 120 | } | ||
| 117 | return caps | 121 | return caps | ||
| 118 | } | 122 | } | ||
| 119 | 123 | ||||
| 124 | + | // termColorMode classifies a lowercase TERM value that carries no truecolor | |||
| 125 | + | // signal. It is deliberately conservative: only well-known terminfo names map | |||
| 126 | + | // to a constrained mode, and anything else reports ok=false so the caller keeps | |||
| 127 | + | // its unverified 256-color default. The rules apply in this order: | |||
| 128 | + | // | |||
| 129 | + | // - monochrome: dumb, vt52, vt100, vt102, vt220, or a name ending in -m or -mono; | |||
| 130 | + | // - 256 colors: any name containing "256color"; | |||
| 131 | + | // - 16 colors: linux, ansi, cons25, or a name ending in -16color or -color. | |||
| 132 | + | func termColorMode(term string) (ColorMode, bool) { | |||
| 133 | + | switch term { | |||
| 134 | + | case "dumb", "vt52", "vt100", "vt102", "vt220": | |||
| 135 | + | return Monochrome, true | |||
| 136 | + | case "linux", "ansi", "cons25": | |||
| 137 | + | return ANSI16, true | |||
| 138 | + | } | |||
| 139 | + | switch { | |||
| 140 | + | case strings.HasSuffix(term, "-m"), strings.HasSuffix(term, "-mono"): | |||
| 141 | + | return Monochrome, true | |||
| 142 | + | case strings.Contains(term, "256color"): | |||
| 143 | + | return ANSI256, true | |||
| 144 | + | case strings.HasSuffix(term, "-16color"), strings.HasSuffix(term, "-color"): | |||
| 145 | + | return ANSI16, true | |||
| 146 | + | } | |||
| 147 | + | return 0, false | |||
| 148 | + | } | |||
| 149 | + | ||||
| 150 | + | // ParseColorMode parses a user color override: auto, truecolor, 256, 16, or | |||
| 151 | + | // mono, case-insensitively and ignoring surrounding space. An empty value is | |||
| 152 | + | // auto. When auto is true the caller must use detection and mode is | |||
| 153 | + | // meaningless; an unknown value returns an error. | |||
| 154 | + | func ParseColorMode(value string) (mode ColorMode, auto bool, err error) { | |||
| 155 | + | switch strings.ToLower(strings.TrimSpace(value)) { | |||
| 156 | + | case "", "auto": | |||
| 157 | + | return TrueColor, true, nil | |||
| 158 | + | case "truecolor": | |||
| 159 | + | return TrueColor, false, nil | |||
| 160 | + | case "256": | |||
| 161 | + | return ANSI256, false, nil | |||
| 162 | + | case "16": | |||
| 163 | + | return ANSI16, false, nil | |||
| 164 | + | case "mono": | |||
| 165 | + | return Monochrome, false, nil | |||
| 166 | + | } | |||
| 167 | + | return TrueColor, false, fmt.Errorf("invalid color mode %q (want auto, truecolor, 256, 16, or mono)", value) | |||
| 168 | + | } | |||
| 169 | + | ||||
| 120 | func environmentValues(env []string) map[string]string { | 170 | func environmentValues(env []string) map[string]string { | ||
| 121 | values := make(map[string]string, len(env)) | 171 | values := make(map[string]string, len(env)) | ||
| 122 | for _, entry := range env { | 172 | for _, entry := range env { | ||
M internal/domain/terminalcap/capabilities_test.go +67 -0
| @@ -92,6 +92,32 @@ func TestDetect(t *testing.T) { | |||
| 92 | 92 | {name: "kitty environment with declared truecolor remains color-only", env: []string{"TERM=xterm-kitty", "COLORTERM=truecolor", "KITTY_WINDOW_ID=1"}, wantMode: TrueColor, wantSource: SourceDeclared, wantApp: ApplicationKitty}, | |
| 93 | 93 | {name: "256 color terminal remains a constrained attachment", env: []string{"TERM=xterm-256color"}, wantMode: ANSI256, wantSource: SourceDeclared}, | |
| 94 | 94 | {name: "unknown terminal is conservatively indexed", env: []string{"TERM=unknown"}, wantMode: ANSI256, wantSource: SourceUnknown}, | |
| 95 | + | {name: "empty TERM is unknown", env: nil, wantMode: ANSI256, wantSource: SourceUnknown}, | |
| 96 | + | {name: "TERM=screen is unknown", env: []string{"TERM=screen"}, wantMode: ANSI256, wantSource: SourceUnknown}, | |
| 97 | + | {name: "TERM=xterm is unknown", env: []string{"TERM=xterm"}, wantMode: ANSI256, wantSource: SourceUnknown}, | |
| 98 | + | {name: "screen-256color", env: []string{"TERM=screen-256color"}, wantMode: ANSI256, wantSource: SourceDeclared}, | |
| 99 | + | {name: "tmux-256color", env: []string{"TERM=tmux-256color"}, wantMode: ANSI256, wantSource: SourceDeclared}, | |
| 100 | + | {name: "TERM is case-insensitive and trimmed", env: []string{"TERM= LINUX "}, wantMode: ANSI16, wantSource: SourceDeclared}, | |
| 101 | + | {name: "dumb is monochrome", env: []string{"TERM=dumb"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |
| 102 | + | {name: "vt52 is monochrome", env: []string{"TERM=vt52"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |
| 103 | + | {name: "vt100 is monochrome", env: []string{"TERM=vt100"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |
| 104 | + | {name: "vt102 is monochrome", env: []string{"TERM=vt102"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |
| 105 | + | {name: "vt220 is monochrome", env: []string{"TERM=vt220"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |
| 106 | + | {name: "-mono suffix is monochrome", env: []string{"TERM=xterm-mono"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |
| 107 | + | {name: "-m suffix is monochrome", env: []string{"TERM=rxvt-m"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |
| 108 | + | {name: "vt320 is not assumed monochrome", env: []string{"TERM=vt320"}, wantMode: ANSI256, wantSource: SourceUnknown}, | |
| 109 | + | {name: "linux console is 16 colors", env: []string{"TERM=linux"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |
| 110 | + | {name: "ansi is 16 colors", env: []string{"TERM=ansi"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |
| 111 | + | {name: "cons25 is 16 colors", env: []string{"TERM=cons25"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |
| 112 | + | {name: "-16color suffix", env: []string{"TERM=xterm-16color"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |
| 113 | + | {name: "-color suffix", env: []string{"TERM=xterm-color"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |
| 114 | + | {name: "screen-color suffix", env: []string{"TERM=screen-color"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |
| 115 | + | {name: "256color is not the -color suffix", env: []string{"TERM=xterm-256color"}, wantMode: ANSI256, wantSource: SourceDeclared}, | |
| 116 | + | {name: "COLORTERM truecolor beats linux", env: []string{"TERM=linux", "COLORTERM=truecolor"}, wantMode: TrueColor, wantSource: SourceDeclared}, | |
| 117 | + | {name: "COLORTERM 24bit beats dumb", env: []string{"TERM=dumb", "COLORTERM=24bit"}, wantMode: TrueColor, wantSource: SourceDeclared}, | |
| 118 | + | {name: "direct terminfo beats mono suffix", env: []string{"TERM=foot-direct"}, wantMode: TrueColor, wantSource: SourceDeclared}, | |
| 119 | + | {name: "unrelated COLORTERM does not change TERM rules", env: []string{"TERM=linux", "COLORTERM=yes"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |
| 120 | + | {name: "kitty identity beats TERM rules", env: []string{"TERM=xterm-kitty", "KITTY_WINDOW_ID=1"}, wantMode: TrueColor, wantSource: SourceHeuristic, wantApp: ApplicationKitty}, | |
| 95 | 121 | {name: "kitty environment behind a multiplexer is not graphics evidence", env: []string{"TERM=tmux-256color", "KITTY_WINDOW_ID=1"}, wantMode: ANSI256, wantSource: SourceDeclared, wantApp: ApplicationKitty}, | |
| 96 | 122 | } | |
| 97 | 123 | ||
| @@ -105,3 +131,44 @@ func TestDetect(t *testing.T) { | |||
| 105 | 131 | }) | |
| 106 | 132 | } | |
| 107 | 133 | } | |
| 134 | + | ||
| 135 | + | func TestParseColorMode(t *testing.T) { | |
| 136 | + | tests := []struct { | |
| 137 | + | name string | |
| 138 | + | in string | |
| 139 | + | wantMode ColorMode | |
| 140 | + | wantAuto bool | |
| 141 | + | wantErr bool | |
| 142 | + | }{ | |
| 143 | + | {name: "empty is auto", in: "", wantAuto: true}, | |
| 144 | + | {name: "blank is auto", in: " \t", wantAuto: true}, | |
| 145 | + | {name: "auto", in: "auto", wantAuto: true}, | |
| 146 | + | {name: "auto is case-insensitive", in: " AUTO ", wantAuto: true}, | |
| 147 | + | {name: "truecolor", in: "truecolor", wantMode: TrueColor}, | |
| 148 | + | {name: "truecolor mixed case", in: "TrueColor", wantMode: TrueColor}, | |
| 149 | + | {name: "256", in: "256", wantMode: ANSI256}, | |
| 150 | + | {name: "16", in: " 16 ", wantMode: ANSI16}, | |
| 151 | + | {name: "mono", in: "mono", wantMode: Monochrome}, | |
| 152 | + | {name: "mono upper case", in: "MONO", wantMode: Monochrome}, | |
| 153 | + | {name: "24bit is not accepted", in: "24bit", wantErr: true}, | |
| 154 | + | {name: "256color is not accepted", in: "256color", wantErr: true}, | |
| 155 | + | {name: "number out of set", in: "8", wantErr: true}, | |
| 156 | + | {name: "monochrome spelled out is not accepted", in: "monochrome", wantErr: true}, | |
| 157 | + | {name: "garbage", in: "rainbow", wantErr: true}, | |
| 158 | + | } | |
| 159 | + | for _, tt := range tests { | |
| 160 | + | t.Run(tt.name, func(t *testing.T) { | |
| 161 | + | mode, auto, err := ParseColorMode(tt.in) | |
| 162 | + | if tt.wantErr { | |
| 163 | + | require.Error(t, err) | |
| 164 | + | require.False(t, auto) | |
| 165 | + | return | |
| 166 | + | } | |
| 167 | + | require.NoError(t, err) | |
| 168 | + | require.Equal(t, tt.wantAuto, auto) | |
| 169 | + | if !auto { | |
| 170 | + | require.Equal(t, tt.wantMode, mode) | |
| 171 | + | } | |
| 172 | + | }) | |
| 173 | + | } | |
| 174 | + | } | |
| @@ -92,6 +92,32 @@ func TestDetect(t *testing.T) { | |||||
| 92 | {name: "kitty environment with declared truecolor remains color-only", env: []string{"TERM=xterm-kitty", "COLORTERM=truecolor", "KITTY_WINDOW_ID=1"}, wantMode: TrueColor, wantSource: SourceDeclared, wantApp: ApplicationKitty}, | 92 | {name: "kitty environment with declared truecolor remains color-only", env: []string{"TERM=xterm-kitty", "COLORTERM=truecolor", "KITTY_WINDOW_ID=1"}, wantMode: TrueColor, wantSource: SourceDeclared, wantApp: ApplicationKitty}, | ||
| 93 | {name: "256 color terminal remains a constrained attachment", env: []string{"TERM=xterm-256color"}, wantMode: ANSI256, wantSource: SourceDeclared}, | 93 | {name: "256 color terminal remains a constrained attachment", env: []string{"TERM=xterm-256color"}, wantMode: ANSI256, wantSource: SourceDeclared}, | ||
| 94 | {name: "unknown terminal is conservatively indexed", env: []string{"TERM=unknown"}, wantMode: ANSI256, wantSource: SourceUnknown}, | 94 | {name: "unknown terminal is conservatively indexed", env: []string{"TERM=unknown"}, wantMode: ANSI256, wantSource: SourceUnknown}, | ||
| 95 | + | {name: "empty TERM is unknown", env: nil, wantMode: ANSI256, wantSource: SourceUnknown}, | |||
| 96 | + | {name: "TERM=screen is unknown", env: []string{"TERM=screen"}, wantMode: ANSI256, wantSource: SourceUnknown}, | |||
| 97 | + | {name: "TERM=xterm is unknown", env: []string{"TERM=xterm"}, wantMode: ANSI256, wantSource: SourceUnknown}, | |||
| 98 | + | {name: "screen-256color", env: []string{"TERM=screen-256color"}, wantMode: ANSI256, wantSource: SourceDeclared}, | |||
| 99 | + | {name: "tmux-256color", env: []string{"TERM=tmux-256color"}, wantMode: ANSI256, wantSource: SourceDeclared}, | |||
| 100 | + | {name: "TERM is case-insensitive and trimmed", env: []string{"TERM= LINUX "}, wantMode: ANSI16, wantSource: SourceDeclared}, | |||
| 101 | + | {name: "dumb is monochrome", env: []string{"TERM=dumb"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |||
| 102 | + | {name: "vt52 is monochrome", env: []string{"TERM=vt52"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |||
| 103 | + | {name: "vt100 is monochrome", env: []string{"TERM=vt100"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |||
| 104 | + | {name: "vt102 is monochrome", env: []string{"TERM=vt102"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |||
| 105 | + | {name: "vt220 is monochrome", env: []string{"TERM=vt220"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |||
| 106 | + | {name: "-mono suffix is monochrome", env: []string{"TERM=xterm-mono"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |||
| 107 | + | {name: "-m suffix is monochrome", env: []string{"TERM=rxvt-m"}, wantMode: Monochrome, wantSource: SourceDeclared}, | |||
| 108 | + | {name: "vt320 is not assumed monochrome", env: []string{"TERM=vt320"}, wantMode: ANSI256, wantSource: SourceUnknown}, | |||
| 109 | + | {name: "linux console is 16 colors", env: []string{"TERM=linux"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |||
| 110 | + | {name: "ansi is 16 colors", env: []string{"TERM=ansi"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |||
| 111 | + | {name: "cons25 is 16 colors", env: []string{"TERM=cons25"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |||
| 112 | + | {name: "-16color suffix", env: []string{"TERM=xterm-16color"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |||
| 113 | + | {name: "-color suffix", env: []string{"TERM=xterm-color"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |||
| 114 | + | {name: "screen-color suffix", env: []string{"TERM=screen-color"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |||
| 115 | + | {name: "256color is not the -color suffix", env: []string{"TERM=xterm-256color"}, wantMode: ANSI256, wantSource: SourceDeclared}, | |||
| 116 | + | {name: "COLORTERM truecolor beats linux", env: []string{"TERM=linux", "COLORTERM=truecolor"}, wantMode: TrueColor, wantSource: SourceDeclared}, | |||
| 117 | + | {name: "COLORTERM 24bit beats dumb", env: []string{"TERM=dumb", "COLORTERM=24bit"}, wantMode: TrueColor, wantSource: SourceDeclared}, | |||
| 118 | + | {name: "direct terminfo beats mono suffix", env: []string{"TERM=foot-direct"}, wantMode: TrueColor, wantSource: SourceDeclared}, | |||
| 119 | + | {name: "unrelated COLORTERM does not change TERM rules", env: []string{"TERM=linux", "COLORTERM=yes"}, wantMode: ANSI16, wantSource: SourceDeclared}, | |||
| 120 | + | {name: "kitty identity beats TERM rules", env: []string{"TERM=xterm-kitty", "KITTY_WINDOW_ID=1"}, wantMode: TrueColor, wantSource: SourceHeuristic, wantApp: ApplicationKitty}, | |||
| 95 | {name: "kitty environment behind a multiplexer is not graphics evidence", env: []string{"TERM=tmux-256color", "KITTY_WINDOW_ID=1"}, wantMode: ANSI256, wantSource: SourceDeclared, wantApp: ApplicationKitty}, | 121 | {name: "kitty environment behind a multiplexer is not graphics evidence", env: []string{"TERM=tmux-256color", "KITTY_WINDOW_ID=1"}, wantMode: ANSI256, wantSource: SourceDeclared, wantApp: ApplicationKitty}, | ||
| 96 | } | 122 | } | ||
| 97 | 123 | ||||
| @@ -105,3 +131,44 @@ func TestDetect(t *testing.T) { | |||||
| 105 | }) | 131 | }) | ||
| 106 | } | 132 | } | ||
| 107 | } | 133 | } | ||
| 134 | + | ||||
| 135 | + | func TestParseColorMode(t *testing.T) { | |||
| 136 | + | tests := []struct { | |||
| 137 | + | name string | |||
| 138 | + | in string | |||
| 139 | + | wantMode ColorMode | |||
| 140 | + | wantAuto bool | |||
| 141 | + | wantErr bool | |||
| 142 | + | }{ | |||
| 143 | + | {name: "empty is auto", in: "", wantAuto: true}, | |||
| 144 | + | {name: "blank is auto", in: " \t", wantAuto: true}, | |||
| 145 | + | {name: "auto", in: "auto", wantAuto: true}, | |||
| 146 | + | {name: "auto is case-insensitive", in: " AUTO ", wantAuto: true}, | |||
| 147 | + | {name: "truecolor", in: "truecolor", wantMode: TrueColor}, | |||
| 148 | + | {name: "truecolor mixed case", in: "TrueColor", wantMode: TrueColor}, | |||
| 149 | + | {name: "256", in: "256", wantMode: ANSI256}, | |||
| 150 | + | {name: "16", in: " 16 ", wantMode: ANSI16}, | |||
| 151 | + | {name: "mono", in: "mono", wantMode: Monochrome}, | |||
| 152 | + | {name: "mono upper case", in: "MONO", wantMode: Monochrome}, | |||
| 153 | + | {name: "24bit is not accepted", in: "24bit", wantErr: true}, | |||
| 154 | + | {name: "256color is not accepted", in: "256color", wantErr: true}, | |||
| 155 | + | {name: "number out of set", in: "8", wantErr: true}, | |||
| 156 | + | {name: "monochrome spelled out is not accepted", in: "monochrome", wantErr: true}, | |||
| 157 | + | {name: "garbage", in: "rainbow", wantErr: true}, | |||
| 158 | + | } | |||
| 159 | + | for _, tt := range tests { | |||
| 160 | + | t.Run(tt.name, func(t *testing.T) { | |||
| 161 | + | mode, auto, err := ParseColorMode(tt.in) | |||
| 162 | + | if tt.wantErr { | |||
| 163 | + | require.Error(t, err) | |||
| 164 | + | require.False(t, auto) | |||
| 165 | + | return | |||
| 166 | + | } | |||
| 167 | + | require.NoError(t, err) | |||
| 168 | + | require.Equal(t, tt.wantAuto, auto) | |||
| 169 | + | if !auto { | |||
| 170 | + | require.Equal(t, tt.wantMode, mode) | |||
| 171 | + | } | |||
| 172 | + | }) | |||
| 173 | + | } | |||
| 174 | + | } | |||