1
2
3
4 package colorable
5
6 import (
7 "bytes"
8 "io"
9 "math"
10 "os"
11 "strconv"
12 "strings"
13 "sync"
14 "syscall"
15 "unsafe"
16
17 "github.com/mattn/go-isatty"
18 )
19
20 const (
21 foregroundBlue = 0x1
22 foregroundGreen = 0x2
23 foregroundRed = 0x4
24 foregroundIntensity = 0x8
25 foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
26 backgroundBlue = 0x10
27 backgroundGreen = 0x20
28 backgroundRed = 0x40
29 backgroundIntensity = 0x80
30 backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
31 commonLvbUnderscore = 0x8000
32
33 cENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4
34 )
35
36 const (
37 genericRead = 0x80000000
38 genericWrite = 0x40000000
39 )
40
41 const (
42 consoleTextmodeBuffer = 0x1
43 )
44
45 type wchar uint16
46 type short int16
47 type dword uint32
48 type word uint16
49
50 type coord struct {
51 x short
52 y short
53 }
54
55 type smallRect struct {
56 left short
57 top short
58 right short
59 bottom short
60 }
61
62 type consoleScreenBufferInfo struct {
63 size coord
64 cursorPosition coord
65 attributes word
66 window smallRect
67 maximumWindowSize coord
68 }
69
70 type consoleCursorInfo struct {
71 size dword
72 visible int32
73 }
74
75 var (
76 kernel32 = syscall.NewLazyDLL("kernel32.dll")
77 procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
78 procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
79 procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
80 procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
81 procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
82 procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
83 procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
84 procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW")
85 procGetConsoleMode = kernel32.NewProc("GetConsoleMode")
86 procSetConsoleMode = kernel32.NewProc("SetConsoleMode")
87 procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer")
88 )
89
90
91 type Writer struct {
92 out io.Writer
93 handle syscall.Handle
94 althandle syscall.Handle
95 oldattr word
96 oldpos coord
97 rest bytes.Buffer
98 mutex sync.Mutex
99 }
100
101
102 func NewColorable(file *os.File) io.Writer {
103 if file == nil {
104 panic("nil passed instead of *os.File to NewColorable()")
105 }
106
107 if isatty.IsTerminal(file.Fd()) {
108 var mode uint32
109 if r, _, _ := procGetConsoleMode.Call(file.Fd(), uintptr(unsafe.Pointer(&mode))); r != 0 && mode&cENABLE_VIRTUAL_TERMINAL_PROCESSING != 0 {
110 return file
111 }
112 var csbi consoleScreenBufferInfo
113 handle := syscall.Handle(file.Fd())
114 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
115 return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}}
116 }
117 return file
118 }
119
120
121 func NewColorableStdout() io.Writer {
122 return NewColorable(os.Stdout)
123 }
124
125
126 func NewColorableStderr() io.Writer {
127 return NewColorable(os.Stderr)
128 }
129
130 var color256 = map[int]int{
131 0: 0x000000,
132 1: 0x800000,
133 2: 0x008000,
134 3: 0x808000,
135 4: 0x000080,
136 5: 0x800080,
137 6: 0x008080,
138 7: 0xc0c0c0,
139 8: 0x808080,
140 9: 0xff0000,
141 10: 0x00ff00,
142 11: 0xffff00,
143 12: 0x0000ff,
144 13: 0xff00ff,
145 14: 0x00ffff,
146 15: 0xffffff,
147 16: 0x000000,
148 17: 0x00005f,
149 18: 0x000087,
150 19: 0x0000af,
151 20: 0x0000d7,
152 21: 0x0000ff,
153 22: 0x005f00,
154 23: 0x005f5f,
155 24: 0x005f87,
156 25: 0x005faf,
157 26: 0x005fd7,
158 27: 0x005fff,
159 28: 0x008700,
160 29: 0x00875f,
161 30: 0x008787,
162 31: 0x0087af,
163 32: 0x0087d7,
164 33: 0x0087ff,
165 34: 0x00af00,
166 35: 0x00af5f,
167 36: 0x00af87,
168 37: 0x00afaf,
169 38: 0x00afd7,
170 39: 0x00afff,
171 40: 0x00d700,
172 41: 0x00d75f,
173 42: 0x00d787,
174 43: 0x00d7af,
175 44: 0x00d7d7,
176 45: 0x00d7ff,
177 46: 0x00ff00,
178 47: 0x00ff5f,
179 48: 0x00ff87,
180 49: 0x00ffaf,
181 50: 0x00ffd7,
182 51: 0x00ffff,
183 52: 0x5f0000,
184 53: 0x5f005f,
185 54: 0x5f0087,
186 55: 0x5f00af,
187 56: 0x5f00d7,
188 57: 0x5f00ff,
189 58: 0x5f5f00,
190 59: 0x5f5f5f,
191 60: 0x5f5f87,
192 61: 0x5f5faf,
193 62: 0x5f5fd7,
194 63: 0x5f5fff,
195 64: 0x5f8700,
196 65: 0x5f875f,
197 66: 0x5f8787,
198 67: 0x5f87af,
199 68: 0x5f87d7,
200 69: 0x5f87ff,
201 70: 0x5faf00,
202 71: 0x5faf5f,
203 72: 0x5faf87,
204 73: 0x5fafaf,
205 74: 0x5fafd7,
206 75: 0x5fafff,
207 76: 0x5fd700,
208 77: 0x5fd75f,
209 78: 0x5fd787,
210 79: 0x5fd7af,
211 80: 0x5fd7d7,
212 81: 0x5fd7ff,
213 82: 0x5fff00,
214 83: 0x5fff5f,
215 84: 0x5fff87,
216 85: 0x5fffaf,
217 86: 0x5fffd7,
218 87: 0x5fffff,
219 88: 0x870000,
220 89: 0x87005f,
221 90: 0x870087,
222 91: 0x8700af,
223 92: 0x8700d7,
224 93: 0x8700ff,
225 94: 0x875f00,
226 95: 0x875f5f,
227 96: 0x875f87,
228 97: 0x875faf,
229 98: 0x875fd7,
230 99: 0x875fff,
231 100: 0x878700,
232 101: 0x87875f,
233 102: 0x878787,
234 103: 0x8787af,
235 104: 0x8787d7,
236 105: 0x8787ff,
237 106: 0x87af00,
238 107: 0x87af5f,
239 108: 0x87af87,
240 109: 0x87afaf,
241 110: 0x87afd7,
242 111: 0x87afff,
243 112: 0x87d700,
244 113: 0x87d75f,
245 114: 0x87d787,
246 115: 0x87d7af,
247 116: 0x87d7d7,
248 117: 0x87d7ff,
249 118: 0x87ff00,
250 119: 0x87ff5f,
251 120: 0x87ff87,
252 121: 0x87ffaf,
253 122: 0x87ffd7,
254 123: 0x87ffff,
255 124: 0xaf0000,
256 125: 0xaf005f,
257 126: 0xaf0087,
258 127: 0xaf00af,
259 128: 0xaf00d7,
260 129: 0xaf00ff,
261 130: 0xaf5f00,
262 131: 0xaf5f5f,
263 132: 0xaf5f87,
264 133: 0xaf5faf,
265 134: 0xaf5fd7,
266 135: 0xaf5fff,
267 136: 0xaf8700,
268 137: 0xaf875f,
269 138: 0xaf8787,
270 139: 0xaf87af,
271 140: 0xaf87d7,
272 141: 0xaf87ff,
273 142: 0xafaf00,
274 143: 0xafaf5f,
275 144: 0xafaf87,
276 145: 0xafafaf,
277 146: 0xafafd7,
278 147: 0xafafff,
279 148: 0xafd700,
280 149: 0xafd75f,
281 150: 0xafd787,
282 151: 0xafd7af,
283 152: 0xafd7d7,
284 153: 0xafd7ff,
285 154: 0xafff00,
286 155: 0xafff5f,
287 156: 0xafff87,
288 157: 0xafffaf,
289 158: 0xafffd7,
290 159: 0xafffff,
291 160: 0xd70000,
292 161: 0xd7005f,
293 162: 0xd70087,
294 163: 0xd700af,
295 164: 0xd700d7,
296 165: 0xd700ff,
297 166: 0xd75f00,
298 167: 0xd75f5f,
299 168: 0xd75f87,
300 169: 0xd75faf,
301 170: 0xd75fd7,
302 171: 0xd75fff,
303 172: 0xd78700,
304 173: 0xd7875f,
305 174: 0xd78787,
306 175: 0xd787af,
307 176: 0xd787d7,
308 177: 0xd787ff,
309 178: 0xd7af00,
310 179: 0xd7af5f,
311 180: 0xd7af87,
312 181: 0xd7afaf,
313 182: 0xd7afd7,
314 183: 0xd7afff,
315 184: 0xd7d700,
316 185: 0xd7d75f,
317 186: 0xd7d787,
318 187: 0xd7d7af,
319 188: 0xd7d7d7,
320 189: 0xd7d7ff,
321 190: 0xd7ff00,
322 191: 0xd7ff5f,
323 192: 0xd7ff87,
324 193: 0xd7ffaf,
325 194: 0xd7ffd7,
326 195: 0xd7ffff,
327 196: 0xff0000,
328 197: 0xff005f,
329 198: 0xff0087,
330 199: 0xff00af,
331 200: 0xff00d7,
332 201: 0xff00ff,
333 202: 0xff5f00,
334 203: 0xff5f5f,
335 204: 0xff5f87,
336 205: 0xff5faf,
337 206: 0xff5fd7,
338 207: 0xff5fff,
339 208: 0xff8700,
340 209: 0xff875f,
341 210: 0xff8787,
342 211: 0xff87af,
343 212: 0xff87d7,
344 213: 0xff87ff,
345 214: 0xffaf00,
346 215: 0xffaf5f,
347 216: 0xffaf87,
348 217: 0xffafaf,
349 218: 0xffafd7,
350 219: 0xffafff,
351 220: 0xffd700,
352 221: 0xffd75f,
353 222: 0xffd787,
354 223: 0xffd7af,
355 224: 0xffd7d7,
356 225: 0xffd7ff,
357 226: 0xffff00,
358 227: 0xffff5f,
359 228: 0xffff87,
360 229: 0xffffaf,
361 230: 0xffffd7,
362 231: 0xffffff,
363 232: 0x080808,
364 233: 0x121212,
365 234: 0x1c1c1c,
366 235: 0x262626,
367 236: 0x303030,
368 237: 0x3a3a3a,
369 238: 0x444444,
370 239: 0x4e4e4e,
371 240: 0x585858,
372 241: 0x626262,
373 242: 0x6c6c6c,
374 243: 0x767676,
375 244: 0x808080,
376 245: 0x8a8a8a,
377 246: 0x949494,
378 247: 0x9e9e9e,
379 248: 0xa8a8a8,
380 249: 0xb2b2b2,
381 250: 0xbcbcbc,
382 251: 0xc6c6c6,
383 252: 0xd0d0d0,
384 253: 0xdadada,
385 254: 0xe4e4e4,
386 255: 0xeeeeee,
387 }
388
389
390 func doTitleSequence(er *bytes.Reader) error {
391 var c byte
392 var err error
393
394 c, err = er.ReadByte()
395 if err != nil {
396 return err
397 }
398 if c != '0' && c != '2' {
399 return nil
400 }
401 c, err = er.ReadByte()
402 if err != nil {
403 return err
404 }
405 if c != ';' {
406 return nil
407 }
408 title := make([]byte, 0, 80)
409 for {
410 c, err = er.ReadByte()
411 if err != nil {
412 return err
413 }
414 if c == 0x07 || c == '\n' {
415 break
416 }
417 title = append(title, c)
418 }
419 if len(title) > 0 {
420 title8, err := syscall.UTF16PtrFromString(string(title))
421 if err == nil {
422 procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8)))
423 }
424 }
425 return nil
426 }
427
428
429 func atoiWithDefault(s string, def int) (int, error) {
430 if s == "" {
431 return def, nil
432 }
433 return strconv.Atoi(s)
434 }
435
436
437 func (w *Writer) Write(data []byte) (n int, err error) {
438 w.mutex.Lock()
439 defer w.mutex.Unlock()
440 var csbi consoleScreenBufferInfo
441 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
442
443 handle := w.handle
444
445 var er *bytes.Reader
446 if w.rest.Len() > 0 {
447 var rest bytes.Buffer
448 w.rest.WriteTo(&rest)
449 w.rest.Reset()
450 rest.Write(data)
451 er = bytes.NewReader(rest.Bytes())
452 } else {
453 er = bytes.NewReader(data)
454 }
455 var plaintext bytes.Buffer
456 loop:
457 for {
458 c1, err := er.ReadByte()
459 if err != nil {
460 plaintext.WriteTo(w.out)
461 break loop
462 }
463 if c1 != 0x1b {
464 plaintext.WriteByte(c1)
465 continue
466 }
467 _, err = plaintext.WriteTo(w.out)
468 if err != nil {
469 break loop
470 }
471 c2, err := er.ReadByte()
472 if err != nil {
473 break loop
474 }
475
476 switch c2 {
477 case '>':
478 continue
479 case ']':
480 w.rest.WriteByte(c1)
481 w.rest.WriteByte(c2)
482 er.WriteTo(&w.rest)
483 if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 {
484 break loop
485 }
486 er = bytes.NewReader(w.rest.Bytes()[2:])
487 err := doTitleSequence(er)
488 if err != nil {
489 break loop
490 }
491 w.rest.Reset()
492 continue
493
494 case '7':
495 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
496 w.oldpos = csbi.cursorPosition
497 continue
498 case '8':
499 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
500 continue
501 case 0x5b:
502
503 default:
504 continue
505 }
506
507 w.rest.WriteByte(c1)
508 w.rest.WriteByte(c2)
509 er.WriteTo(&w.rest)
510
511 var buf bytes.Buffer
512 var m byte
513 for i, c := range w.rest.Bytes()[2:] {
514 if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
515 m = c
516 er = bytes.NewReader(w.rest.Bytes()[2+i+1:])
517 w.rest.Reset()
518 break
519 }
520 buf.Write([]byte(string(c)))
521 }
522 if m == 0 {
523 break loop
524 }
525
526 switch m {
527 case 'A':
528 n, err = atoiWithDefault(buf.String(), 1)
529 if err != nil {
530 continue
531 }
532 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
533 csbi.cursorPosition.y -= short(n)
534 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
535 case 'B':
536 n, err = atoiWithDefault(buf.String(), 1)
537 if err != nil {
538 continue
539 }
540 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
541 csbi.cursorPosition.y += short(n)
542 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
543 case 'C':
544 n, err = atoiWithDefault(buf.String(), 1)
545 if err != nil {
546 continue
547 }
548 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
549 csbi.cursorPosition.x += short(n)
550 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
551 case 'D':
552 n, err = atoiWithDefault(buf.String(), 1)
553 if err != nil {
554 continue
555 }
556 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
557 csbi.cursorPosition.x -= short(n)
558 if csbi.cursorPosition.x < 0 {
559 csbi.cursorPosition.x = 0
560 }
561 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
562 case 'E':
563 n, err = strconv.Atoi(buf.String())
564 if err != nil {
565 continue
566 }
567 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
568 csbi.cursorPosition.x = 0
569 csbi.cursorPosition.y += short(n)
570 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
571 case 'F':
572 n, err = strconv.Atoi(buf.String())
573 if err != nil {
574 continue
575 }
576 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
577 csbi.cursorPosition.x = 0
578 csbi.cursorPosition.y -= short(n)
579 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
580 case 'G':
581 n, err = strconv.Atoi(buf.String())
582 if err != nil {
583 continue
584 }
585 if n < 1 {
586 n = 1
587 }
588 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
589 csbi.cursorPosition.x = short(n - 1)
590 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
591 case 'H', 'f':
592 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
593 if buf.Len() > 0 {
594 token := strings.Split(buf.String(), ";")
595 switch len(token) {
596 case 1:
597 n1, err := strconv.Atoi(token[0])
598 if err != nil {
599 continue
600 }
601 csbi.cursorPosition.y = short(n1 - 1)
602 case 2:
603 n1, err := strconv.Atoi(token[0])
604 if err != nil {
605 continue
606 }
607 n2, err := strconv.Atoi(token[1])
608 if err != nil {
609 continue
610 }
611 csbi.cursorPosition.x = short(n2 - 1)
612 csbi.cursorPosition.y = short(n1 - 1)
613 }
614 } else {
615 csbi.cursorPosition.y = 0
616 }
617 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
618 case 'J':
619 n := 0
620 if buf.Len() > 0 {
621 n, err = strconv.Atoi(buf.String())
622 if err != nil {
623 continue
624 }
625 }
626 var count, written dword
627 var cursor coord
628 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
629 switch n {
630 case 0:
631 cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
632 count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
633 case 1:
634 cursor = coord{x: csbi.window.left, y: csbi.window.top}
635 count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.window.top-csbi.cursorPosition.y)*dword(csbi.size.x)
636 case 2:
637 cursor = coord{x: csbi.window.left, y: csbi.window.top}
638 count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
639 }
640 procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
641 procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
642 case 'K':
643 n := 0
644 if buf.Len() > 0 {
645 n, err = strconv.Atoi(buf.String())
646 if err != nil {
647 continue
648 }
649 }
650 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
651 var cursor coord
652 var count, written dword
653 switch n {
654 case 0:
655 cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
656 count = dword(csbi.size.x - csbi.cursorPosition.x)
657 case 1:
658 cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
659 count = dword(csbi.size.x - csbi.cursorPosition.x)
660 case 2:
661 cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
662 count = dword(csbi.size.x)
663 }
664 procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
665 procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
666 case 'X':
667 n := 0
668 if buf.Len() > 0 {
669 n, err = strconv.Atoi(buf.String())
670 if err != nil {
671 continue
672 }
673 }
674 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
675 var cursor coord
676 var written dword
677 cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
678 procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(n), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
679 procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(n), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
680 case 'm':
681 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
682 attr := csbi.attributes
683 cs := buf.String()
684 if cs == "" {
685 procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(w.oldattr))
686 continue
687 }
688 token := strings.Split(cs, ";")
689 for i := 0; i < len(token); i++ {
690 ns := token[i]
691 if n, err = strconv.Atoi(ns); err == nil {
692 switch {
693 case n == 0 || n == 100:
694 attr = w.oldattr
695 case n == 4:
696 attr |= commonLvbUnderscore
697 case (1 <= n && n <= 3) || n == 5:
698 attr |= foregroundIntensity
699 case n == 7 || n == 27:
700 attr =
701 (attr &^ (foregroundMask | backgroundMask)) |
702 ((attr & foregroundMask) << 4) |
703 ((attr & backgroundMask) >> 4)
704 case n == 22:
705 attr &^= foregroundIntensity
706 case n == 24:
707 attr &^= commonLvbUnderscore
708 case 30 <= n && n <= 37:
709 attr &= backgroundMask
710 if (n-30)&1 != 0 {
711 attr |= foregroundRed
712 }
713 if (n-30)&2 != 0 {
714 attr |= foregroundGreen
715 }
716 if (n-30)&4 != 0 {
717 attr |= foregroundBlue
718 }
719 case n == 38:
720 if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") {
721 if n256, err := strconv.Atoi(token[i+2]); err == nil {
722 if n256foreAttr == nil {
723 n256setup()
724 }
725 attr &= backgroundMask
726 attr |= n256foreAttr[n256%len(n256foreAttr)]
727 i += 2
728 }
729 } else if len(token) == 5 && token[i+1] == "2" {
730 var r, g, b int
731 r, _ = strconv.Atoi(token[i+2])
732 g, _ = strconv.Atoi(token[i+3])
733 b, _ = strconv.Atoi(token[i+4])
734 i += 4
735 if r > 127 {
736 attr |= foregroundRed
737 }
738 if g > 127 {
739 attr |= foregroundGreen
740 }
741 if b > 127 {
742 attr |= foregroundBlue
743 }
744 } else {
745 attr = attr & (w.oldattr & backgroundMask)
746 }
747 case n == 39:
748 attr &= backgroundMask
749 attr |= w.oldattr & foregroundMask
750 case 40 <= n && n <= 47:
751 attr &= foregroundMask
752 if (n-40)&1 != 0 {
753 attr |= backgroundRed
754 }
755 if (n-40)&2 != 0 {
756 attr |= backgroundGreen
757 }
758 if (n-40)&4 != 0 {
759 attr |= backgroundBlue
760 }
761 case n == 48:
762 if i < len(token)-2 && token[i+1] == "5" {
763 if n256, err := strconv.Atoi(token[i+2]); err == nil {
764 if n256backAttr == nil {
765 n256setup()
766 }
767 attr &= foregroundMask
768 attr |= n256backAttr[n256%len(n256backAttr)]
769 i += 2
770 }
771 } else if len(token) == 5 && token[i+1] == "2" {
772 var r, g, b int
773 r, _ = strconv.Atoi(token[i+2])
774 g, _ = strconv.Atoi(token[i+3])
775 b, _ = strconv.Atoi(token[i+4])
776 i += 4
777 if r > 127 {
778 attr |= backgroundRed
779 }
780 if g > 127 {
781 attr |= backgroundGreen
782 }
783 if b > 127 {
784 attr |= backgroundBlue
785 }
786 } else {
787 attr = attr & (w.oldattr & foregroundMask)
788 }
789 case n == 49:
790 attr &= foregroundMask
791 attr |= w.oldattr & backgroundMask
792 case 90 <= n && n <= 97:
793 attr = (attr & backgroundMask)
794 attr |= foregroundIntensity
795 if (n-90)&1 != 0 {
796 attr |= foregroundRed
797 }
798 if (n-90)&2 != 0 {
799 attr |= foregroundGreen
800 }
801 if (n-90)&4 != 0 {
802 attr |= foregroundBlue
803 }
804 case 100 <= n && n <= 107:
805 attr = (attr & foregroundMask)
806 attr |= backgroundIntensity
807 if (n-100)&1 != 0 {
808 attr |= backgroundRed
809 }
810 if (n-100)&2 != 0 {
811 attr |= backgroundGreen
812 }
813 if (n-100)&4 != 0 {
814 attr |= backgroundBlue
815 }
816 }
817 procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(attr))
818 }
819 }
820 case 'h':
821 var ci consoleCursorInfo
822 cs := buf.String()
823 if cs == "5>" {
824 procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
825 ci.visible = 0
826 procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
827 } else if cs == "?25" {
828 procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
829 ci.visible = 1
830 procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
831 } else if cs == "?1049" {
832 if w.althandle == 0 {
833 h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0)
834 w.althandle = syscall.Handle(h)
835 if w.althandle != 0 {
836 handle = w.althandle
837 }
838 }
839 }
840 case 'l':
841 var ci consoleCursorInfo
842 cs := buf.String()
843 if cs == "5>" {
844 procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
845 ci.visible = 1
846 procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
847 } else if cs == "?25" {
848 procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
849 ci.visible = 0
850 procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
851 } else if cs == "?1049" {
852 if w.althandle != 0 {
853 syscall.CloseHandle(w.althandle)
854 w.althandle = 0
855 handle = w.handle
856 }
857 }
858 case 's':
859 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
860 w.oldpos = csbi.cursorPosition
861 case 'u':
862 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
863 }
864 }
865
866 return len(data), nil
867 }
868
869 type consoleColor struct {
870 rgb int
871 red bool
872 green bool
873 blue bool
874 intensity bool
875 }
876
877 func (c consoleColor) foregroundAttr() (attr word) {
878 if c.red {
879 attr |= foregroundRed
880 }
881 if c.green {
882 attr |= foregroundGreen
883 }
884 if c.blue {
885 attr |= foregroundBlue
886 }
887 if c.intensity {
888 attr |= foregroundIntensity
889 }
890 return
891 }
892
893 func (c consoleColor) backgroundAttr() (attr word) {
894 if c.red {
895 attr |= backgroundRed
896 }
897 if c.green {
898 attr |= backgroundGreen
899 }
900 if c.blue {
901 attr |= backgroundBlue
902 }
903 if c.intensity {
904 attr |= backgroundIntensity
905 }
906 return
907 }
908
909 var color16 = []consoleColor{
910 {0x000000, false, false, false, false},
911 {0x000080, false, false, true, false},
912 {0x008000, false, true, false, false},
913 {0x008080, false, true, true, false},
914 {0x800000, true, false, false, false},
915 {0x800080, true, false, true, false},
916 {0x808000, true, true, false, false},
917 {0xc0c0c0, true, true, true, false},
918 {0x808080, false, false, false, true},
919 {0x0000ff, false, false, true, true},
920 {0x00ff00, false, true, false, true},
921 {0x00ffff, false, true, true, true},
922 {0xff0000, true, false, false, true},
923 {0xff00ff, true, false, true, true},
924 {0xffff00, true, true, false, true},
925 {0xffffff, true, true, true, true},
926 }
927
928 type hsv struct {
929 h, s, v float32
930 }
931
932 func (a hsv) dist(b hsv) float32 {
933 dh := a.h - b.h
934 switch {
935 case dh > 0.5:
936 dh = 1 - dh
937 case dh < -0.5:
938 dh = -1 - dh
939 }
940 ds := a.s - b.s
941 dv := a.v - b.v
942 return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv)))
943 }
944
945 func toHSV(rgb int) hsv {
946 r, g, b := float32((rgb&0xFF0000)>>16)/256.0,
947 float32((rgb&0x00FF00)>>8)/256.0,
948 float32(rgb&0x0000FF)/256.0
949 min, max := minmax3f(r, g, b)
950 h := max - min
951 if h > 0 {
952 if max == r {
953 h = (g - b) / h
954 if h < 0 {
955 h += 6
956 }
957 } else if max == g {
958 h = 2 + (b-r)/h
959 } else {
960 h = 4 + (r-g)/h
961 }
962 }
963 h /= 6.0
964 s := max - min
965 if max != 0 {
966 s /= max
967 }
968 v := max
969 return hsv{h: h, s: s, v: v}
970 }
971
972 type hsvTable []hsv
973
974 func toHSVTable(rgbTable []consoleColor) hsvTable {
975 t := make(hsvTable, len(rgbTable))
976 for i, c := range rgbTable {
977 t[i] = toHSV(c.rgb)
978 }
979 return t
980 }
981
982 func (t hsvTable) find(rgb int) consoleColor {
983 hsv := toHSV(rgb)
984 n := 7
985 l := float32(5.0)
986 for i, p := range t {
987 d := hsv.dist(p)
988 if d < l {
989 l, n = d, i
990 }
991 }
992 return color16[n]
993 }
994
995 func minmax3f(a, b, c float32) (min, max float32) {
996 if a < b {
997 if b < c {
998 return a, c
999 } else if a < c {
1000 return a, b
1001 } else {
1002 return c, b
1003 }
1004 } else {
1005 if a < c {
1006 return b, c
1007 } else if b < c {
1008 return b, a
1009 } else {
1010 return c, a
1011 }
1012 }
1013 }
1014
1015 var n256foreAttr []word
1016 var n256backAttr []word
1017
1018 func n256setup() {
1019 n256foreAttr = make([]word, 256)
1020 n256backAttr = make([]word, 256)
1021 t := toHSVTable(color16)
1022 for i, rgb := range color256 {
1023 c := t.find(rgb)
1024 n256foreAttr[i] = c.foregroundAttr()
1025 n256backAttr[i] = c.backgroundAttr()
1026 }
1027 }
1028
1029
1030 func EnableColorsStdout(enabled *bool) func() {
1031 var mode uint32
1032 h := os.Stdout.Fd()
1033 if r, _, _ := procGetConsoleMode.Call(h, uintptr(unsafe.Pointer(&mode))); r != 0 {
1034 if r, _, _ = procSetConsoleMode.Call(h, uintptr(mode|cENABLE_VIRTUAL_TERMINAL_PROCESSING)); r != 0 {
1035 if enabled != nil {
1036 *enabled = true
1037 }
1038 return func() {
1039 procSetConsoleMode.Call(h, uintptr(mode))
1040 }
1041 }
1042 }
1043 if enabled != nil {
1044 *enabled = true
1045 }
1046 return func() {}
1047 }
1048
View as plain text