forking tview to fork tcell to increase terminal input buffer length for password/key pasting
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ansi.go 6.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237
  1. package tview
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "strconv"
  7. "strings"
  8. )
  9. // The states of the ANSI escape code parser.
  10. const (
  11. ansiText = iota
  12. ansiEscape
  13. ansiSubstring
  14. ansiControlSequence
  15. )
  16. // ansi is a io.Writer which translates ANSI escape codes into tview color
  17. // tags.
  18. type ansi struct {
  19. io.Writer
  20. // Reusable buffers.
  21. buffer *bytes.Buffer // The entire output text of one Write().
  22. csiParameter, csiIntermediate *bytes.Buffer // Partial CSI strings.
  23. // The current state of the parser. One of the ansi constants.
  24. state int
  25. }
  26. // ANSIWriter returns an io.Writer which translates any ANSI escape codes
  27. // written to it into tview color tags. Other escape codes don't have an effect
  28. // and are simply removed. The translated text is written to the provided
  29. // writer.
  30. func ANSIWriter(writer io.Writer) io.Writer {
  31. return &ansi{
  32. Writer: writer,
  33. buffer: new(bytes.Buffer),
  34. csiParameter: new(bytes.Buffer),
  35. csiIntermediate: new(bytes.Buffer),
  36. state: ansiText,
  37. }
  38. }
  39. // Write parses the given text as a string of runes, translates ANSI escape
  40. // codes to color tags and writes them to the output writer.
  41. func (a *ansi) Write(text []byte) (int, error) {
  42. defer func() {
  43. a.buffer.Reset()
  44. }()
  45. for _, r := range string(text) {
  46. switch a.state {
  47. // We just entered an escape sequence.
  48. case ansiEscape:
  49. switch r {
  50. case '[': // Control Sequence Introducer.
  51. a.csiParameter.Reset()
  52. a.csiIntermediate.Reset()
  53. a.state = ansiControlSequence
  54. case 'c': // Reset.
  55. fmt.Fprint(a.buffer, "[-:-:-]")
  56. a.state = ansiText
  57. case 'P', ']', 'X', '^', '_': // Substrings and commands.
  58. a.state = ansiSubstring
  59. default: // Ignore.
  60. a.state = ansiText
  61. }
  62. // CSI Sequences.
  63. case ansiControlSequence:
  64. switch {
  65. case r >= 0x30 && r <= 0x3f: // Parameter bytes.
  66. if _, err := a.csiParameter.WriteRune(r); err != nil {
  67. return 0, err
  68. }
  69. case r >= 0x20 && r <= 0x2f: // Intermediate bytes.
  70. if _, err := a.csiIntermediate.WriteRune(r); err != nil {
  71. return 0, err
  72. }
  73. case r >= 0x40 && r <= 0x7e: // Final byte.
  74. switch r {
  75. case 'E': // Next line.
  76. count, _ := strconv.Atoi(a.csiParameter.String())
  77. if count == 0 {
  78. count = 1
  79. }
  80. fmt.Fprint(a.buffer, strings.Repeat("\n", count))
  81. case 'm': // Select Graphic Rendition.
  82. var (
  83. background, foreground, attributes string
  84. clearAttributes bool
  85. )
  86. fields := strings.Split(a.csiParameter.String(), ";")
  87. if len(fields) == 0 || len(fields) == 1 && fields[0] == "0" {
  88. // Reset.
  89. if _, err := a.buffer.WriteString("[-:-:-]"); err != nil {
  90. return 0, err
  91. }
  92. break
  93. }
  94. lookupColor := func(colorNumber int, bright bool) string {
  95. if colorNumber < 0 || colorNumber > 7 {
  96. return "black"
  97. }
  98. if bright {
  99. colorNumber += 8
  100. }
  101. return [...]string{
  102. "black",
  103. "red",
  104. "green",
  105. "yellow",
  106. "blue",
  107. "darkmagenta",
  108. "darkcyan",
  109. "white",
  110. "#7f7f7f",
  111. "#ff0000",
  112. "#00ff00",
  113. "#ffff00",
  114. "#5c5cff",
  115. "#ff00ff",
  116. "#00ffff",
  117. "#ffffff",
  118. }[colorNumber]
  119. }
  120. for index, field := range fields {
  121. switch field {
  122. case "1", "01":
  123. attributes += "b"
  124. case "2", "02":
  125. attributes += "d"
  126. case "4", "04":
  127. attributes += "u"
  128. case "5", "05":
  129. attributes += "l"
  130. case "7", "07":
  131. attributes += "7"
  132. case "22", "24", "25", "27":
  133. clearAttributes = true
  134. case "30", "31", "32", "33", "34", "35", "36", "37":
  135. colorNumber, _ := strconv.Atoi(field)
  136. foreground = lookupColor(colorNumber-30, false)
  137. case "40", "41", "42", "43", "44", "45", "46", "47":
  138. colorNumber, _ := strconv.Atoi(field)
  139. background = lookupColor(colorNumber-40, false)
  140. case "90", "91", "92", "93", "94", "95", "96", "97":
  141. colorNumber, _ := strconv.Atoi(field)
  142. foreground = lookupColor(colorNumber-90, true)
  143. case "100", "101", "102", "103", "104", "105", "106", "107":
  144. colorNumber, _ := strconv.Atoi(field)
  145. background = lookupColor(colorNumber-100, true)
  146. case "38", "48":
  147. var color string
  148. if len(fields) > index+1 {
  149. if fields[index+1] == "5" && len(fields) > index+2 { // 8-bit colors.
  150. colorNumber, _ := strconv.Atoi(fields[index+2])
  151. if colorNumber <= 7 {
  152. color = lookupColor(colorNumber, false)
  153. } else if colorNumber <= 15 {
  154. color = lookupColor(colorNumber, true)
  155. } else if colorNumber <= 231 {
  156. red := (colorNumber - 16) / 36
  157. green := ((colorNumber - 16) / 6) % 6
  158. blue := (colorNumber - 16) % 6
  159. color = fmt.Sprintf("#%02x%02x%02x", 255*red/5, 255*green/5, 255*blue/5)
  160. } else if colorNumber <= 255 {
  161. grey := 255 * (colorNumber - 232) / 23
  162. color = fmt.Sprintf("#%02x%02x%02x", grey, grey, grey)
  163. }
  164. } else if fields[index+1] == "2" && len(fields) > index+4 { // 24-bit colors.
  165. red, _ := strconv.Atoi(fields[index+2])
  166. green, _ := strconv.Atoi(fields[index+3])
  167. blue, _ := strconv.Atoi(fields[index+4])
  168. color = fmt.Sprintf("#%02x%02x%02x", red, green, blue)
  169. }
  170. }
  171. if len(color) > 0 {
  172. if field == "38" {
  173. foreground = color
  174. } else {
  175. background = color
  176. }
  177. }
  178. }
  179. }
  180. if len(attributes) > 0 || clearAttributes {
  181. attributes = ":" + attributes
  182. }
  183. if len(foreground) > 0 || len(background) > 0 || len(attributes) > 0 {
  184. fmt.Fprintf(a.buffer, "[%s:%s%s]", foreground, background, attributes)
  185. }
  186. }
  187. a.state = ansiText
  188. default: // Undefined byte.
  189. a.state = ansiText // Abort CSI.
  190. }
  191. // We just entered a substring/command sequence.
  192. case ansiSubstring:
  193. if r == 27 { // Most likely the end of the substring.
  194. a.state = ansiEscape
  195. } // Ignore all other characters.
  196. // "ansiText" and all others.
  197. default:
  198. if r == 27 {
  199. // This is the start of an escape sequence.
  200. a.state = ansiEscape
  201. } else {
  202. // Just a regular rune. Send to buffer.
  203. if _, err := a.buffer.WriteRune(r); err != nil {
  204. return 0, err
  205. }
  206. }
  207. }
  208. }
  209. // Write buffer to target writer.
  210. n, err := a.buffer.WriteTo(a.Writer)
  211. if err != nil {
  212. return int(n), err
  213. }
  214. return len(text), nil
  215. }
  216. // TranslateANSI replaces ANSI escape sequences found in the provided string
  217. // with tview's color tags and returns the resulting string.
  218. func TranslateANSI(text string) string {
  219. var buffer bytes.Buffer
  220. writer := ANSIWriter(&buffer)
  221. writer.Write([]byte(text))
  222. return buffer.String()
  223. }