123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- package ui
- import (
- "strconv"
- "strings"
- )
- type sgrTokenizer struct {
- text string
- styling Styling
- content string
- }
- const sgrPrefix = "\033["
- func (st *sgrTokenizer) Next() bool {
- for strings.HasPrefix(st.text, sgrPrefix) {
- trimmed := strings.TrimPrefix(st.text, sgrPrefix)
- // Find the terminator of this sequence.
- termIndex := strings.IndexFunc(trimmed, func(r rune) bool {
- return r != ';' && (r < '0' || r > '9')
- })
- if termIndex == -1 {
- // The string ends with an unterminated escape sequence; ignore
- // it.
- st.text = ""
- return false
- }
- term := trimmed[termIndex]
- sgr := trimmed[:termIndex]
- st.text = trimmed[termIndex+1:]
- if term == 'm' {
- st.styling = StylingFromSGR(sgr)
- st.content = ""
- return true
- }
- // If the terminator is not 'm'; we have seen a non-SGR escape sequence;
- // ignore it and continue.
- }
- if st.text == "" {
- return false
- }
- // Parse a content segment until the next SGR prefix.
- content := ""
- nextSGR := strings.Index(st.text, sgrPrefix)
- if nextSGR == -1 {
- content = st.text
- } else {
- content = st.text[:nextSGR]
- }
- st.text = st.text[len(content):]
- st.styling = nil
- st.content = content
- return true
- }
- func (st *sgrTokenizer) Token() (Styling, string) {
- return st.styling, st.content
- }
- // ParseSGREscapedText parses SGR-escaped text into a Text. It also removes
- // non-SGR CSI sequences sequences in the text.
- func ParseSGREscapedText(s string) Text {
- var text Text
- var style Style
- tokenizer := sgrTokenizer{text: s}
- for tokenizer.Next() {
- styling, content := tokenizer.Token()
- if styling != nil {
- styling.transform(&style)
- }
- if content != "" {
- text = append(text, &Segment{style, content})
- }
- }
- return text
- }
- var sgrStyling = map[int]Styling{
- 0: Reset,
- 1: Bold,
- 2: Dim,
- 4: Underlined,
- 5: Blink,
- 7: Inverse,
- }
- // StyleFromSGR builds a Style from an SGR sequence.
- func StyleFromSGR(s string) Style {
- var ret Style
- StylingFromSGR(s).transform(&ret)
- return ret
- }
- // StylingFromSGR builds a Style from an SGR sequence.
- func StylingFromSGR(s string) Styling {
- styling := jointStyling{}
- codes := getSGRCodes(s)
- if len(codes) == 0 {
- return Reset
- }
- for len(codes) > 0 {
- code := codes[0]
- consume := 1
- var moreStyling Styling
- switch {
- case sgrStyling[code] != nil:
- moreStyling = sgrStyling[code]
- case 30 <= code && code <= 37:
- moreStyling = Fg(ansiColor(code - 30))
- case 40 <= code && code <= 47:
- moreStyling = Bg(ansiColor(code - 40))
- case 90 <= code && code <= 97:
- moreStyling = Fg(ansiBrightColor(code - 90))
- case 100 <= code && code <= 107:
- moreStyling = Bg(ansiBrightColor(code - 100))
- case code == 38 && len(codes) >= 3 && codes[1] == 5:
- moreStyling = Fg(xterm256Color(codes[2]))
- consume = 3
- case code == 48 && len(codes) >= 3 && codes[1] == 5:
- moreStyling = Bg(xterm256Color(codes[2]))
- consume = 3
- case code == 38 && len(codes) >= 5 && codes[1] == 2:
- moreStyling = Fg(trueColor{
- uint8(codes[2]), uint8(codes[3]), uint8(codes[4])})
- consume = 5
- case code == 48 && len(codes) >= 5 && codes[1] == 2:
- moreStyling = Bg(trueColor{
- uint8(codes[2]), uint8(codes[3]), uint8(codes[4])})
- consume = 5
- case code == 39:
- moreStyling = ExplicitFgDefault
- case code == 49:
- moreStyling = ExplicitBgDefault
- default:
- // Do nothing; skip this code
- }
- codes = codes[consume:]
- if moreStyling != nil {
- styling = append(styling, moreStyling)
- }
- }
- return styling
- }
- func getSGRCodes(s string) []int {
- var codes []int
- for _, part := range strings.Split(s, ";") {
- if part == "" {
- codes = append(codes, 0)
- } else {
- code, err := strconv.Atoi(part)
- if err == nil {
- codes = append(codes, code)
- }
- }
- }
- return codes
- }
|