16 "github.com/mattn/go-isatty"
23 foregroundIntensity = 0x8
24 foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
26 backgroundGreen = 0x20
28 backgroundIntensity = 0x80
29 backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
33 genericRead = 0x80000000
34 genericWrite = 0x40000000
38 consoleTextmodeBuffer = 0x1
51 type smallRect struct {
58 type consoleScreenBufferInfo struct {
63 maximumWindowSize coord
66 type consoleCursorInfo struct {
72 kernel32 = syscall.NewLazyDLL("kernel32.dll")
73 procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
74 procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
75 procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
76 procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
77 procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
78 procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo")
79 procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo")
80 procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW")
81 procCreateConsoleScreenBuffer = kernel32.NewProc("CreateConsoleScreenBuffer")
84 // Writer provide colorable Writer to the console
88 althandle syscall.Handle
94 // NewColorable return new instance of Writer which handle escape sequence from File.
95 func NewColorable(file *os.File) io.Writer {
97 panic("nil passed instead of *os.File to NewColorable()")
100 if isatty.IsTerminal(file.Fd()) {
101 var csbi consoleScreenBufferInfo
102 handle := syscall.Handle(file.Fd())
103 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
104 return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}}
109 // NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
110 func NewColorableStdout() io.Writer {
111 return NewColorable(os.Stdout)
114 // NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
115 func NewColorableStderr() io.Writer {
116 return NewColorable(os.Stderr)
119 var color256 = map[int]int{
378 // `\033]0;TITLESTR\007`
379 func doTitleSequence(er *bytes.Reader) error {
383 c, err = er.ReadByte()
387 if c != '0' && c != '2' {
390 c, err = er.ReadByte()
397 title := make([]byte, 0, 80)
399 c, err = er.ReadByte()
403 if c == 0x07 || c == '\n' {
406 title = append(title, c)
409 title8, err := syscall.UTF16PtrFromString(string(title))
411 procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8)))
417 // Write write data on console
418 func (w *Writer) Write(data []byte) (n int, err error) {
419 var csbi consoleScreenBufferInfo
420 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
425 if w.rest.Len() > 0 {
426 var rest bytes.Buffer
427 w.rest.WriteTo(&rest)
430 er = bytes.NewReader(rest.Bytes())
432 er = bytes.NewReader(data)
437 c1, err := er.ReadByte()
446 c2, err := er.ReadByte()
458 if bytes.IndexByte(w.rest.Bytes(), 0x07) == -1 {
461 er = bytes.NewReader(w.rest.Bytes()[2:])
462 err := doTitleSequence(er)
468 // https://github.com/mattn/go-colorable/issues/27
470 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
471 w.oldpos = csbi.cursorPosition
474 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
477 // execute part after switch
488 for i, c := range w.rest.Bytes()[2:] {
489 if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
491 er = bytes.NewReader(w.rest.Bytes()[2+i+1:])
495 buf.Write([]byte(string(c)))
503 n, err = strconv.Atoi(buf.String())
507 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
508 csbi.cursorPosition.y -= short(n)
509 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
511 n, err = strconv.Atoi(buf.String())
515 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
516 csbi.cursorPosition.y += short(n)
517 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
519 n, err = strconv.Atoi(buf.String())
523 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
524 csbi.cursorPosition.x += short(n)
525 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
527 n, err = strconv.Atoi(buf.String())
531 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
532 csbi.cursorPosition.x -= short(n)
533 if csbi.cursorPosition.x < 0 {
534 csbi.cursorPosition.x = 0
536 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
538 n, err = strconv.Atoi(buf.String())
542 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
543 csbi.cursorPosition.x = 0
544 csbi.cursorPosition.y += short(n)
545 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
547 n, err = strconv.Atoi(buf.String())
551 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
552 csbi.cursorPosition.x = 0
553 csbi.cursorPosition.y -= short(n)
554 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
556 n, err = strconv.Atoi(buf.String())
560 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
561 csbi.cursorPosition.x = short(n - 1)
562 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
564 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
566 token := strings.Split(buf.String(), ";")
569 n1, err := strconv.Atoi(token[0])
573 csbi.cursorPosition.y = short(n1 - 1)
575 n1, err := strconv.Atoi(token[0])
579 n2, err := strconv.Atoi(token[1])
583 csbi.cursorPosition.x = short(n2 - 1)
584 csbi.cursorPosition.y = short(n1 - 1)
587 csbi.cursorPosition.y = 0
589 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
593 n, err = strconv.Atoi(buf.String())
598 var count, written dword
600 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
603 cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
604 count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
606 cursor = coord{x: csbi.window.left, y: csbi.window.top}
607 count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.window.top-csbi.cursorPosition.y)*dword(csbi.size.x)
609 cursor = coord{x: csbi.window.left, y: csbi.window.top}
610 count = dword(csbi.size.x) - dword(csbi.cursorPosition.x) + dword(csbi.size.y-csbi.cursorPosition.y)*dword(csbi.size.x)
612 procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
613 procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
617 n, err = strconv.Atoi(buf.String())
622 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
624 var count, written dword
627 cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
628 count = dword(csbi.size.x - csbi.cursorPosition.x)
630 cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
631 count = dword(csbi.size.x - csbi.cursorPosition.x)
633 cursor = coord{x: csbi.window.left, y: csbi.cursorPosition.y}
634 count = dword(csbi.size.x)
636 procFillConsoleOutputCharacter.Call(uintptr(handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
637 procFillConsoleOutputAttribute.Call(uintptr(handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
639 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
640 attr := csbi.attributes
643 procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(w.oldattr))
646 token := strings.Split(cs, ";")
647 for i := 0; i < len(token); i++ {
649 if n, err = strconv.Atoi(ns); err == nil {
651 case n == 0 || n == 100:
653 case 1 <= n && n <= 5:
654 attr |= foregroundIntensity
656 attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
657 case n == 22 || n == 25:
658 attr |= foregroundIntensity
660 attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
661 case 30 <= n && n <= 37:
662 attr &= backgroundMask
664 attr |= foregroundRed
667 attr |= foregroundGreen
670 attr |= foregroundBlue
672 case n == 38: // set foreground color.
673 if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") {
674 if n256, err := strconv.Atoi(token[i+2]); err == nil {
675 if n256foreAttr == nil {
678 attr &= backgroundMask
679 attr |= n256foreAttr[n256]
682 } else if len(token) == 5 && token[i+1] == "2" {
684 r, _ = strconv.Atoi(token[i+2])
685 g, _ = strconv.Atoi(token[i+3])
686 b, _ = strconv.Atoi(token[i+4])
689 attr |= foregroundRed
692 attr |= foregroundGreen
695 attr |= foregroundBlue
698 attr = attr & (w.oldattr & backgroundMask)
700 case n == 39: // reset foreground color.
701 attr &= backgroundMask
702 attr |= w.oldattr & foregroundMask
703 case 40 <= n && n <= 47:
704 attr &= foregroundMask
706 attr |= backgroundRed
709 attr |= backgroundGreen
712 attr |= backgroundBlue
714 case n == 48: // set background color.
715 if i < len(token)-2 && token[i+1] == "5" {
716 if n256, err := strconv.Atoi(token[i+2]); err == nil {
717 if n256backAttr == nil {
720 attr &= foregroundMask
721 attr |= n256backAttr[n256]
724 } else if len(token) == 5 && token[i+1] == "2" {
726 r, _ = strconv.Atoi(token[i+2])
727 g, _ = strconv.Atoi(token[i+3])
728 b, _ = strconv.Atoi(token[i+4])
731 attr |= backgroundRed
734 attr |= backgroundGreen
737 attr |= backgroundBlue
740 attr = attr & (w.oldattr & foregroundMask)
742 case n == 49: // reset foreground color.
743 attr &= foregroundMask
744 attr |= w.oldattr & backgroundMask
745 case 90 <= n && n <= 97:
746 attr = (attr & backgroundMask)
747 attr |= foregroundIntensity
749 attr |= foregroundRed
752 attr |= foregroundGreen
755 attr |= foregroundBlue
757 case 100 <= n && n <= 107:
758 attr = (attr & foregroundMask)
759 attr |= backgroundIntensity
761 attr |= backgroundRed
764 attr |= backgroundGreen
767 attr |= backgroundBlue
770 procSetConsoleTextAttribute.Call(uintptr(handle), uintptr(attr))
774 var ci consoleCursorInfo
777 procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
779 procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
780 } else if cs == "?25" {
781 procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
783 procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
784 } else if cs == "?1049" {
785 if w.althandle == 0 {
786 h, _, _ := procCreateConsoleScreenBuffer.Call(uintptr(genericRead|genericWrite), 0, 0, uintptr(consoleTextmodeBuffer), 0, 0)
787 w.althandle = syscall.Handle(h)
788 if w.althandle != 0 {
794 var ci consoleCursorInfo
797 procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
799 procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
800 } else if cs == "?25" {
801 procGetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
803 procSetConsoleCursorInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&ci)))
804 } else if cs == "?1049" {
805 if w.althandle != 0 {
806 syscall.CloseHandle(w.althandle)
812 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
813 w.oldpos = csbi.cursorPosition
815 procSetConsoleCursorPosition.Call(uintptr(handle), *(*uintptr)(unsafe.Pointer(&w.oldpos)))
819 return len(data), nil
822 type consoleColor struct {
830 func (c consoleColor) foregroundAttr() (attr word) {
832 attr |= foregroundRed
835 attr |= foregroundGreen
838 attr |= foregroundBlue
841 attr |= foregroundIntensity
846 func (c consoleColor) backgroundAttr() (attr word) {
848 attr |= backgroundRed
851 attr |= backgroundGreen
854 attr |= backgroundBlue
857 attr |= backgroundIntensity
862 var color16 = []consoleColor{
863 {0x000000, false, false, false, false},
864 {0x000080, false, false, true, false},
865 {0x008000, false, true, false, false},
866 {0x008080, false, true, true, false},
867 {0x800000, true, false, false, false},
868 {0x800080, true, false, true, false},
869 {0x808000, true, true, false, false},
870 {0xc0c0c0, true, true, true, false},
871 {0x808080, false, false, false, true},
872 {0x0000ff, false, false, true, true},
873 {0x00ff00, false, true, false, true},
874 {0x00ffff, false, true, true, true},
875 {0xff0000, true, false, false, true},
876 {0xff00ff, true, false, true, true},
877 {0xffff00, true, true, false, true},
878 {0xffffff, true, true, true, true},
885 func (a hsv) dist(b hsv) float32 {
895 return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv)))
898 func toHSV(rgb int) hsv {
899 r, g, b := float32((rgb&0xFF0000)>>16)/256.0,
900 float32((rgb&0x00FF00)>>8)/256.0,
901 float32(rgb&0x0000FF)/256.0
902 min, max := minmax3f(r, g, b)
922 return hsv{h: h, s: s, v: v}
927 func toHSVTable(rgbTable []consoleColor) hsvTable {
928 t := make(hsvTable, len(rgbTable))
929 for i, c := range rgbTable {
935 func (t hsvTable) find(rgb int) consoleColor {
939 for i, p := range t {
948 func minmax3f(a, b, c float32) (min, max float32) {
968 var n256foreAttr []word
969 var n256backAttr []word
972 n256foreAttr = make([]word, 256)
973 n256backAttr = make([]word, 256)
974 t := toHSVTable(color16)
975 for i, rgb := range color256 {
977 n256foreAttr[i] = c.foregroundAttr()
978 n256backAttr[i] = c.backgroundAttr()