diff --git a/_examples/color256.go b/_examples/color_256.go similarity index 100% rename from _examples/color256.go rename to _examples/color_256.go diff --git a/_examples/color_tag.go b/_examples/color_tag.go new file mode 100644 index 0000000..0281b64 --- /dev/null +++ b/_examples/color_tag.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/gookit/color" +) + +// go run ./_examples/color_tag1.go +func main() { + i := 0 + fmt.Println("Current env whether support color:", color.IsSupportColor()) + fmt.Print("All color tags:\n\n") + + for tag := range color.GetColorTags() { + if strings.Contains(tag, "_") { + continue + } + + i++ + color.Tag(tag).Print(tag+" tag") + if i%5 == 0 { + fmt.Print("\n") + } else { + fmt.Print(" ") + } + } + + fmt.Printf("\n\ntotal tags: %d\n", i) +} diff --git a/_examples/envcheck.go b/_examples/envcheck.go index e002038..9e23ae1 100644 --- a/_examples/envcheck.go +++ b/_examples/envcheck.go @@ -2,11 +2,18 @@ package main import ( "fmt" + "os" + "runtime" "github.com/gookit/color" + "github.com/gookit/goutil/dump" ) func main() { + fmt.Println("OS", runtime.GOOS) + fmt.Println("IsSupport256Color", color.IsSupport256Color()) fmt.Println("IsSupportColor", color.IsSupportColor()) + + dump.P(os.Environ()) } diff --git a/_examples/go.mod b/_examples/go.mod new file mode 100644 index 0000000..fc5ab19 --- /dev/null +++ b/_examples/go.mod @@ -0,0 +1,7 @@ +module color.example + +go 1.12 + +require github.com/gookit/color v1.2.9 + +replace github.com/gookit/color => ../ diff --git a/_examples/go.sum b/_examples/go.sum new file mode 100644 index 0000000..4e63f60 --- /dev/null +++ b/_examples/go.sum @@ -0,0 +1,16 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/gookit/color v1.2.9 h1:1gwRqF/u6wZrxzn+thlROF7D4Toi9eVGUidZNAxAZHE= +github.com/gookit/color v1.2.9/go.mod h1:AhIE+pS6D4Ql0SQWbBeXPHw7gY0/sjHoA4s/n1KB7xg= +github.com/gookit/goutil v0.3.5 h1:95hWrCXcz7wuwlvcHw7YyUbFH0fV15YM0WPioYMcZww= +github.com/gookit/goutil v0.3.5/go.mod h1:OHs5W5Xmfj4pCMXHnMxsDPrCc0SRbHLgJ2qs6wr5fxM= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= diff --git a/_examples/issue26.go b/_examples/issue26.go new file mode 100644 index 0000000..252fb1e --- /dev/null +++ b/_examples/issue26.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + "time" + + "github.com/gookit/color" +) + +func main() { + green := color.HEX("#00FF62").Sprintf("Hello world!") + now := time.Now().Format("02-01-2006 15:04:05.000") + + fmt.Printf("[%v] %v\n", now, green) + + color.Printf("[%v] %v\n", now, green) + color.HEX("#00FF62").Println("Hello world!") +} diff --git a/_examples/ref/deprecated.go.txt b/_examples/ref/deprecated.go.txt new file mode 100644 index 0000000..b95db0e --- /dev/null +++ b/_examples/ref/deprecated.go.txt @@ -0,0 +1,452 @@ +package main + +import "fmt" + +func doPrint(code string, colors []Color, str string) { + if isLikeInCmd { + winPrint(str, colors...) + } else { + _, _ = fmt.Fprint(output, RenderString(code, str)) + } +} + +func doPrintln(code string, colors []Color, args []interface{}) { + str := formatArgsForPrintln(args) + if isLikeInCmd { + winPrintln(str, colors...) + } else { + _, _ = fmt.Fprintln(output, RenderString(code, str)) + } +} +// +build windows + +// Display color on windows +// refer: +// golang.org/x/sys/windows +// golang.org/x/crypto/ssh/terminal +// https://docs.microsoft.com/en-us/windows/console +package color + +import ( + "fmt" + "syscall" + "unsafe" +) + +// color on windows cmd +// you can see on windows by command: COLOR /? +// windows color build by: "Bg + Fg" OR only "Fg" +// Consists of any two of the following: +// the first is the background color, and the second is the foreground color +// 颜色属性由两个十六进制数字指定 +// - 第一个对应于背景,第二个对应于前景。 +// - 当只传入一个值时,则认为是前景色 +// 每个数字可以为以下任何值: +// more see: https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/cmd +const ( + // Foreground colors. + winFgBlack uint16 = 0x00 // 0 黑色 + winFgBlue uint16 = 0x01 // 1 蓝色 + winFgGreen uint16 = 0x02 // 2 绿色 + winFgAqua uint16 = 0x03 // 3 浅绿 skyblue + winFgRed uint16 = 0x04 // 4 红色 + winFgPink uint16 = 0x05 // 5 紫色/品红 + winFgYellow uint16 = 0x06 // 6 黄色 + winFgWhite uint16 = 0x07 // 7 白色 + winFgGray uint16 = 0x08 // 8 灰色 + + winFgLightRed uint16 = 0x09 // 9 淡红色 + winFgLightGreen uint16 = 0x0a // 10 淡绿色 + winFgLightAqua uint16 = 0x0b // 11 淡浅绿色 + winFgLightBlue uint16 = 0x0c // 12 淡蓝色 + winFgLightPink uint16 = 0x0d // 13 Purple 淡紫色, Pink 粉红 + winFgLightYellow uint16 = 0x0e // 14 淡黄色 + winFgLightWhite uint16 = 0x0f // 15 亮白色 + + // Background colors. + winBgBlack uint16 = 0x00 // 黑色 + winBgBlue uint16 = 0x10 // 蓝色 + winBgGreen uint16 = 0x20 // 绿色 + winBgAqua uint16 = 0x30 // 浅绿 skyblue + winBgRed uint16 = 0x40 // 红色 + winBgPink uint16 = 0x50 // 紫色 + winBgYellow uint16 = 0x60 // 黄色 + winBgWhite uint16 = 0x70 // 白色 + winBgGray uint16 = 0x80 // 128 灰色 + + winBgLightRed uint16 = 0x90 // 淡红色 + winBgLightGreen uint16 = 0xa0 // 淡绿色 + winBgLightAqua uint16 = 0xb0 // 淡浅绿色 + winBgLightBlue uint16 = 0xc0 // 淡蓝色 + winBgLightPink uint16 = 0xd0 // 淡紫色 + winBgLightYellow uint16 = 0xe0 // 淡黄色 + winBgLightWhite uint16 = 0xf0 // 240 亮白色 + + // bg black, fg white + winDefSetting = winBgBlack | winFgWhite + + // Option settings + // see https://docs.microsoft.com/en-us/windows/console/char-info-str + winFgIntensity uint16 = 0x0008 // 8 前景强度 + winBgIntensity uint16 = 0x0080 // 128 背景强度 + + WinOpLeading uint16 = 0x0100 // 前导字节 + WinOpTrailing uint16 = 0x0200 // 尾随字节 + WinOpHorizontal uint16 = 0x0400 // 顶部水平 + WinOpReverse uint16 = 0x4000 // 反转前景和背景 + WinOpUnderscore uint16 = 0x8000 // 32768 下划线 +) + +// color on windows +var winColorsMap map[Color]uint16 + +// related docs +// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences +// https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples +var ( + // isMSys bool + kernel32 *syscall.LazyDLL + + procGetConsoleMode *syscall.LazyProc + procSetConsoleMode *syscall.LazyProc + + procSetTextAttribute *syscall.LazyProc + procGetConsoleScreenBufferInfo *syscall.LazyProc + + // console screen buffer info + // eg {size:{x:215 y:3000} cursorPosition:{x:0 y:893} attributes:7 window:{left:0 top:882 right:214 bottom:893} maximumWindowSize:{x:215 y:170}} + defScreenInfo consoleScreenBufferInfo +) + +func init() { + // if at linux, mac, or windows's ConEmu, Cmder, putty + if isSupportColor { + return + } + + isLikeInCmd = true + if !Enable { + return + } + + // force open + isSupportColor = true + // init simple color code info + // initWinColorsMap() + + // load related windows dll + // isMSys = utils.IsMSys() + kernel32 = syscall.NewLazyDLL("kernel32.dll") + + // https://docs.microsoft.com/en-us/windows/console/setconsolemode + procGetConsoleMode = kernel32.NewProc("GetConsoleMode") + procSetConsoleMode = kernel32.NewProc("SetConsoleMode") + + procSetTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") + // https://docs.microsoft.com/en-us/windows/console/getconsolescreenbufferinfo + procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") + + err := EnableVirtualTerminalProcessing(syscall.Stdout, true) + if err != nil { + SettingErr = err + } + + // fetch console screen buffer info + // err := getConsoleScreenBufferInfo(uintptr(syscall.Stdout), &defScreenInfo) +} + +/************************************************************* + * render full color code on windows(8,16,24bit color) + *************************************************************/ + +// docs https://docs.microsoft.com/zh-cn/windows/console/getconsolemode#parameters +const ( + // equals to docs page's ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 + EnableVirtualTerminalProcessingMode uint32 = 0x4 +) + +// EnableVirtualTerminalProcessing Enable virtual terminal processing +// +// ref from github.com/konsorten/go-windows-terminal-sequences +// doc https://docs.microsoft.com/zh-cn/windows/console/console-virtual-terminal-sequences#samples +// +// Usage: +// err := EnableVirtualTerminalProcessing(syscall.Stdout, true) +// // support print color text +// err = EnableVirtualTerminalProcessing(syscall.Stdout, false) +func EnableVirtualTerminalProcessing(stream syscall.Handle, enable bool) error { + var mode uint32 + // Check if it is currently in the terminal + err := syscall.GetConsoleMode(syscall.Stdout, &mode) + if err != nil { + return err + } + + if enable { + mode |= EnableVirtualTerminalProcessingMode + } else { + mode &^= EnableVirtualTerminalProcessingMode + } + + ret, _, err := procSetConsoleMode.Call(uintptr(stream), uintptr(mode)) + if ret == 0 { + return err + } + + return nil +} + +// renderColorCodeOnCmd enable cmd color render. +func renderColorCodeOnCmd(fn func()) { + err := EnableVirtualTerminalProcessing(syscall.Stdout, true) + // if is not in terminal, will clear color tag. + if err != nil { + // panic(err) + fn() + return + } + + // force open color render + old := ForceOpenColor() + fn() + // revert color setting + isSupportColor = old + + err = EnableVirtualTerminalProcessing(syscall.Stdout, false) + if err != nil { + panic(err) + } +} + +/************************************************************* + * render simple color code on windows + *************************************************************/ + +// initWinColorsMap init colors to win-colors mapping +func initWinColorsMap() { + // init map + winColorsMap = map[Color]uint16{ + // Foreground colors + FgBlack: winFgBlack, + FgRed: winFgRed, + FgGreen: winFgGreen, + FgYellow: winFgYellow, + FgBlue: winFgBlue, + FgMagenta: winFgPink, // diff + FgCyan: winFgAqua, // diff + FgWhite: winFgWhite, + FgDefault: winFgWhite, + + // Extra Foreground colors + FgDarkGray: winFgGray, + FgLightRed: winFgLightBlue, + FgLightGreen: winFgLightGreen, + FgLightYellow: winFgLightYellow, + FgLightBlue: winFgLightRed, + FgLightMagenta: winFgLightPink, + FgLightCyan: winFgLightAqua, + FgLightWhite: winFgLightWhite, + + // Background colors + BgBlack: winBgBlack, + BgRed: winBgRed, + BgGreen: winBgGreen, + BgYellow: winBgYellow, + BgBlue: winBgBlue, + BgMagenta: winBgPink, // diff + BgCyan: winBgAqua, // diff + BgWhite: winBgWhite, + BgDefault: winBgBlack, + + // Extra Background colors + BgDarkGray: winBgGray, + BgLightRed: winBgLightBlue, + BgLightGreen: winBgLightGreen, + BgLightYellow: winBgLightYellow, + BgLightBlue: winBgLightRed, + BgLightMagenta: winBgLightPink, + BgLightCyan: winBgLightAqua, + BgLightWhite: winBgLightWhite, + + // Option settings(注释掉的,将在win cmd中忽略掉) + // OpReset: winDefSetting, // 重置所有设置 + OpBold: winFgIntensity, // 加粗 -> + // OpFuzzy: // 模糊(不是所有的终端仿真器都支持) + // OpItalic // 斜体(不是所有的终端仿真器都支持) + OpUnderscore: WinOpUnderscore, // 下划线 + // OpBlink // 闪烁 + // OpFastBlink // 快速闪烁(未广泛支持) + // OpReverse: WinOpReverse // 颠倒的 交换背景色与前景色 + // OpConcealed // 隐匿的 + // OpStrikethrough // 删除的,删除线(未广泛支持) + } +} + +// winPrint +func winPrint(str string, colors ...Color) { + _, _ = winInternalPrint(str, colorsToWinAttr(colors), false) +} + +// winPrintln +func winPrintln(str string, colors ...Color) { + _, _ = winInternalPrint(str, colorsToWinAttr(colors), true) +} + +// winInternalPrint +// winInternalPrint("hello [OK];", 2|8, true) //亮绿色 +func winInternalPrint(str string, attribute uint16, newline bool) (int, error) { + if !Enable { // not enable + if newline { + return fmt.Fprintln(output, str) + } + return fmt.Fprint(output, str) + } + + // fmt.Print("attribute val: ", attribute, "\n") + _, _ = setConsoleTextAttr(uintptr(syscall.Stdout), attribute) + if newline { + _, _ = fmt.Fprintln(output, str) + } else { + _, _ = fmt.Fprint(output, str) + } + + // handle, _, _ = procSetTextAttribute.Call(uintptr(syscall.Stdout), winDefSetting) + // closeHandle := kernel32.NewProc("CloseHandle") + // closeHandle.Call(handle) + + return winReset() +} + +// winSet set console color attributes +func winSet(colors ...Color) (int, error) { + // not enable + if !Enable { + return 0, nil + } + + return setConsoleTextAttr(uintptr(syscall.Stdout), colorsToWinAttr(colors)) +} + +// winReset reset color settings to default +func winReset() (int, error) { + // not enable + if !Enable { + return 0, nil + } + + return setConsoleTextAttr(uintptr(syscall.Stdout), winDefSetting) +} + +// colorsToWinAttr convert generic colors to win-colors attribute +func colorsToWinAttr(colors []Color) uint16 { + var setting uint16 + for _, c := range colors { + // check exists + if wc, ok := winColorsMap[c]; ok { + setting |= wc + } + } + + return setting +} + +// getWinColor convert Color to win-color value +func getWinColor(color Color) uint16 { + if wc, ok := winColorsMap[color]; ok { + return wc + } + + return 0 +} + +// setConsoleTextAttr +// ret != 0 is OK. +func setConsoleTextAttr(consoleOutput uintptr, winAttr uint16) (n int, err error) { + // err is type of syscall.Errno + ret, _, err := procSetTextAttribute.Call(consoleOutput, uintptr(winAttr)) + + // if success, err.Error() is equals "The operation completed successfully." + // NOTICE: on windows IDE run client, has error: "The handle is invalid." + if err != nil && err.Error() == "The operation completed successfully." { + err = nil // set as nil + } + + return int(ret), err +} + +// IsTty returns true if the given file descriptor is a terminal. +func IsTty(fd uintptr) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +// IsTerminal returns true if the given file descriptor is a terminal. +// Usage: +// fd := os.Stdout.Fd() +// fd := uintptr(syscall.Stdout) // for windows +// IsTerminal(fd) +func IsTerminal(fd int) bool { + var st uint32 + r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, uintptr(fd), uintptr(unsafe.Pointer(&st)), 0) + return r != 0 && e == 0 +} + +/************************************************************* + * some extra utils for windows + *************************************************************/ + +// from package: golang.org/x/sys/windows +type ( + short int16 + word uint16 + + // coord cursor position coordinates + coord struct { + x short + y short + } + + smallRect struct { + left short + top short + right short + bottom short + } + + // Used with GetConsoleScreenBuffer to retrieve information about a console + // screen buffer. See + // https://docs.microsoft.com/en-us/windows/console/console-screen-buffer-info-str + // for details. + consoleScreenBufferInfo struct { + size coord + cursorPosition coord + attributes word // is windows color setting + window smallRect + maximumWindowSize coord + } +) + +// GetSize returns the dimensions of the given terminal. +func getSize(fd int) (width, height int, err error) { + var info consoleScreenBufferInfo + if err := getConsoleScreenBufferInfo(uintptr(fd), &info); err != nil { + return 0, 0, err + } + + return int(info.size.x), int(info.size.y), nil +} + +// from package: golang.org/x/sys/windows +func getConsoleScreenBufferInfo(consoleOutput uintptr, info *consoleScreenBufferInfo) (err error) { + r1, _, e1 := syscall.Syscall(procGetConsoleScreenBufferInfo.Addr(), 2, consoleOutput, uintptr(unsafe.Pointer(info)), 0) + if r1 == 0 { + if e1 != 0 { + err = e1 + } else { + err = syscall.EINVAL + } + } + + return +}