From 88bd3ee9cac7fde5af00b15280232b6a043769bc Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 22 Mar 2023 13:41:54 +0530 Subject: [PATCH] New SGR codes to turn off bold/dim independently Allows for robust patching of formatting into already formatted text. Without this it is not possible to turn off bold without affecting existing dim and vice versa. --- kitty/cursor.c | 8 ++ tools/tui/sgr/insert-formatting.go | 182 ++++++++++++++---------- tools/tui/sgr/insert-formatting_test.go | 5 + tools/utils/style/wrapper.go | 4 +- tools/utils/style/wrapper_test.go | 4 +- 5 files changed, 123 insertions(+), 80 deletions(-) diff --git a/kitty/cursor.c b/kitty/cursor.c index d0a38a32a..6b2c738da 100644 --- a/kitty/cursor.c +++ b/kitty/cursor.c @@ -99,6 +99,10 @@ START_ALLOW_CASE_RANGE self->strikethrough = true; break; case 21: self->decoration = 2; break; + case 221: + self->bold = false; break; + case 222: + self->dim = false; break; case 22: self->bold = false; self->dim = false; break; case 23: @@ -170,6 +174,10 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, un S(strike, true); case 21: S(decoration, 2); + case 221: + S(bold, false); + case 222: + S(dim, false); case 22: RANGE { cell->attrs.bold = false; cell->attrs.dim = false; } break; case 23: diff --git a/tools/tui/sgr/insert-formatting.go b/tools/tui/sgr/insert-formatting.go index a26b6c1f8..7130aea0b 100644 --- a/tools/tui/sgr/insert-formatting.go +++ b/tools/tui/sgr/insert-formatting.go @@ -106,77 +106,50 @@ type SGR struct { Foreground, Background, Underline_color ColorVal } -func (self *SGR) AsCSI(for_close bool) string { +func (self *BoolVal) AsCSI(set, reset string) string { + if !self.Is_set { + return "" + } + if self.Val { + return set + } + return reset +} + +func (self *UnderlineStyleVal) AsCSI() string { + if !self.Is_set { + return "" + } + return fmt.Sprintf("4:%d;", self.Val) +} + +func (self *ColorVal) AsCSI(base int) string { + if !self.Is_set { + return "" + } + if self.Is_default { + return strconv.Itoa(base + 9) + } + return self.Val.AsCSI(base) +} + +func (self *SGR) AsCSI() string { ans := make([]byte, 0, 16) - if for_close { - if self.Bold.Is_set || self.Dim.Is_set { - ans = append(ans, '2', '2', ';') - } - if self.Italic.Is_set { - ans = append(ans, '2', '3', ';') - } - if self.Reverse.Is_set { - ans = append(ans, '2', '7', ';') - } - if self.Strikethrough.Is_set { - ans = append(ans, '2', '9', ';') - } - if self.Underline_style.Is_set { - ans = append(ans, '4', ':', '0', ';') - } - if self.Foreground.Is_set { - ans = append(ans, '3', '9', ';') - } - if self.Background.Is_set { - ans = append(ans, '4', '9', ';') - } - if self.Underline_color.Is_set { - ans = append(ans, '5', '9', ';') - } - } else { - if self.Bold.Is_set { - ans = append(ans, '1', ';') - } - if self.Dim.Is_set { - ans = append(ans, '2', ';') - } - if self.Italic.Is_set { - ans = append(ans, '3', ';') - } - if self.Reverse.Is_set { - ans = append(ans, '7', ';') - } - if self.Strikethrough.Is_set { - ans = append(ans, '9', ';') - } - if self.Underline_style.Is_set { - ans = append(ans, fmt.Sprintf("4:%d;", self.Underline_style.Val)...) - } - if self.Foreground.Is_set { - if self.Foreground.Is_default { - ans = append(ans, '3', '9', ';') - } else { - ans = append(ans, self.Foreground.Val.AsCSI(30)...) - ans = append(ans, ';') - } - } - if self.Background.Is_set { - if self.Background.Is_default { - ans = append(ans, '4', '9', ';') - } else { - ans = append(ans, self.Background.Val.AsCSI(40)...) - ans = append(ans, ';') - } - } - if self.Underline_color.Is_set { - if self.Underline_color.Is_default { - ans = append(ans, '5', '9', ';') - } else { - ans = append(ans, self.Underline_color.Val.AsCSI(50)...) - ans = append(ans, ';') - } + w := func(x string) { + if x != "" { + ans = append(ans, x...) + ans = append(ans, ';') } } + w(self.Bold.AsCSI("1", "221")) + w(self.Dim.AsCSI("2", "222")) + w(self.Italic.AsCSI("3", "23")) + w(self.Reverse.AsCSI("7", "27")) + w(self.Strikethrough.AsCSI("9", "29")) + w(self.Underline_style.AsCSI()) + w(self.Foreground.AsCSI(30)) + w(self.Background.AsCSI(40)) + w(self.Underline_color.AsCSI(50)) if len(ans) > 0 { ans = ans[:len(ans)-1] @@ -275,11 +248,13 @@ func SGRFromCSI(csi string) (ans SGR) { case 0: ans = SGR{} case 1: - ans.Dim.Val, ans.Bold.Val = false, true - ans.Dim.Is_set, ans.Bold.Is_set = true, true + ans.Bold.Val, ans.Bold.Is_set = true, true + case 221: + ans.Bold.Val, ans.Bold.Is_set = false, true case 2: - ans.Dim.Val, ans.Bold.Val = true, false - ans.Dim.Is_set, ans.Bold.Is_set = true, true + ans.Dim.Val, ans.Dim.Is_set = true, true + case 222: + ans.Dim.Val, ans.Dim.Is_set = false, true case 22: ans.Dim.Val, ans.Bold.Val = false, false ans.Dim.Is_set, ans.Bold.Is_set = true, true @@ -420,9 +395,64 @@ func (self *Span) SetUnderlineStyle(val UnderlineStyle) *Span { return self } +type defaulting_val interface { + DefaultCSI() string +} + +func append_default_csi(x defaulting_val, ans []byte) []byte { + val := x.DefaultCSI() + if val != "" { + ans = append(ans, val...) + ans = append(ans, ';') + } + return ans +} + +func (self *Span) ClosingCSI() string { + ans := make([]byte, 0, 16) + w := func(x string) { + if x != "" { + ans = append(append(ans, x...), ';') + } + } + if self.SGR.Bold.Is_set { + w(self.SGR.Bold.AsCSI("1", "221")) + } + if self.SGR.Dim.Is_set { + w(self.SGR.Dim.AsCSI("2", "222")) + } + if self.SGR.Italic.Is_set { + w(self.SGR.Italic.AsCSI("3", "23")) + } + if self.SGR.Reverse.Is_set { + w(self.SGR.Reverse.AsCSI("7", "27")) + } + if self.SGR.Strikethrough.Is_set { + w(self.SGR.Strikethrough.AsCSI("9", "29")) + } + wc := func(cval ColorVal, base int) { + if cval.Is_set { + cval.Is_default = true + w(cval.AsCSI(base)) + } + } + wc(self.SGR.Foreground, 30) + wc(self.SGR.Background, 40) + wc(self.SGR.Underline_color, 50) + if len(ans) > 0 { + ans = ans[:len(ans)-1] + ans = append(ans, 'm') + } + return utils.UnsafeBytesToString(ans) +} + // Insert formatting into text at the specified offsets, overriding any existing formatting, and restoring // existing formatting after the replaced sections. func InsertFormatting(text string, spans ...*Span) string { + spans = utils.Filter(spans, func(s *Span) bool { return !s.SGR.IsEmpty() }) + if len(spans) == 0 { + return text + } var in_span *Span ans := make([]byte, 0, 2*len(text)) var overall_sgr_state SGR @@ -440,7 +470,7 @@ func InsertFormatting(text string, spans ...*Span) string { in_span = spans[0] spans = spans[1:] if in_span.Size > 0 { - write_csi(in_span.SGR.AsCSI(false)) + write_csi(in_span.SGR.AsCSI()) } else { in_span = nil } @@ -448,8 +478,8 @@ func InsertFormatting(text string, spans ...*Span) string { } close_span := func() { - write_csi(in_span.SGR.AsCSI(true)) - write_csi(overall_sgr_state.AsCSI(false)) + write_csi(in_span.ClosingCSI()) + write_csi(overall_sgr_state.AsCSI()) in_span = nil } @@ -486,7 +516,7 @@ func InsertFormatting(text string, spans ...*Span) string { write_csi(csi) } else { sgr.ApplyMask(in_span.SGR) - csi := sgr.AsCSI(false) + csi := sgr.AsCSI() write_csi(csi) } return nil diff --git a/tools/tui/sgr/insert-formatting_test.go b/tools/tui/sgr/insert-formatting_test.go index 91411f159..abcc9975b 100644 --- a/tools/tui/sgr/insert-formatting_test.go +++ b/tools/tui/sgr/insert-formatting_test.go @@ -28,4 +28,9 @@ func TestInsertFormatting(t *testing.T) { "a\x1b[92mbcd\x1b[39m", NewSpan(1, 11).SetForeground(10), ) + test( + "AB\x1b[1mC\x1b[221mDE", + "A\x1b[37mB\x1b[1mC\x1b[221mDE\x1b[39m\x1b[221m", + NewSpan(1, 11).SetForeground(7), + ) } diff --git a/tools/utils/style/wrapper.go b/tools/utils/style/wrapper.go index 99c8fc877..17b49b047 100644 --- a/tools/utils/style/wrapper.go +++ b/tools/utils/style/wrapper.go @@ -311,8 +311,8 @@ func (self url_code) is_empty() bool { func (self *sgr_code) update() { p := make([]string, 0, 1) s := make([]string, 0, 1) - p, s = self.bold.as_sgr("1", "22", p, s) - p, s = self.dim.as_sgr("2", "22", p, s) + p, s = self.bold.as_sgr("1", "221", p, s) + p, s = self.dim.as_sgr("2", "222", p, s) p, s = self.italic.as_sgr("3", "23", p, s) p, s = self.reverse.as_sgr("7", "27", p, s) p, s = self.strikethrough.as_sgr("9", "29", p, s) diff --git a/tools/utils/style/wrapper_test.go b/tools/utils/style/wrapper_test.go index c7d85fc4e..866be1ba5 100644 --- a/tools/utils/style/wrapper_test.go +++ b/tools/utils/style/wrapper_test.go @@ -33,8 +33,8 @@ func TestANSIStyleSprint(t *testing.T) { } test("", "", "") - test("bold", "\x1b[1m", "\x1b[22m") - test("bold fg=red u=curly", "\x1b[1;4:3;31m", "\x1b[22;4:0;39m") + test("bold", "\x1b[1m", "\x1b[221m") + test("bold fg=red u=curly", "\x1b[1;4:3;31m", "\x1b[221;4:0;39m") test("fg=123", "\x1b[38:5:123m", "\x1b[39m") test("fg=15", "\x1b[97m", "\x1b[39m") test("bg=15", "\x1b[107m", "\x1b[49m")