write.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. package door
  2. import (
  3. "log"
  4. "regexp"
  5. "strconv"
  6. "strings"
  7. )
  8. var FindANSIColor *regexp.Regexp
  9. func init() {
  10. FindANSIColor = regexp.MustCompile("\x1b\\[([0-9;]*)m")
  11. }
  12. /*
  13. find \x1b[0;1;37;40m codes.
  14. return array of start/end positions in the given string.
  15. */
  16. func find_ansicolor(text string) [][]int {
  17. var color_codes [][]int
  18. // word, _ := regexp.Compile("\x1b\\[([0-9;]+)m")
  19. colors := FindANSIColor.FindAllStringIndex(text, -1)
  20. // regexp seems to be ignoring the capture groups.
  21. // colors := word.FindAllSubmatchIndex([]byte(text), len(text))
  22. for _, pos := range colors {
  23. txt := text[pos[0]+2 : pos[1]-1]
  24. if txt == "" {
  25. txt = "0"
  26. }
  27. // log.Printf("Text: [%s]\n", txt)
  28. codes := strings.Split(txt, ";")
  29. // log.Printf("Codes: [%#v]\n", codes)
  30. code := make([]int, len(codes))
  31. for idx, c := range codes {
  32. var err error
  33. code[idx], err = strconv.Atoi(c)
  34. if err != nil {
  35. log.Printf("Atoi: %#v [%s]\n", err, c)
  36. }
  37. color_codes = append(color_codes, code)
  38. }
  39. }
  40. return color_codes
  41. }
  42. type ANSIColorParts struct {
  43. Bold bool
  44. Blink bool
  45. FG int
  46. BG int
  47. }
  48. // Parse an array of codes into their ANSIColorParts.
  49. func ParseColorArray(colorarray []int) ANSIColorParts {
  50. var acp ANSIColorParts
  51. acp.FG = -1
  52. acp.BG = -1
  53. for _, c := range colorarray {
  54. switch c {
  55. case 0:
  56. acp.FG = 7
  57. acp.BG = 0
  58. case 1:
  59. acp.Bold = true
  60. case 5:
  61. acp.Blink = true
  62. case 30, 31, 32, 33, 34, 35, 36, 37:
  63. acp.FG = c - 30
  64. case 40, 41, 42, 43, 44, 45, 46, 47:
  65. acp.BG = c - 40
  66. }
  67. }
  68. return acp
  69. }
  70. // Update the LastColor with the latest codes.
  71. func (d *Door) UpdateLastColor(output string, LastColor *[]int) {
  72. // use the last color information + whatever is in the string to
  73. // track the last color set
  74. updated := ParseColorArray(*LastColor)
  75. colors := find_ansicolor(output)
  76. for _, codes := range colors {
  77. if codes[0] == 0 {
  78. updated = ParseColorArray(codes)
  79. } else {
  80. newCode := ParseColorArray(codes)
  81. if newCode.Bold {
  82. updated.Bold = true
  83. }
  84. if newCode.Blink {
  85. updated.Blink = true
  86. }
  87. if (newCode.FG != -1) && (newCode.FG != updated.FG) {
  88. updated.FG = newCode.FG
  89. }
  90. if (newCode.BG != -1) && (newCode.BG != updated.BG) {
  91. updated.BG = newCode.BG
  92. }
  93. }
  94. }
  95. *LastColor = make([]int, 1)
  96. (*LastColor)[0] = 0
  97. if updated.Blink {
  98. *LastColor = append(*LastColor, 5)
  99. }
  100. if updated.Bold {
  101. *LastColor = append(*LastColor, 1)
  102. }
  103. if updated.FG != -1 {
  104. *LastColor = append(*LastColor, updated.FG+30)
  105. }
  106. if updated.BG != -1 {
  107. *LastColor = append(*LastColor, updated.BG+40)
  108. }
  109. }
  110. /*
  111. reading from a closed channel is easy to detect.
  112. res, ok := <-channel
  113. ok == false
  114. writing to a closed channel is a panic.
  115. */
  116. // var writeMutex sync.Mutex
  117. // Write string to client.
  118. func (d *Door) Write(output string) {
  119. if d.Disconnected {
  120. return
  121. }
  122. d.writerChannel <- output
  123. return
  124. // There is a potential race condition here --
  125. // We could be updating d.LastColor when called again.
  126. d.writerMutex.Lock()
  127. defer d.writerMutex.Unlock()
  128. defer func() {
  129. if r := recover(); r != nil {
  130. log.Println("Write error/HANGUP.")
  131. d.Disconnected = true
  132. }
  133. }()
  134. // Updating some other part of the screen.
  135. // Use SavePos/RestorePos in the same string.
  136. // We'll append the LastColor to the string,
  137. // allowing the update to not mess up current color.
  138. if strings.HasSuffix(output, RestorePos) {
  139. output += Color(d.LastColor...)
  140. } else {
  141. d.UpdateLastColor(output, &d.LastColor)
  142. }
  143. /*
  144. // Output the string, translating the ESC code to ^[ for outputting.
  145. temp := strings.Replace(output, "\x1b", "^[", -1)
  146. log.Printf("Parse: [%s]\n", temp)
  147. */
  148. d.writerChannel <- output
  149. }