colorable_windows.go 3.6 KB

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