github.com/elves/elvish@v0.15.0/pkg/ui/parse_sgr.go (about) 1 package ui 2 3 import ( 4 "strconv" 5 "strings" 6 ) 7 8 type sgrTokenizer struct { 9 text string 10 11 styling Styling 12 content string 13 } 14 15 const sgrPrefix = "\033[" 16 17 func (st *sgrTokenizer) Next() bool { 18 for strings.HasPrefix(st.text, sgrPrefix) { 19 trimmed := strings.TrimPrefix(st.text, sgrPrefix) 20 // Find the terminator of this sequence. 21 termIndex := strings.IndexFunc(trimmed, func(r rune) bool { 22 return r != ';' && (r < '0' || r > '9') 23 }) 24 if termIndex == -1 { 25 // The string ends with an unterminated escape sequence; ignore 26 // it. 27 st.text = "" 28 return false 29 } 30 term := trimmed[termIndex] 31 sgr := trimmed[:termIndex] 32 st.text = trimmed[termIndex+1:] 33 if term == 'm' { 34 st.styling = StylingFromSGR(sgr) 35 st.content = "" 36 return true 37 } 38 // If the terminator is not 'm'; we have seen a non-SGR escape sequence; 39 // ignore it and continue. 40 } 41 if st.text == "" { 42 return false 43 } 44 // Parse a content segment until the next SGR prefix. 45 content := "" 46 nextSGR := strings.Index(st.text, sgrPrefix) 47 if nextSGR == -1 { 48 content = st.text 49 } else { 50 content = st.text[:nextSGR] 51 } 52 st.text = st.text[len(content):] 53 st.styling = nil 54 st.content = content 55 return true 56 } 57 58 func (st *sgrTokenizer) Token() (Styling, string) { 59 return st.styling, st.content 60 } 61 62 // ParseSGREscapedText parses SGR-escaped text into a Text. It also removes 63 // non-SGR CSI sequences sequences in the text. 64 func ParseSGREscapedText(s string) Text { 65 var text Text 66 var style Style 67 68 tokenizer := sgrTokenizer{text: s} 69 for tokenizer.Next() { 70 styling, content := tokenizer.Token() 71 if styling != nil { 72 styling.transform(&style) 73 } 74 if content != "" { 75 text = append(text, &Segment{style, content}) 76 } 77 } 78 return text 79 } 80 81 var sgrStyling = map[int]Styling{ 82 0: Reset, 83 1: Bold, 84 2: Dim, 85 4: Underlined, 86 5: Blink, 87 7: Inverse, 88 } 89 90 // StyleFromSGR builds a Style from an SGR sequence. 91 func StyleFromSGR(s string) Style { 92 var ret Style 93 StylingFromSGR(s).transform(&ret) 94 return ret 95 } 96 97 // StylingFromSGR builds a Style from an SGR sequence. 98 func StylingFromSGR(s string) Styling { 99 styling := jointStyling{} 100 codes := getSGRCodes(s) 101 if len(codes) == 0 { 102 return Reset 103 } 104 for len(codes) > 0 { 105 code := codes[0] 106 consume := 1 107 var moreStyling Styling 108 109 switch { 110 case sgrStyling[code] != nil: 111 moreStyling = sgrStyling[code] 112 case 30 <= code && code <= 37: 113 moreStyling = Fg(ansiColor(code - 30)) 114 case 40 <= code && code <= 47: 115 moreStyling = Bg(ansiColor(code - 40)) 116 case 90 <= code && code <= 97: 117 moreStyling = Fg(ansiBrightColor(code - 90)) 118 case 100 <= code && code <= 107: 119 moreStyling = Bg(ansiBrightColor(code - 100)) 120 case code == 38 && len(codes) >= 3 && codes[1] == 5: 121 moreStyling = Fg(xterm256Color(codes[2])) 122 consume = 3 123 case code == 48 && len(codes) >= 3 && codes[1] == 5: 124 moreStyling = Bg(xterm256Color(codes[2])) 125 consume = 3 126 case code == 38 && len(codes) >= 5 && codes[1] == 2: 127 moreStyling = Fg(trueColor{ 128 uint8(codes[2]), uint8(codes[3]), uint8(codes[4])}) 129 consume = 5 130 case code == 48 && len(codes) >= 5 && codes[1] == 2: 131 moreStyling = Bg(trueColor{ 132 uint8(codes[2]), uint8(codes[3]), uint8(codes[4])}) 133 consume = 5 134 default: 135 // Do nothing; skip this code 136 } 137 codes = codes[consume:] 138 if moreStyling != nil { 139 styling = append(styling, moreStyling) 140 } 141 } 142 return styling 143 } 144 145 func getSGRCodes(s string) []int { 146 var codes []int 147 for _, part := range strings.Split(s, ";") { 148 if part == "" { 149 codes = append(codes, 0) 150 } else { 151 code, err := strconv.Atoi(part) 152 if err == nil { 153 codes = append(codes, code) 154 } 155 } 156 } 157 return codes 158 }