Merge pull request #27 from bnema/codex/support-colon-sgr-colors
[codex] support colon SGR colors
2 files · +162 -7
M pkg/vt/screen.go +55 -7
| @@ -952,7 +952,7 @@ func (s *Screen) applySGR(params string) { | |||
| 952 | 952 | s.Style = renderer.DefaultStyle() | |
| 953 | 953 | return | |
| 954 | 954 | } | |
| 955 | - | parts := parseCSIInts(params) | |
| 955 | + | parts := parseSGRInts(params) | |
| 956 | 956 | for i := 0; i < len(parts); i++ { | |
| 957 | 957 | switch parts[i] { | |
| 958 | 958 | case 0: | |
| @@ -1314,20 +1314,68 @@ func parseCSIInts(params string) []int { | |||
| 1314 | 1314 | parts := strings.Split(params, ";") | |
| 1315 | 1315 | out := make([]int, 0, len(parts)) | |
| 1316 | 1316 | for _, p := range parts { | |
| 1317 | - | if p == "" { | |
| 1318 | - | out = append(out, 0) | |
| 1317 | + | out = append(out, parseCSIInt(p)) | |
| 1318 | + | } | |
| 1319 | + | return out | |
| 1320 | + | } | |
| 1321 | + | ||
| 1322 | + | func parseSGRInts(params string) []int { | |
| 1323 | + | if params == "" { | |
| 1324 | + | return nil | |
| 1325 | + | } | |
| 1326 | + | groups := strings.Split(params, ";") | |
| 1327 | + | out := make([]int, 0, len(groups)) | |
| 1328 | + | for _, group := range groups { | |
| 1329 | + | if !strings.Contains(group, ":") { | |
| 1330 | + | out = append(out, parseCSIInt(group)) | |
| 1319 | 1331 | continue | |
| 1320 | 1332 | } | |
| 1321 | - | v, err := strconv.Atoi(p) | |
| 1322 | - | if err != nil { | |
| 1323 | - | out = append(out, 0) | |
| 1333 | + | sub := strings.Split(group, ":") | |
| 1334 | + | code := parseCSIInt(sub[0]) | |
| 1335 | + | if code != 38 && code != 48 { | |
| 1336 | + | out = append(out, code) | |
| 1324 | 1337 | continue | |
| 1325 | 1338 | } | |
| 1326 | - | out = append(out, v) | |
| 1339 | + | mode := 0 | |
| 1340 | + | if len(sub) > 1 { | |
| 1341 | + | mode = parseCSIInt(sub[1]) | |
| 1342 | + | } | |
| 1343 | + | switch mode { | |
| 1344 | + | case 5: | |
| 1345 | + | if len(sub) < 3 { | |
| 1346 | + | continue | |
| 1347 | + | } | |
| 1348 | + | out = append(out, code, mode, parseCSIInt(sub[2])) | |
| 1349 | + | case 2: | |
| 1350 | + | // code:mode::R:G:B and code:mode:cs:R:G:B both put RGB after | |
| 1351 | + | // the colorspace slot; code:mode:R:G:B omits that slot. | |
| 1352 | + | start := 2 | |
| 1353 | + | if len(sub) > 2 && (sub[2] == "" || len(sub) >= 6) { | |
| 1354 | + | start = 3 | |
| 1355 | + | } | |
| 1356 | + | if len(sub) < start+3 { | |
| 1357 | + | continue | |
| 1358 | + | } | |
| 1359 | + | out = append(out, code, mode) | |
| 1360 | + | for i := 0; i < 3; i++ { | |
| 1361 | + | out = append(out, parseCSIInt(sub[start+i])) | |
| 1362 | + | } | |
| 1363 | + | } | |
| 1327 | 1364 | } | |
| 1328 | 1365 | return out | |
| 1329 | 1366 | } | |
| 1330 | 1367 | ||
| 1368 | + | func parseCSIInt(param string) int { | |
| 1369 | + | if param == "" { | |
| 1370 | + | return 0 | |
| 1371 | + | } | |
| 1372 | + | v, err := strconv.Atoi(param) | |
| 1373 | + | if err != nil { | |
| 1374 | + | return 0 | |
| 1375 | + | } | |
| 1376 | + | return v | |
| 1377 | + | } | |
| 1378 | + | ||
| 1331 | 1379 | func clamp(v, lo, hi int) int { | |
| 1332 | 1380 | if hi < lo { | |
| 1333 | 1381 | return lo | |
| @@ -952,7 +952,7 @@ func (s *Screen) applySGR(params string) { | |||||
| 952 | s.Style = renderer.DefaultStyle() | 952 | s.Style = renderer.DefaultStyle() | ||
| 953 | return | 953 | return | ||
| 954 | } | 954 | } | ||
| 955 | - | parts := parseCSIInts(params) | 955 | + | parts := parseSGRInts(params) |
| 956 | for i := 0; i < len(parts); i++ { | 956 | for i := 0; i < len(parts); i++ { | ||
| 957 | switch parts[i] { | 957 | switch parts[i] { | ||
| 958 | case 0: | 958 | case 0: | ||
| @@ -1314,20 +1314,68 @@ func parseCSIInts(params string) []int { | |||||
| 1314 | parts := strings.Split(params, ";") | 1314 | parts := strings.Split(params, ";") | ||
| 1315 | out := make([]int, 0, len(parts)) | 1315 | out := make([]int, 0, len(parts)) | ||
| 1316 | for _, p := range parts { | 1316 | for _, p := range parts { | ||
| 1317 | - | if p == "" { | 1317 | + | out = append(out, parseCSIInt(p)) |
| 1318 | - | out = append(out, 0) | 1318 | + | } |
| 1319 | + | return out | |||
| 1320 | + | } | |||
| 1321 | + | ||||
| 1322 | + | func parseSGRInts(params string) []int { | |||
| 1323 | + | if params == "" { | |||
| 1324 | + | return nil | |||
| 1325 | + | } | |||
| 1326 | + | groups := strings.Split(params, ";") | |||
| 1327 | + | out := make([]int, 0, len(groups)) | |||
| 1328 | + | for _, group := range groups { | |||
| 1329 | + | if !strings.Contains(group, ":") { | |||
| 1330 | + | out = append(out, parseCSIInt(group)) | |||
| 1319 | continue | 1331 | continue | ||
| 1320 | } | 1332 | } | ||
| 1321 | - | v, err := strconv.Atoi(p) | 1333 | + | sub := strings.Split(group, ":") |
| 1322 | - | if err != nil { | 1334 | + | code := parseCSIInt(sub[0]) |
| 1323 | - | out = append(out, 0) | 1335 | + | if code != 38 && code != 48 { |
| 1336 | + | out = append(out, code) | |||
| 1324 | continue | 1337 | continue | ||
| 1325 | } | 1338 | } | ||
| 1326 | - | out = append(out, v) | 1339 | + | mode := 0 |
| 1340 | + | if len(sub) > 1 { | |||
| 1341 | + | mode = parseCSIInt(sub[1]) | |||
| 1342 | + | } | |||
| 1343 | + | switch mode { | |||
| 1344 | + | case 5: | |||
| 1345 | + | if len(sub) < 3 { | |||
| 1346 | + | continue | |||
| 1347 | + | } | |||
| 1348 | + | out = append(out, code, mode, parseCSIInt(sub[2])) | |||
| 1349 | + | case 2: | |||
| 1350 | + | // code:mode::R:G:B and code:mode:cs:R:G:B both put RGB after | |||
| 1351 | + | // the colorspace slot; code:mode:R:G:B omits that slot. | |||
| 1352 | + | start := 2 | |||
| 1353 | + | if len(sub) > 2 && (sub[2] == "" || len(sub) >= 6) { | |||
| 1354 | + | start = 3 | |||
| 1355 | + | } | |||
| 1356 | + | if len(sub) < start+3 { | |||
| 1357 | + | continue | |||
| 1358 | + | } | |||
| 1359 | + | out = append(out, code, mode) | |||
| 1360 | + | for i := 0; i < 3; i++ { | |||
| 1361 | + | out = append(out, parseCSIInt(sub[start+i])) | |||
| 1362 | + | } | |||
| 1363 | + | } | |||
| 1327 | } | 1364 | } | ||
| 1328 | return out | 1365 | return out | ||
| 1329 | } | 1366 | } | ||
| 1330 | 1367 | ||||
| 1368 | + | func parseCSIInt(param string) int { | |||
| 1369 | + | if param == "" { | |||
| 1370 | + | return 0 | |||
| 1371 | + | } | |||
| 1372 | + | v, err := strconv.Atoi(param) | |||
| 1373 | + | if err != nil { | |||
| 1374 | + | return 0 | |||
| 1375 | + | } | |||
| 1376 | + | return v | |||
| 1377 | + | } | |||
| 1378 | + | ||||
| 1331 | func clamp(v, lo, hi int) int { | 1379 | func clamp(v, lo, hi int) int { | ||
| 1332 | if hi < lo { | 1380 | if hi < lo { | ||
| 1333 | return lo | 1381 | return lo | ||
M pkg/vt/screen_test.go +107 -0
| @@ -738,6 +738,67 @@ func TestSGRTruecolor(t *testing.T) { | |||
| 738 | 738 | } | |
| 739 | 739 | }, | |
| 740 | 740 | }, | |
| 741 | + | { | |
| 742 | + | name: "SGR 38:2 sets truecolor foreground", | |
| 743 | + | seq: "\x1b[38:2:12:34:56mX", | |
| 744 | + | check: func(t *testing.T, s *Screen) { | |
| 745 | + | want := renderer.RGB{R: 12, G: 34, B: 56} | |
| 746 | + | if !s.Style.HasForegroundRGB || s.Style.ForegroundRGB != want { | |
| 747 | + | t.Errorf("foreground RGB = (%v, %+v), want true/%+v", s.Style.HasForegroundRGB, s.Style.ForegroundRGB, want) | |
| 748 | + | } | |
| 749 | + | if got := cellAt(s, 0, 0).Style.ForegroundRGB; got != want { | |
| 750 | + | t.Errorf("cell foreground RGB = %+v, want %+v", got, want) | |
| 751 | + | } | |
| 752 | + | }, | |
| 753 | + | }, | |
| 754 | + | { | |
| 755 | + | name: "SGR 38:2 empty color space sets truecolor foreground", | |
| 756 | + | seq: "\x1b[38:2::12:34:56mX", | |
| 757 | + | check: func(t *testing.T, s *Screen) { | |
| 758 | + | want := renderer.RGB{R: 12, G: 34, B: 56} | |
| 759 | + | if !s.Style.HasForegroundRGB || s.Style.ForegroundRGB != want { | |
| 760 | + | t.Errorf("foreground RGB = (%v, %+v), want true/%+v", s.Style.HasForegroundRGB, s.Style.ForegroundRGB, want) | |
| 761 | + | } | |
| 762 | + | if got := cellAt(s, 0, 0).Style.ForegroundRGB; got != want { | |
| 763 | + | t.Errorf("cell foreground RGB = %+v, want %+v", got, want) | |
| 764 | + | } | |
| 765 | + | }, | |
| 766 | + | }, | |
| 767 | + | { | |
| 768 | + | name: "SGR 38:2 color space id sets truecolor foreground", | |
| 769 | + | seq: "\x1b[38:2:1:12:34:56mX", | |
| 770 | + | check: func(t *testing.T, s *Screen) { | |
| 771 | + | want := renderer.RGB{R: 12, G: 34, B: 56} | |
| 772 | + | if !s.Style.HasForegroundRGB || s.Style.ForegroundRGB != want { | |
| 773 | + | t.Errorf("foreground RGB = (%v, %+v), want true/%+v", s.Style.HasForegroundRGB, s.Style.ForegroundRGB, want) | |
| 774 | + | } | |
| 775 | + | if got := cellAt(s, 0, 0).Style.ForegroundRGB; got != want { | |
| 776 | + | t.Errorf("cell foreground RGB = %+v, want %+v", got, want) | |
| 777 | + | } | |
| 778 | + | }, | |
| 779 | + | }, | |
| 780 | + | { | |
| 781 | + | name: "mixed semicolon and colon SGR groups", | |
| 782 | + | seq: "\x1b[1;38:2:10:20:30;7mX", | |
| 783 | + | check: func(t *testing.T, s *Screen) { | |
| 784 | + | want := renderer.RGB{R: 10, G: 20, B: 30} | |
| 785 | + | if !s.Style.Bold || !s.Style.Inverse { | |
| 786 | + | t.Errorf("bold/inverse = %v/%v, want true/true", s.Style.Bold, s.Style.Inverse) | |
| 787 | + | } | |
| 788 | + | if !s.Style.HasForegroundRGB || s.Style.ForegroundRGB != want { | |
| 789 | + | t.Errorf("foreground RGB = (%v, %+v), want true/%+v", s.Style.HasForegroundRGB, s.Style.ForegroundRGB, want) | |
| 790 | + | } | |
| 791 | + | }, | |
| 792 | + | }, | |
| 793 | + | { | |
| 794 | + | name: "truncated colon truecolor group does not consume next parameter", | |
| 795 | + | seq: "\x1b[38:2:1:2;31m", | |
| 796 | + | check: func(t *testing.T, s *Screen) { | |
| 797 | + | if s.Style.Foreground != 1 || s.Style.HasForegroundRGB { | |
| 798 | + | t.Errorf("foreground = %d rgb:%v, want 1 false", s.Style.Foreground, s.Style.HasForegroundRGB) | |
| 799 | + | } | |
| 800 | + | }, | |
| 801 | + | }, | |
| 741 | 802 | { | |
| 742 | 803 | name: "SGR 48;2 sets truecolor background", | |
| 743 | 804 | seq: "\x1b[48;2;200;100;50m", | |
| @@ -748,6 +809,52 @@ func TestSGRTruecolor(t *testing.T) { | |||
| 748 | 809 | } | |
| 749 | 810 | }, | |
| 750 | 811 | }, | |
| 812 | + | { | |
| 813 | + | name: "SGR 48:2 sets truecolor background", | |
| 814 | + | seq: "\x1b[48:2:200:100:50m", | |
| 815 | + | check: func(t *testing.T, s *Screen) { | |
| 816 | + | want := renderer.RGB{R: 200, G: 100, B: 50} | |
| 817 | + | if !s.Style.HasBackgroundRGB || s.Style.BackgroundRGB != want { | |
| 818 | + | t.Errorf("background RGB = (%v, %+v), want true/%+v", s.Style.HasBackgroundRGB, s.Style.BackgroundRGB, want) | |
| 819 | + | } | |
| 820 | + | }, | |
| 821 | + | }, | |
| 822 | + | { | |
| 823 | + | name: "SGR 38:5 sets 256-color foreground", | |
| 824 | + | seq: "\x1b[38:5:82m", | |
| 825 | + | check: func(t *testing.T, s *Screen) { | |
| 826 | + | if s.Style.Foreground != 82 || s.Style.HasForegroundRGB { | |
| 827 | + | t.Errorf("foreground = %d rgb:%v, want 82 false", s.Style.Foreground, s.Style.HasForegroundRGB) | |
| 828 | + | } | |
| 829 | + | }, | |
| 830 | + | }, | |
| 831 | + | { | |
| 832 | + | name: "SGR 48:5 sets 256-color background", | |
| 833 | + | seq: "\x1b[48:5:200m", | |
| 834 | + | check: func(t *testing.T, s *Screen) { | |
| 835 | + | if s.Style.Background != 200 || s.Style.HasBackgroundRGB { | |
| 836 | + | t.Errorf("background = %d rgb:%v, want 200 false", s.Style.Background, s.Style.HasBackgroundRGB) | |
| 837 | + | } | |
| 838 | + | }, | |
| 839 | + | }, | |
| 840 | + | { | |
| 841 | + | name: "truncated colon foreground index group does not consume next parameter", | |
| 842 | + | seq: "\x1b[38:5;31m", | |
| 843 | + | check: func(t *testing.T, s *Screen) { | |
| 844 | + | if s.Style.Foreground != 1 || s.Style.HasForegroundRGB { | |
| 845 | + | t.Errorf("foreground = %d rgb:%v, want 1 false", s.Style.Foreground, s.Style.HasForegroundRGB) | |
| 846 | + | } | |
| 847 | + | }, | |
| 848 | + | }, | |
| 849 | + | { | |
| 850 | + | name: "truncated colon background index group does not consume next parameter", | |
| 851 | + | seq: "\x1b[48:5;44m", | |
| 852 | + | check: func(t *testing.T, s *Screen) { | |
| 853 | + | if s.Style.Background != 4 || s.Style.HasBackgroundRGB { | |
| 854 | + | t.Errorf("background = %d rgb:%v, want 4 false", s.Style.Background, s.Style.HasBackgroundRGB) | |
| 855 | + | } | |
| 856 | + | }, | |
| 857 | + | }, | |
| 751 | 858 | { | |
| 752 | 859 | name: "SGR 39 after truecolor clears truecolor foreground", | |
| 753 | 860 | seq: "\x1b[38;2;12;34;56m\x1b[39m", | |
| @@ -738,6 +738,67 @@ func TestSGRTruecolor(t *testing.T) { | |||||
| 738 | } | 738 | } | ||
| 739 | }, | 739 | }, | ||
| 740 | }, | 740 | }, | ||
| 741 | + | { | |||
| 742 | + | name: "SGR 38:2 sets truecolor foreground", | |||
| 743 | + | seq: "\x1b[38:2:12:34:56mX", | |||
| 744 | + | check: func(t *testing.T, s *Screen) { | |||
| 745 | + | want := renderer.RGB{R: 12, G: 34, B: 56} | |||
| 746 | + | if !s.Style.HasForegroundRGB || s.Style.ForegroundRGB != want { | |||
| 747 | + | t.Errorf("foreground RGB = (%v, %+v), want true/%+v", s.Style.HasForegroundRGB, s.Style.ForegroundRGB, want) | |||
| 748 | + | } | |||
| 749 | + | if got := cellAt(s, 0, 0).Style.ForegroundRGB; got != want { | |||
| 750 | + | t.Errorf("cell foreground RGB = %+v, want %+v", got, want) | |||
| 751 | + | } | |||
| 752 | + | }, | |||
| 753 | + | }, | |||
| 754 | + | { | |||
| 755 | + | name: "SGR 38:2 empty color space sets truecolor foreground", | |||
| 756 | + | seq: "\x1b[38:2::12:34:56mX", | |||
| 757 | + | check: func(t *testing.T, s *Screen) { | |||
| 758 | + | want := renderer.RGB{R: 12, G: 34, B: 56} | |||
| 759 | + | if !s.Style.HasForegroundRGB || s.Style.ForegroundRGB != want { | |||
| 760 | + | t.Errorf("foreground RGB = (%v, %+v), want true/%+v", s.Style.HasForegroundRGB, s.Style.ForegroundRGB, want) | |||
| 761 | + | } | |||
| 762 | + | if got := cellAt(s, 0, 0).Style.ForegroundRGB; got != want { | |||
| 763 | + | t.Errorf("cell foreground RGB = %+v, want %+v", got, want) | |||
| 764 | + | } | |||
| 765 | + | }, | |||
| 766 | + | }, | |||
| 767 | + | { | |||
| 768 | + | name: "SGR 38:2 color space id sets truecolor foreground", | |||
| 769 | + | seq: "\x1b[38:2:1:12:34:56mX", | |||
| 770 | + | check: func(t *testing.T, s *Screen) { | |||
| 771 | + | want := renderer.RGB{R: 12, G: 34, B: 56} | |||
| 772 | + | if !s.Style.HasForegroundRGB || s.Style.ForegroundRGB != want { | |||
| 773 | + | t.Errorf("foreground RGB = (%v, %+v), want true/%+v", s.Style.HasForegroundRGB, s.Style.ForegroundRGB, want) | |||
| 774 | + | } | |||
| 775 | + | if got := cellAt(s, 0, 0).Style.ForegroundRGB; got != want { | |||
| 776 | + | t.Errorf("cell foreground RGB = %+v, want %+v", got, want) | |||
| 777 | + | } | |||
| 778 | + | }, | |||
| 779 | + | }, | |||
| 780 | + | { | |||
| 781 | + | name: "mixed semicolon and colon SGR groups", | |||
| 782 | + | seq: "\x1b[1;38:2:10:20:30;7mX", | |||
| 783 | + | check: func(t *testing.T, s *Screen) { | |||
| 784 | + | want := renderer.RGB{R: 10, G: 20, B: 30} | |||
| 785 | + | if !s.Style.Bold || !s.Style.Inverse { | |||
| 786 | + | t.Errorf("bold/inverse = %v/%v, want true/true", s.Style.Bold, s.Style.Inverse) | |||
| 787 | + | } | |||
| 788 | + | if !s.Style.HasForegroundRGB || s.Style.ForegroundRGB != want { | |||
| 789 | + | t.Errorf("foreground RGB = (%v, %+v), want true/%+v", s.Style.HasForegroundRGB, s.Style.ForegroundRGB, want) | |||
| 790 | + | } | |||
| 791 | + | }, | |||
| 792 | + | }, | |||
| 793 | + | { | |||
| 794 | + | name: "truncated colon truecolor group does not consume next parameter", | |||
| 795 | + | seq: "\x1b[38:2:1:2;31m", | |||
| 796 | + | check: func(t *testing.T, s *Screen) { | |||
| 797 | + | if s.Style.Foreground != 1 || s.Style.HasForegroundRGB { | |||
| 798 | + | t.Errorf("foreground = %d rgb:%v, want 1 false", s.Style.Foreground, s.Style.HasForegroundRGB) | |||
| 799 | + | } | |||
| 800 | + | }, | |||
| 801 | + | }, | |||
| 741 | { | 802 | { | ||
| 742 | name: "SGR 48;2 sets truecolor background", | 803 | name: "SGR 48;2 sets truecolor background", | ||
| 743 | seq: "\x1b[48;2;200;100;50m", | 804 | seq: "\x1b[48;2;200;100;50m", | ||
| @@ -748,6 +809,52 @@ func TestSGRTruecolor(t *testing.T) { | |||||
| 748 | } | 809 | } | ||
| 749 | }, | 810 | }, | ||
| 750 | }, | 811 | }, | ||
| 812 | + | { | |||
| 813 | + | name: "SGR 48:2 sets truecolor background", | |||
| 814 | + | seq: "\x1b[48:2:200:100:50m", | |||
| 815 | + | check: func(t *testing.T, s *Screen) { | |||
| 816 | + | want := renderer.RGB{R: 200, G: 100, B: 50} | |||
| 817 | + | if !s.Style.HasBackgroundRGB || s.Style.BackgroundRGB != want { | |||
| 818 | + | t.Errorf("background RGB = (%v, %+v), want true/%+v", s.Style.HasBackgroundRGB, s.Style.BackgroundRGB, want) | |||
| 819 | + | } | |||
| 820 | + | }, | |||
| 821 | + | }, | |||
| 822 | + | { | |||
| 823 | + | name: "SGR 38:5 sets 256-color foreground", | |||
| 824 | + | seq: "\x1b[38:5:82m", | |||
| 825 | + | check: func(t *testing.T, s *Screen) { | |||
| 826 | + | if s.Style.Foreground != 82 || s.Style.HasForegroundRGB { | |||
| 827 | + | t.Errorf("foreground = %d rgb:%v, want 82 false", s.Style.Foreground, s.Style.HasForegroundRGB) | |||
| 828 | + | } | |||
| 829 | + | }, | |||
| 830 | + | }, | |||
| 831 | + | { | |||
| 832 | + | name: "SGR 48:5 sets 256-color background", | |||
| 833 | + | seq: "\x1b[48:5:200m", | |||
| 834 | + | check: func(t *testing.T, s *Screen) { | |||
| 835 | + | if s.Style.Background != 200 || s.Style.HasBackgroundRGB { | |||
| 836 | + | t.Errorf("background = %d rgb:%v, want 200 false", s.Style.Background, s.Style.HasBackgroundRGB) | |||
| 837 | + | } | |||
| 838 | + | }, | |||
| 839 | + | }, | |||
| 840 | + | { | |||
| 841 | + | name: "truncated colon foreground index group does not consume next parameter", | |||
| 842 | + | seq: "\x1b[38:5;31m", | |||
| 843 | + | check: func(t *testing.T, s *Screen) { | |||
| 844 | + | if s.Style.Foreground != 1 || s.Style.HasForegroundRGB { | |||
| 845 | + | t.Errorf("foreground = %d rgb:%v, want 1 false", s.Style.Foreground, s.Style.HasForegroundRGB) | |||
| 846 | + | } | |||
| 847 | + | }, | |||
| 848 | + | }, | |||
| 849 | + | { | |||
| 850 | + | name: "truncated colon background index group does not consume next parameter", | |||
| 851 | + | seq: "\x1b[48:5;44m", | |||
| 852 | + | check: func(t *testing.T, s *Screen) { | |||
| 853 | + | if s.Style.Background != 4 || s.Style.HasBackgroundRGB { | |||
| 854 | + | t.Errorf("background = %d rgb:%v, want 4 false", s.Style.Background, s.Style.HasBackgroundRGB) | |||
| 855 | + | } | |||
| 856 | + | }, | |||
| 857 | + | }, | |||
| 751 | { | 858 | { | ||
| 752 | name: "SGR 39 after truecolor clears truecolor foreground", | 859 | name: "SGR 39 after truecolor clears truecolor foreground", | ||
| 753 | seq: "\x1b[38;2;12;34;56m\x1b[39m", | 860 | seq: "\x1b[38;2;12;34;56m\x1b[39m", | ||