colorable_windows.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  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 NewColorableStderr() io.Writer {
  58. return &Writer{out: syscall.Handle(os.Stderr.Fd())}
  59. }
  60. func (w *Writer) Write(data []byte) (n int, err error) {
  61. var csbi consoleScreenBufferInfo
  62. procGetConsoleScreenBufferInfo.Call(uintptr(w.out), uintptr(unsafe.Pointer(&csbi)))
  63. attr_old := csbi.attributes
  64. defer func() {
  65. procSetConsoleTextAttribute.Call(uintptr(w.out), uintptr(attr_old))
  66. }()
  67. er := bytes.NewBuffer(data)
  68. loop:
  69. for {
  70. r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.out), uintptr(unsafe.Pointer(&csbi)))
  71. if r1 == 0 {
  72. break loop
  73. }
  74. c1, _, err := er.ReadRune()
  75. if err != nil {
  76. break loop
  77. }
  78. if c1 != 0x1b {
  79. fmt.Print(string(c1))
  80. continue
  81. }
  82. c2, _, err := er.ReadRune()
  83. if err != nil {
  84. w.lastbuf.WriteRune(c1)
  85. break loop
  86. }
  87. if c2 != 0x5b {
  88. w.lastbuf.WriteRune(c1)
  89. w.lastbuf.WriteRune(c2)
  90. continue
  91. }
  92. var buf bytes.Buffer
  93. var m rune
  94. for {
  95. c, _, err := er.ReadRune()
  96. if err != nil {
  97. w.lastbuf.WriteRune(c1)
  98. w.lastbuf.WriteRune(c2)
  99. w.lastbuf.Write(buf.Bytes())
  100. break loop
  101. }
  102. if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
  103. m = c
  104. break
  105. }
  106. buf.Write([]byte(string(c)))
  107. }
  108. switch m {
  109. case 'm':
  110. attr := csbi.attributes
  111. cs := buf.String()
  112. if cs == "" {
  113. procSetConsoleTextAttribute.Call(uintptr(w.out), uintptr(attr_old))
  114. continue
  115. }
  116. for _, ns := range strings.Split(cs, ";") {
  117. if n, err = strconv.Atoi(ns); err == nil {
  118. switch {
  119. case n == 0 || n == 100:
  120. attr = attr_old
  121. case 1 <= n && n <= 5:
  122. attr |= foregroundIntensity
  123. case n == 7:
  124. attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
  125. case 22 == n || n == 25 || n == 25:
  126. attr |= foregroundIntensity
  127. case n == 27:
  128. attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
  129. case 30 <= n && n <= 37:
  130. attr = (attr & backgroundMask)
  131. if (n-30)&1 != 0 {
  132. attr |= foregroundRed
  133. }
  134. if (n-30)&2 != 0 {
  135. attr |= foregroundGreen
  136. }
  137. if (n-30)&4 != 0 {
  138. attr |= foregroundBlue
  139. }
  140. case 40 <= n && n <= 47:
  141. attr = (attr & foregroundMask)
  142. if (n-40)&1 != 0 {
  143. attr |= backgroundRed
  144. }
  145. if (n-40)&2 != 0 {
  146. attr |= backgroundGreen
  147. }
  148. if (n-40)&4 != 0 {
  149. attr |= backgroundBlue
  150. }
  151. }
  152. procSetConsoleTextAttribute.Call(uintptr(w.out), uintptr(attr))
  153. }
  154. }
  155. }
  156. }
  157. return len(data) - w.lastbuf.Len(), nil
  158. }