123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- // License: GPLv3 Copyright: 2023, Kovid Goyal, <kovid at kovidgoyal.net>
- package sgr
- import (
- "fmt"
- "slices"
- "strconv"
- "strings"
- "unicode/utf8"
- "kitty/tools/utils"
- "kitty/tools/utils/style"
- "kitty/tools/wcswidth"
- )
- var _ = fmt.Print
- type UnderlineStyle uint8
- const (
- No_underline UnderlineStyle = iota
- Straight_underline
- Double_underline
- Curly_underline
- Dotted_underline
- Dashed_underline
- )
- type Color struct {
- Red, Green, Blue uint8
- Is_numbered bool
- }
- func (self *Color) Set(val any) (err error) {
- switch v := val.(type) {
- case int:
- self.Is_numbered = true
- self.Red = uint8(v)
- case style.RGBA:
- self.Is_numbered = false
- self.Red, self.Green, self.Blue = v.Red, v.Green, v.Blue
- case string:
- rgba, err := style.ParseColor(v)
- if err != nil {
- return err
- }
- self.Is_numbered = false
- self.Red, self.Green, self.Blue = rgba.Red, rgba.Green, rgba.Blue
- default:
- return fmt.Errorf("Unknown type to set color from: %T", v)
- }
- return nil
- }
- func (self Color) AsCSI(base int) string {
- if self.Is_numbered && base < 50 {
- if self.Red < 8 {
- return strconv.Itoa(base + int(self.Red))
- }
- if self.Red < 16 {
- return strconv.Itoa(base + 52 + int(self.Red))
- }
- return fmt.Sprintf("%d:5:%d", base+8, self.Red)
- }
- return fmt.Sprintf("%d:2:%d:%d:%d", base+8, self.Red, self.Green, self.Blue)
- }
- func (self *Color) FromNumber(n uint8) {
- self.Is_numbered, self.Red = true, n
- }
- func (self *Color) FromExtended(nums ...int) bool {
- switch nums[0] {
- case 5:
- if len(nums) > 1 {
- self.Red = uint8(nums[1])
- self.Is_numbered = true
- return true
- }
- case 2:
- if len(nums) > 3 {
- self.Is_numbered = false
- self.Red, self.Green, self.Blue = uint8(nums[1]), uint8(nums[2]), uint8(nums[3])
- return true
- }
- }
- return false
- }
- type BoolVal struct{ Is_set, Val bool }
- type UnderlineStyleVal struct {
- Is_set bool
- Val UnderlineStyle
- }
- type ColorVal struct {
- Is_set, Is_default bool
- Val Color
- }
- type SGR struct {
- Italic, Reverse, Bold, Dim, Strikethrough BoolVal
- Underline_style UnderlineStyleVal
- Foreground, Background, Underline_color ColorVal
- }
- 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)
- 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]
- ans = append(ans, 'm')
- }
- return utils.UnsafeBytesToString(ans)
- }
- func (self *SGR) IsEmpty() bool {
- return !(self.Foreground.Is_set || self.Background.Is_set || self.Underline_color.Is_set || self.Underline_style.Is_set || self.Italic.Is_set || self.Bold.Is_set || self.Reverse.Is_set || self.Dim.Is_set || self.Strikethrough.Is_set)
- }
- func (self *SGR) ApplyMask(other SGR) {
- if other.Italic.Is_set {
- self.Italic.Is_set = false
- }
- if other.Reverse.Is_set {
- self.Reverse.Is_set = false
- }
- if other.Bold.Is_set {
- self.Bold.Is_set = false
- }
- if other.Dim.Is_set {
- self.Dim.Is_set = false
- }
- if other.Strikethrough.Is_set {
- self.Strikethrough.Is_set = false
- }
- if other.Underline_style.Is_set {
- self.Underline_style.Is_set = false
- }
- if other.Foreground.Is_set {
- self.Foreground.Is_set = false
- }
- if other.Background.Is_set {
- self.Background.Is_set = false
- }
- if other.Underline_color.Is_set {
- self.Underline_color.Is_set = false
- }
- }
- func (self *SGR) ApplySGR(other SGR) {
- if other.Italic.Is_set {
- self.Italic = other.Italic
- }
- if other.Reverse.Is_set {
- self.Reverse = other.Reverse
- }
- if other.Bold.Is_set {
- self.Bold = other.Bold
- }
- if other.Dim.Is_set {
- self.Dim = other.Dim
- }
- if other.Strikethrough.Is_set {
- self.Strikethrough = other.Strikethrough
- }
- if other.Underline_style.Is_set {
- self.Underline_style = other.Underline_style
- }
- if other.Foreground.Is_set {
- self.Foreground = other.Foreground
- }
- if other.Background.Is_set {
- self.Background = other.Background
- }
- if other.Underline_color.Is_set {
- self.Underline_color = other.Underline_color
- }
- }
- func SGRFromCSI(csi string) (ans SGR) {
- if !strings.HasSuffix(csi, "m") {
- return
- }
- csi = csi[:len(csi)-1]
- if csi == "" {
- csi = "0"
- }
- parts := strings.Split(csi, ";")
- nums := make([]int, 0, 8)
- for _, part := range parts {
- subparts := strings.Split(part, ":")
- nums = nums[:0]
- for _, b := range subparts {
- q, err := strconv.Atoi(b)
- if err == nil {
- nums = append(nums, q)
- }
- }
- if len(nums) == 0 {
- continue
- }
- switch nums[0] {
- case 0:
- ans = SGR{}
- case 1:
- 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.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
- case 3:
- ans.Italic.Is_set, ans.Italic.Val = true, true
- case 23:
- ans.Italic.Is_set, ans.Italic.Val = true, false
- case 7:
- ans.Reverse.Is_set, ans.Reverse.Val = true, true
- case 27:
- ans.Reverse.Is_set, ans.Reverse.Val = true, false
- case 9:
- ans.Strikethrough.Is_set, ans.Strikethrough.Val = true, true
- case 29:
- ans.Strikethrough.Is_set, ans.Strikethrough.Val = true, false
- case 24:
- ans.Underline_style.Is_set, ans.Underline_style.Val = true, No_underline
- case 4:
- us := 1
- if len(nums) > 1 {
- us = nums[1]
- }
- switch us {
- case 0:
- ans.Underline_style.Is_set, ans.Underline_style.Val = true, No_underline
- case 1:
- ans.Underline_style.Is_set, ans.Underline_style.Val = true, Straight_underline
- case 2:
- ans.Underline_style.Is_set, ans.Underline_style.Val = true, Double_underline
- case 3:
- ans.Underline_style.Is_set, ans.Underline_style.Val = true, Curly_underline
- case 4:
- ans.Underline_style.Is_set, ans.Underline_style.Val = true, Dotted_underline
- case 5:
- ans.Underline_style.Is_set, ans.Underline_style.Val = true, Dashed_underline
- }
- case 30, 31, 32, 33, 34, 35, 36, 37:
- ans.Foreground.Is_set, ans.Foreground.Is_default = true, false
- ans.Foreground.Val.FromNumber(uint8(nums[0] - 30))
- case 90, 91, 92, 93, 94, 95, 96, 97:
- ans.Foreground.Is_set, ans.Foreground.Is_default = true, false
- ans.Foreground.Val.FromNumber(uint8(nums[0] - 82))
- case 38:
- if ans.Foreground.Val.FromExtended(nums[1:]...) {
- ans.Foreground.Is_set, ans.Foreground.Is_default = true, false
- }
- case 39:
- ans.Foreground.Is_set, ans.Foreground.Is_default = true, true
- case 40, 41, 42, 43, 44, 45, 46, 47:
- ans.Background.Is_set, ans.Background.Is_default = true, false
- ans.Background.Val.FromNumber(uint8(nums[0] - 40))
- case 100, 101, 102, 103, 104, 105, 106, 107:
- ans.Background.Is_set, ans.Background.Is_default = true, false
- ans.Background.Val.FromNumber(uint8(nums[0] - 92))
- case 48:
- if ans.Background.Val.FromExtended(nums[1:]...) {
- ans.Background.Is_set, ans.Background.Is_default = true, false
- }
- case 49:
- ans.Background.Is_set, ans.Background.Is_default = true, true
- case 58:
- if ans.Underline_color.Val.FromExtended(nums[1:]...) {
- ans.Underline_color.Is_set, ans.Underline_color.Is_default = true, false
- }
- case 59:
- ans.Underline_color.Is_set, ans.Underline_color.Is_default = true, true
- }
- }
- return
- }
- type Span struct {
- Offset, Size int // in bytes
- opening_sgr, closing_sgr SGR
- }
- func NewSpan(offset, size int) *Span {
- return &Span{Offset: offset, Size: size}
- }
- func (self *BoolVal) Set(val bool) {
- self.Is_set = true
- self.Val = val
- }
- func (self *ColorVal) Set(val any) {
- self.Is_set = true
- if val == nil {
- self.Is_default = true
- } else {
- self.Is_default = false
- if err := self.Val.Set(val); err != nil {
- panic(err)
- }
- }
- }
- func (self *Span) SetForeground(val any) *Span {
- self.opening_sgr.Foreground.Set(val)
- return self
- }
- func (self *Span) SetBackground(val any) *Span {
- self.opening_sgr.Background.Set(val)
- return self
- }
- func (self *Span) SetUnderlineColor(val any) *Span {
- self.opening_sgr.Underline_color.Set(val)
- return self
- }
- func (self *Span) SetItalic(val bool) *Span {
- self.opening_sgr.Italic.Set(val)
- return self
- }
- func (self *Span) SetBold(val bool) *Span {
- self.opening_sgr.Bold.Set(val)
- return self
- }
- func (self *Span) SetReverse(val bool) *Span {
- self.opening_sgr.Reverse.Set(val)
- return self
- }
- func (self *Span) SetDim(val bool) *Span {
- self.opening_sgr.Dim.Set(val)
- return self
- }
- func (self *Span) SetStrikethrough(val bool) *Span {
- self.opening_sgr.Strikethrough.Set(val)
- return self
- }
- func (self *Span) SetUnderlineStyle(val UnderlineStyle) *Span {
- self.opening_sgr.Underline_style.Is_set = true
- self.opening_sgr.Underline_style.Val = val
- return self
- }
- func (self *Span) SetClosingForeground(val any) *Span {
- self.closing_sgr.Foreground.Set(val)
- return self
- }
- func (self *Span) SetClosingBackground(val any) *Span {
- self.closing_sgr.Background.Set(val)
- return self
- }
- func (self *Span) SetClosingUnderlineColor(val any) *Span {
- self.closing_sgr.Underline_color.Set(val)
- return self
- }
- func (self *Span) SetClosingItalic(val bool) *Span {
- self.closing_sgr.Italic.Set(val)
- return self
- }
- func (self *Span) SetClosingBold(val bool) *Span {
- self.closing_sgr.Bold.Set(val)
- return self
- }
- func (self *Span) SetClosingReverse(val bool) *Span {
- self.closing_sgr.Reverse.Set(val)
- return self
- }
- func (self *Span) SetClosingDim(val bool) *Span {
- self.closing_sgr.Dim.Set(val)
- return self
- }
- func (self *Span) SetClosingStrikethrough(val bool) *Span {
- self.closing_sgr.Strikethrough.Set(val)
- return self
- }
- func (self *Span) SetClosingUnderlineStyle(val UnderlineStyle) *Span {
- self.closing_sgr.Underline_style.Is_set = true
- self.opening_sgr.Underline_style.Val = val
- return self
- }
- // 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.opening_sgr.IsEmpty() })
- if len(spans) == 0 {
- return text
- }
- var in_span *Span
- ans := make([]byte, 0, 2*len(text))
- var overall_sgr_state SGR
- slices.SortFunc(spans, func(a, b *Span) int { return a.Offset - b.Offset })
- text_len := 0
- var ep *wcswidth.EscapeCodeParser
- write_csi := func(csi string) {
- if csi != "" {
- ans = append(ans, 0x1b, '[')
- ans = append(ans, csi...)
- }
- }
- open_span := func() {
- in_span = spans[0]
- spans = spans[1:]
- if in_span.Size > 0 {
- write_csi(in_span.opening_sgr.AsCSI())
- } else {
- in_span = nil
- }
- }
- close_span := func() {
- write_csi(in_span.closing_sgr.AsCSI())
- write_csi(overall_sgr_state.AsCSI())
- in_span = nil
- }
- ep = &wcswidth.EscapeCodeParser{
- HandleRune: func(ch rune) error {
- var rlen int
- if in_span == nil {
- if len(spans) > 0 && text_len >= spans[0].Offset {
- open_span()
- return ep.HandleRune(ch)
- }
- before := len(ans)
- ans = utf8.AppendRune(ans, ch)
- rlen = len(ans) - before
- } else {
- rlen = utf8.RuneLen(ch)
- if text_len+rlen > in_span.Offset+in_span.Size {
- close_span()
- }
- ans = utf8.AppendRune(ans, ch)
- }
- text_len += rlen
- return nil
- },
- HandleCSI: func(csib []byte) error {
- csi := utils.UnsafeBytesToString(csib)
- if len(csi) == 0 || csi[len(csi)-1] != 'm' {
- write_csi(csi)
- return nil
- }
- sgr := SGRFromCSI(csi)
- overall_sgr_state.ApplySGR(sgr)
- if in_span == nil {
- write_csi(csi)
- } else {
- sgr.ApplyMask(in_span.opening_sgr)
- csi := sgr.AsCSI()
- write_csi(csi)
- }
- return nil
- },
- HandleOSC: func(osc []byte) error {
- ans = append(ans, 0x1b, ']')
- ans = append(ans, osc...)
- ans = append(ans, 0x1b, '\\')
- return nil
- },
- }
- ep.ParseString(text)
- if in_span != nil {
- close_span()
- }
- return utils.UnsafeBytesToString(ans)
- }
|