/pack/bundle/opt/fzf/src/ansi.go

https://bitbucket.org/skeept/dotvim · Go · 292 lines · 247 code · 27 blank · 18 comment · 91 complexity · b8dd2b3d09808138d7fdf121387cd3e3 MD5 · raw file

  1. package fzf
  2. import (
  3. "bytes"
  4. "regexp"
  5. "strconv"
  6. "strings"
  7. "unicode/utf8"
  8. "github.com/junegunn/fzf/src/tui"
  9. )
  10. type ansiOffset struct {
  11. offset [2]int32
  12. color ansiState
  13. }
  14. type ansiState struct {
  15. fg tui.Color
  16. bg tui.Color
  17. attr tui.Attr
  18. }
  19. func (s *ansiState) colored() bool {
  20. return s.fg != -1 || s.bg != -1 || s.attr > 0
  21. }
  22. func (s *ansiState) equals(t *ansiState) bool {
  23. if t == nil {
  24. return !s.colored()
  25. }
  26. return s.fg == t.fg && s.bg == t.bg && s.attr == t.attr
  27. }
  28. func (s *ansiState) ToString() string {
  29. if !s.colored() {
  30. return ""
  31. }
  32. ret := ""
  33. if s.attr&tui.Bold > 0 {
  34. ret += "1;"
  35. }
  36. if s.attr&tui.Dim > 0 {
  37. ret += "2;"
  38. }
  39. if s.attr&tui.Italic > 0 {
  40. ret += "3;"
  41. }
  42. if s.attr&tui.Underline > 0 {
  43. ret += "4;"
  44. }
  45. if s.attr&tui.Blink > 0 {
  46. ret += "5;"
  47. }
  48. if s.attr&tui.Reverse > 0 {
  49. ret += "7;"
  50. }
  51. ret += toAnsiString(s.fg, 30) + toAnsiString(s.bg, 40)
  52. return "\x1b[" + strings.TrimSuffix(ret, ";") + "m"
  53. }
  54. func toAnsiString(color tui.Color, offset int) string {
  55. col := int(color)
  56. ret := ""
  57. if col == -1 {
  58. ret += strconv.Itoa(offset + 9)
  59. } else if col < 8 {
  60. ret += strconv.Itoa(offset + col)
  61. } else if col < 16 {
  62. ret += strconv.Itoa(offset - 30 + 90 + col - 8)
  63. } else if col < 256 {
  64. ret += strconv.Itoa(offset+8) + ";5;" + strconv.Itoa(col)
  65. } else if col >= (1 << 24) {
  66. r := strconv.Itoa((col >> 16) & 0xff)
  67. g := strconv.Itoa((col >> 8) & 0xff)
  68. b := strconv.Itoa(col & 0xff)
  69. ret += strconv.Itoa(offset+8) + ";2;" + r + ";" + g + ";" + b
  70. }
  71. return ret + ";"
  72. }
  73. var ansiRegex *regexp.Regexp
  74. func init() {
  75. /*
  76. References:
  77. - https://github.com/gnachman/iTerm2
  78. - http://ascii-table.com/ansi-escape-sequences.php
  79. - http://ascii-table.com/ansi-escape-sequences-vt-100.php
  80. - http://tldp.org/HOWTO/Bash-Prompt-HOWTO/x405.html
  81. - https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
  82. */
  83. // The following regular expression will include not all but most of the
  84. // frequently used ANSI sequences
  85. ansiRegex = regexp.MustCompile("(?:\x1b[\\[()][0-9;]*[a-zA-Z@]|\x1b][0-9];[[:print:]]+(?:\x1b\\\\|\x07)|\x1b.|[\x0e\x0f]|.\x08)")
  86. }
  87. func findAnsiStart(str string) int {
  88. idx := 0
  89. for ; idx < len(str); idx++ {
  90. b := str[idx]
  91. if b == 0x1b || b == 0x0e || b == 0x0f {
  92. return idx
  93. }
  94. if b == 0x08 && idx > 0 {
  95. return idx - 1
  96. }
  97. }
  98. return idx
  99. }
  100. func extractColor(str string, state *ansiState, proc func(string, *ansiState) bool) (string, *[]ansiOffset, *ansiState) {
  101. var offsets []ansiOffset
  102. var output bytes.Buffer
  103. if state != nil {
  104. offsets = append(offsets, ansiOffset{[2]int32{0, 0}, *state})
  105. }
  106. prevIdx := 0
  107. runeCount := 0
  108. for idx := 0; idx < len(str); {
  109. idx += findAnsiStart(str[idx:])
  110. if idx == len(str) {
  111. break
  112. }
  113. // Make sure that we found an ANSI code
  114. offset := ansiRegex.FindStringIndex(str[idx:])
  115. if len(offset) < 2 {
  116. idx++
  117. continue
  118. }
  119. offset[0] += idx
  120. offset[1] += idx
  121. idx = offset[1]
  122. // Check if we should continue
  123. prev := str[prevIdx:offset[0]]
  124. if proc != nil && !proc(prev, state) {
  125. return "", nil, nil
  126. }
  127. prevIdx = offset[1]
  128. runeCount += utf8.RuneCountInString(prev)
  129. output.WriteString(prev)
  130. newState := interpretCode(str[offset[0]:offset[1]], state)
  131. if !newState.equals(state) {
  132. if state != nil {
  133. // Update last offset
  134. (&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
  135. }
  136. if newState.colored() {
  137. // Append new offset
  138. state = newState
  139. offsets = append(offsets, ansiOffset{[2]int32{int32(runeCount), int32(runeCount)}, *state})
  140. } else {
  141. // Discard state
  142. state = nil
  143. }
  144. }
  145. }
  146. var rest string
  147. var trimmed string
  148. if prevIdx == 0 {
  149. // No ANSI code found
  150. rest = str
  151. trimmed = str
  152. } else {
  153. rest = str[prevIdx:]
  154. output.WriteString(rest)
  155. trimmed = output.String()
  156. }
  157. if len(rest) > 0 && state != nil {
  158. // Update last offset
  159. runeCount += utf8.RuneCountInString(rest)
  160. (&offsets[len(offsets)-1]).offset[1] = int32(runeCount)
  161. }
  162. if proc != nil {
  163. proc(rest, state)
  164. }
  165. if len(offsets) == 0 {
  166. return trimmed, nil, state
  167. }
  168. return trimmed, &offsets, state
  169. }
  170. func interpretCode(ansiCode string, prevState *ansiState) *ansiState {
  171. // State
  172. var state *ansiState
  173. if prevState == nil {
  174. state = &ansiState{-1, -1, 0}
  175. } else {
  176. state = &ansiState{prevState.fg, prevState.bg, prevState.attr}
  177. }
  178. if ansiCode[0] != '\x1b' || ansiCode[1] != '[' || ansiCode[len(ansiCode)-1] != 'm' {
  179. return state
  180. }
  181. ptr := &state.fg
  182. state256 := 0
  183. init := func() {
  184. state.fg = -1
  185. state.bg = -1
  186. state.attr = 0
  187. state256 = 0
  188. }
  189. ansiCode = ansiCode[2 : len(ansiCode)-1]
  190. if len(ansiCode) == 0 {
  191. init()
  192. }
  193. for _, code := range strings.Split(ansiCode, ";") {
  194. if num, err := strconv.Atoi(code); err == nil {
  195. switch state256 {
  196. case 0:
  197. switch num {
  198. case 38:
  199. ptr = &state.fg
  200. state256++
  201. case 48:
  202. ptr = &state.bg
  203. state256++
  204. case 39:
  205. state.fg = -1
  206. case 49:
  207. state.bg = -1
  208. case 1:
  209. state.attr = state.attr | tui.Bold
  210. case 2:
  211. state.attr = state.attr | tui.Dim
  212. case 3:
  213. state.attr = state.attr | tui.Italic
  214. case 4:
  215. state.attr = state.attr | tui.Underline
  216. case 5:
  217. state.attr = state.attr | tui.Blink
  218. case 7:
  219. state.attr = state.attr | tui.Reverse
  220. case 23: // tput rmso
  221. state.attr = state.attr &^ tui.Italic
  222. case 24: // tput rmul
  223. state.attr = state.attr &^ tui.Underline
  224. case 0:
  225. init()
  226. default:
  227. if num >= 30 && num <= 37 {
  228. state.fg = tui.Color(num - 30)
  229. } else if num >= 40 && num <= 47 {
  230. state.bg = tui.Color(num - 40)
  231. } else if num >= 90 && num <= 97 {
  232. state.fg = tui.Color(num - 90 + 8)
  233. } else if num >= 100 && num <= 107 {
  234. state.bg = tui.Color(num - 100 + 8)
  235. }
  236. }
  237. case 1:
  238. switch num {
  239. case 2:
  240. state256 = 10 // MAGIC
  241. case 5:
  242. state256++
  243. default:
  244. state256 = 0
  245. }
  246. case 2:
  247. *ptr = tui.Color(num)
  248. state256 = 0
  249. case 10:
  250. *ptr = tui.Color(1<<24) | tui.Color(num<<16)
  251. state256++
  252. case 11:
  253. *ptr = *ptr | tui.Color(num<<8)
  254. state256++
  255. case 12:
  256. *ptr = *ptr | tui.Color(num)
  257. state256 = 0
  258. }
  259. }
  260. }
  261. if state256 > 0 {
  262. *ptr = -1
  263. }
  264. return state
  265. }