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.
This commit is contained in:
Kovid Goyal 2023-03-22 13:41:54 +05:30
parent e46a7c39c3
commit 88bd3ee9ca
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 123 additions and 80 deletions

View File

@ -99,6 +99,10 @@ START_ALLOW_CASE_RANGE
self->strikethrough = true; break; self->strikethrough = true; break;
case 21: case 21:
self->decoration = 2; break; self->decoration = 2; break;
case 221:
self->bold = false; break;
case 222:
self->dim = false; break;
case 22: case 22:
self->bold = false; self->dim = false; break; self->bold = false; self->dim = false; break;
case 23: case 23:
@ -170,6 +174,10 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, un
S(strike, true); S(strike, true);
case 21: case 21:
S(decoration, 2); S(decoration, 2);
case 221:
S(bold, false);
case 222:
S(dim, false);
case 22: case 22:
RANGE { cell->attrs.bold = false; cell->attrs.dim = false; } break; RANGE { cell->attrs.bold = false; cell->attrs.dim = false; } break;
case 23: case 23:

View File

@ -106,77 +106,50 @@ type SGR struct {
Foreground, Background, Underline_color ColorVal 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) ans := make([]byte, 0, 16)
if for_close { w := func(x string) {
if self.Bold.Is_set || self.Dim.Is_set { if x != "" {
ans = append(ans, '2', '2', ';') ans = append(ans, x...)
}
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, ';') ans = append(ans, ';')
} }
} }
if self.Background.Is_set { w(self.Bold.AsCSI("1", "221"))
if self.Background.Is_default { w(self.Dim.AsCSI("2", "222"))
ans = append(ans, '4', '9', ';') w(self.Italic.AsCSI("3", "23"))
} else { w(self.Reverse.AsCSI("7", "27"))
ans = append(ans, self.Background.Val.AsCSI(40)...) w(self.Strikethrough.AsCSI("9", "29"))
ans = append(ans, ';') w(self.Underline_style.AsCSI())
} w(self.Foreground.AsCSI(30))
} w(self.Background.AsCSI(40))
if self.Underline_color.Is_set { w(self.Underline_color.AsCSI(50))
if self.Underline_color.Is_default {
ans = append(ans, '5', '9', ';')
} else {
ans = append(ans, self.Underline_color.Val.AsCSI(50)...)
ans = append(ans, ';')
}
}
}
if len(ans) > 0 { if len(ans) > 0 {
ans = ans[:len(ans)-1] ans = ans[:len(ans)-1]
@ -275,11 +248,13 @@ func SGRFromCSI(csi string) (ans SGR) {
case 0: case 0:
ans = SGR{} ans = SGR{}
case 1: case 1:
ans.Dim.Val, ans.Bold.Val = false, true ans.Bold.Val, ans.Bold.Is_set = true, true
ans.Dim.Is_set, ans.Bold.Is_set = true, true case 221:
ans.Bold.Val, ans.Bold.Is_set = false, true
case 2: case 2:
ans.Dim.Val, ans.Bold.Val = true, false ans.Dim.Val, ans.Dim.Is_set = true, true
ans.Dim.Is_set, ans.Bold.Is_set = true, true case 222:
ans.Dim.Val, ans.Dim.Is_set = false, true
case 22: case 22:
ans.Dim.Val, ans.Bold.Val = false, false ans.Dim.Val, ans.Bold.Val = false, false
ans.Dim.Is_set, ans.Bold.Is_set = true, true ans.Dim.Is_set, ans.Bold.Is_set = true, true
@ -420,9 +395,64 @@ func (self *Span) SetUnderlineStyle(val UnderlineStyle) *Span {
return self 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 // Insert formatting into text at the specified offsets, overriding any existing formatting, and restoring
// existing formatting after the replaced sections. // existing formatting after the replaced sections.
func InsertFormatting(text string, spans ...*Span) string { 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 var in_span *Span
ans := make([]byte, 0, 2*len(text)) ans := make([]byte, 0, 2*len(text))
var overall_sgr_state SGR var overall_sgr_state SGR
@ -440,7 +470,7 @@ func InsertFormatting(text string, spans ...*Span) string {
in_span = spans[0] in_span = spans[0]
spans = spans[1:] spans = spans[1:]
if in_span.Size > 0 { if in_span.Size > 0 {
write_csi(in_span.SGR.AsCSI(false)) write_csi(in_span.SGR.AsCSI())
} else { } else {
in_span = nil in_span = nil
} }
@ -448,8 +478,8 @@ func InsertFormatting(text string, spans ...*Span) string {
} }
close_span := func() { close_span := func() {
write_csi(in_span.SGR.AsCSI(true)) write_csi(in_span.ClosingCSI())
write_csi(overall_sgr_state.AsCSI(false)) write_csi(overall_sgr_state.AsCSI())
in_span = nil in_span = nil
} }
@ -486,7 +516,7 @@ func InsertFormatting(text string, spans ...*Span) string {
write_csi(csi) write_csi(csi)
} else { } else {
sgr.ApplyMask(in_span.SGR) sgr.ApplyMask(in_span.SGR)
csi := sgr.AsCSI(false) csi := sgr.AsCSI()
write_csi(csi) write_csi(csi)
} }
return nil return nil

View File

@ -28,4 +28,9 @@ func TestInsertFormatting(t *testing.T) {
"a\x1b[92mbcd\x1b[39m", "a\x1b[92mbcd\x1b[39m",
NewSpan(1, 11).SetForeground(10), 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),
)
} }

View File

@ -311,8 +311,8 @@ func (self url_code) is_empty() bool {
func (self *sgr_code) update() { func (self *sgr_code) update() {
p := make([]string, 0, 1) p := make([]string, 0, 1)
s := make([]string, 0, 1) s := make([]string, 0, 1)
p, s = self.bold.as_sgr("1", "22", p, s) p, s = self.bold.as_sgr("1", "221", p, s)
p, s = self.dim.as_sgr("2", "22", 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.italic.as_sgr("3", "23", p, s)
p, s = self.reverse.as_sgr("7", "27", p, s) p, s = self.reverse.as_sgr("7", "27", p, s)
p, s = self.strikethrough.as_sgr("9", "29", p, s) p, s = self.strikethrough.as_sgr("9", "29", p, s)

View File

@ -33,8 +33,8 @@ func TestANSIStyleSprint(t *testing.T) {
} }
test("", "", "") test("", "", "")
test("bold", "\x1b[1m", "\x1b[22m") test("bold", "\x1b[1m", "\x1b[221m")
test("bold fg=red u=curly", "\x1b[1;4:3;31m", "\x1b[22;4:0;39m") 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=123", "\x1b[38:5:123m", "\x1b[39m")
test("fg=15", "\x1b[97m", "\x1b[39m") test("fg=15", "\x1b[97m", "\x1b[39m")
test("bg=15", "\x1b[107m", "\x1b[49m") test("bg=15", "\x1b[107m", "\x1b[49m")