colorable_windows.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. package colorable
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "os"
  7. "strconv"
  8. "strings"
  9. "syscall"
  10. "unsafe"
  11. )
  12. const (
  13. foregroundBlue = 0x1
  14. foregroundGreen = 0x2
  15. foregroundRed = 0x4
  16. foregroundIntensity = 0x8
  17. foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
  18. backgroundBlue = 0x10
  19. backgroundGreen = 0x20
  20. backgroundRed = 0x40
  21. backgroundIntensity = 0x80
  22. backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
  23. )
  24. type wchar uint16
  25. type short int16
  26. type dword uint32
  27. type word uint16
  28. type coord struct {
  29. x short
  30. y short
  31. }
  32. type smallRect struct {
  33. left short
  34. top short
  35. right short
  36. bottom short
  37. }
  38. type consoleScreenBufferInfo struct {
  39. size coord
  40. cursorPosition coord
  41. attributes word
  42. window smallRect
  43. maximumWindowSize coord
  44. }
  45. var (
  46. kernel32 = syscall.NewLazyDLL("kernel32.dll")
  47. procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
  48. procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
  49. )
  50. type Writer struct {
  51. out syscall.Handle
  52. lastbuf bytes.Buffer
  53. }
  54. func NewColorableStdout() io.Writer {
  55. return &Writer{out: syscall.Handle(os.Stdout.Fd())}
  56. }
  57. func (w *Writer) Write(data []byte) (n int, err error) {
  58. var csbi consoleScreenBufferInfo
  59. procGetConsoleScreenBufferInfo.Call(uintptr(w.out), uintptr(unsafe.Pointer(&csbi)))
  60. attr_old := csbi.attributes
  61. defer func() {
  62. procSetConsoleTextAttribute.Call(uintptr(w.out), uintptr(attr_old))
  63. }()
  64. er := bytes.NewBuffer(data)
  65. loop:
  66. for {
  67. r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.out), uintptr(unsafe.Pointer(&csbi)))
  68. if r1 == 0 {
  69. break loop
  70. }
  71. c1, _, err := er.ReadRune()
  72. if err != nil {
  73. break loop
  74. }
  75. if c1 != 0x1b {
  76. fmt.Print(string(c1))
  77. continue
  78. }
  79. c2, _, err := er.ReadRune()
  80. if err != nil {
  81. w.lastbuf.WriteRune(c1)
  82. break loop
  83. }
  84. if c2 != 0x5b {
  85. w.lastbuf.WriteRune(c1)
  86. w.lastbuf.WriteRune(c2)
  87. continue
  88. }
  89. var buf bytes.Buffer
  90. var m rune
  91. for {
  92. c, _, err := er.ReadRune()
  93. if err != nil {
  94. w.lastbuf.WriteRune(c1)
  95. w.lastbuf.WriteRune(c2)
  96. w.lastbuf.Write(buf.Bytes())
  97. break loop
  98. }
  99. if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
  100. m = c
  101. break
  102. }
  103. buf.Write([]byte(string(c)))
  104. }
  105. switch m {
  106. case 'm':
  107. attr := csbi.attributes
  108. cs := buf.String()
  109. if cs == "" {
  110. procSetConsoleTextAttribute.Call(uintptr(w.out), uintptr(attr_old))
  111. continue
  112. }
  113. for _, ns := range strings.Split(cs, ";") {
  114. if n, err = strconv.Atoi(ns); err == nil {
  115. switch {
  116. case n == 0 || n == 100:
  117. attr = attr_old
  118. case 1 <= n && n <= 5:
  119. attr |= foregroundIntensity
  120. case n == 7:
  121. attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
  122. case 22 == n || n == 25 || n == 25:
  123. attr |= foregroundIntensity
  124. case n == 27:
  125. attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
  126. case 30 <= n && n <= 37:
  127. attr = (attr & backgroundMask)
  128. if (n-30)&1 != 0 {
  129. attr |= foregroundRed
  130. }
  131. if (n-30)&2 != 0 {
  132. attr |= foregroundGreen
  133. }
  134. if (n-30)&4 != 0 {
  135. attr |= foregroundBlue
  136. }
  137. case 40 <= n && n <= 47:
  138. attr = (attr & foregroundMask)
  139. if (n-40)&1 != 0 {
  140. attr |= backgroundRed
  141. }
  142. if (n-40)&2 != 0 {
  143. attr |= backgroundGreen
  144. }
  145. if (n-40)&4 != 0 {
  146. attr |= backgroundBlue
  147. }
  148. }
  149. procSetConsoleTextAttribute.Call(uintptr(w.out), uintptr(attr))
  150. }
  151. }
  152. }
  153. }
  154. return len(data) - w.lastbuf.Len(), nil
  155. }