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.

249 lines
5.1 KiB

  1. // +build windows
  2. package readline
  3. import (
  4. "bufio"
  5. "io"
  6. "strconv"
  7. "strings"
  8. "sync"
  9. "unicode/utf8"
  10. "unsafe"
  11. )
  12. const (
  13. _ = uint16(0)
  14. COLOR_FBLUE = 0x0001
  15. COLOR_FGREEN = 0x0002
  16. COLOR_FRED = 0x0004
  17. COLOR_FINTENSITY = 0x0008
  18. COLOR_BBLUE = 0x0010
  19. COLOR_BGREEN = 0x0020
  20. COLOR_BRED = 0x0040
  21. COLOR_BINTENSITY = 0x0080
  22. COMMON_LVB_UNDERSCORE = 0x8000
  23. COMMON_LVB_BOLD = 0x0007
  24. )
  25. var ColorTableFg = []word{
  26. 0, // 30: Black
  27. COLOR_FRED, // 31: Red
  28. COLOR_FGREEN, // 32: Green
  29. COLOR_FRED | COLOR_FGREEN, // 33: Yellow
  30. COLOR_FBLUE, // 34: Blue
  31. COLOR_FRED | COLOR_FBLUE, // 35: Magenta
  32. COLOR_FGREEN | COLOR_FBLUE, // 36: Cyan
  33. COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White
  34. }
  35. var ColorTableBg = []word{
  36. 0, // 40: Black
  37. COLOR_BRED, // 41: Red
  38. COLOR_BGREEN, // 42: Green
  39. COLOR_BRED | COLOR_BGREEN, // 43: Yellow
  40. COLOR_BBLUE, // 44: Blue
  41. COLOR_BRED | COLOR_BBLUE, // 45: Magenta
  42. COLOR_BGREEN | COLOR_BBLUE, // 46: Cyan
  43. COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White
  44. }
  45. type ANSIWriter struct {
  46. target io.Writer
  47. wg sync.WaitGroup
  48. ctx *ANSIWriterCtx
  49. sync.Mutex
  50. }
  51. func NewANSIWriter(w io.Writer) *ANSIWriter {
  52. a := &ANSIWriter{
  53. target: w,
  54. ctx: NewANSIWriterCtx(w),
  55. }
  56. return a
  57. }
  58. func (a *ANSIWriter) Close() error {
  59. a.wg.Wait()
  60. return nil
  61. }
  62. type ANSIWriterCtx struct {
  63. isEsc bool
  64. isEscSeq bool
  65. arg []string
  66. target *bufio.Writer
  67. wantFlush bool
  68. }
  69. func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx {
  70. return &ANSIWriterCtx{
  71. target: bufio.NewWriter(target),
  72. }
  73. }
  74. func (a *ANSIWriterCtx) Flush() {
  75. a.target.Flush()
  76. }
  77. func (a *ANSIWriterCtx) process(r rune) bool {
  78. if a.wantFlush {
  79. if r == 0 || r == CharEsc {
  80. a.wantFlush = false
  81. a.target.Flush()
  82. }
  83. }
  84. if a.isEscSeq {
  85. a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg)
  86. return true
  87. }
  88. switch r {
  89. case CharEsc:
  90. a.isEsc = true
  91. case '[':
  92. if a.isEsc {
  93. a.arg = nil
  94. a.isEscSeq = true
  95. a.isEsc = false
  96. break
  97. }
  98. fallthrough
  99. default:
  100. a.target.WriteRune(r)
  101. a.wantFlush = true
  102. }
  103. return true
  104. }
  105. func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool {
  106. arg := *argptr
  107. var err error
  108. if r >= 'A' && r <= 'D' {
  109. count := short(GetInt(arg, 1))
  110. info, err := GetConsoleScreenBufferInfo()
  111. if err != nil {
  112. return false
  113. }
  114. switch r {
  115. case 'A': // up
  116. info.dwCursorPosition.y -= count
  117. case 'B': // down
  118. info.dwCursorPosition.y += count
  119. case 'C': // right
  120. info.dwCursorPosition.x += count
  121. case 'D': // left
  122. info.dwCursorPosition.x -= count
  123. }
  124. SetConsoleCursorPosition(&info.dwCursorPosition)
  125. return false
  126. }
  127. switch r {
  128. case 'J':
  129. killLines()
  130. case 'K':
  131. eraseLine()
  132. case 'm':
  133. color := word(0)
  134. for _, item := range arg {
  135. var c int
  136. c, err = strconv.Atoi(item)
  137. if err != nil {
  138. w.WriteString("[" + strings.Join(arg, ";") + "m")
  139. break
  140. }
  141. if c >= 30 && c < 40 {
  142. color ^= COLOR_FINTENSITY
  143. color |= ColorTableFg[c-30]
  144. } else if c >= 40 && c < 50 {
  145. color ^= COLOR_BINTENSITY
  146. color |= ColorTableBg[c-40]
  147. } else if c == 4 {
  148. color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7]
  149. } else if c == 1 {
  150. color |= COMMON_LVB_BOLD | COLOR_FINTENSITY
  151. } else { // unknown code treat as reset
  152. color = ColorTableFg[7]
  153. }
  154. }
  155. if err != nil {
  156. break
  157. }
  158. kernel.SetConsoleTextAttribute(stdout, uintptr(color))
  159. case '\007': // set title
  160. case ';':
  161. if len(arg) == 0 || arg[len(arg)-1] != "" {
  162. arg = append(arg, "")
  163. *argptr = arg
  164. }
  165. return true
  166. default:
  167. if len(arg) == 0 {
  168. arg = append(arg, "")
  169. }
  170. arg[len(arg)-1] += string(r)
  171. *argptr = arg
  172. return true
  173. }
  174. *argptr = nil
  175. return false
  176. }
  177. func (a *ANSIWriter) Write(b []byte) (int, error) {
  178. a.Lock()
  179. defer a.Unlock()
  180. off := 0
  181. for len(b) > off {
  182. r, size := utf8.DecodeRune(b[off:])
  183. if size == 0 {
  184. return off, io.ErrShortWrite
  185. }
  186. off += size
  187. a.ctx.process(r)
  188. }
  189. a.ctx.Flush()
  190. return off, nil
  191. }
  192. func killLines() error {
  193. sbi, err := GetConsoleScreenBufferInfo()
  194. if err != nil {
  195. return err
  196. }
  197. size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x
  198. size += sbi.dwCursorPosition.x
  199. var written int
  200. kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]),
  201. uintptr(size),
  202. sbi.dwCursorPosition.ptr(),
  203. uintptr(unsafe.Pointer(&written)),
  204. )
  205. return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
  206. uintptr(size),
  207. sbi.dwCursorPosition.ptr(),
  208. uintptr(unsafe.Pointer(&written)),
  209. )
  210. }
  211. func eraseLine() error {
  212. sbi, err := GetConsoleScreenBufferInfo()
  213. if err != nil {
  214. return err
  215. }
  216. size := sbi.dwSize.x
  217. sbi.dwCursorPosition.x = 0
  218. var written int
  219. return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
  220. uintptr(size),
  221. sbi.dwCursorPosition.ptr(),
  222. uintptr(unsafe.Pointer(&written)),
  223. )
  224. }