parse_sgr.go 3.7 KB

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