├── .github ├── FUNDING.yml └── workflows │ └── test.yml ├── LICENSE ├── README.md ├── _example ├── escape-seq │ └── main.go ├── logrus │ └── main.go └── title │ └── main.go ├── cmd └── colorable │ └── colorable.go ├── colorable_others.go ├── colorable_test.go ├── colorable_windows.go ├── go.mod ├── go.sum ├── go.test.sh └── noncolorable.go /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: mattn # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | branches: 8 | - master 9 | 10 | jobs: 11 | test: 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | os: 17 | - ubuntu-latest 18 | - macos-latest 19 | - windows-latest 20 | go: 21 | - '1.23' 22 | - '1.22' 23 | - '1.21' 24 | - '1.20' 25 | - '1.19' 26 | - '1.18' 27 | steps: 28 | - uses: actions/setup-go@v2 29 | with: 30 | go-version: ${{ matrix.go }} 31 | - uses: actions/checkout@v2 32 | - name: test 33 | shell: bash 34 | run: | 35 | ./go.test.sh 36 | - name: upload coverage report 37 | uses: codecov/codecov-action@v2 38 | with: 39 | env_vars: OS,GO 40 | env: 41 | OS: ${{ matrix.os }} 42 | GO: ${{ matrix.go }} 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Yasuhiro Matsumoto 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-colorable 2 | 3 | [![Build Status](https://github.com/mattn/go-colorable/workflows/test/badge.svg)](https://github.com/mattn/go-colorable/actions?query=workflow%3Atest) 4 | [![Codecov](https://codecov.io/gh/mattn/go-colorable/branch/master/graph/badge.svg)](https://codecov.io/gh/mattn/go-colorable) 5 | [![GoDoc](https://godoc.org/github.com/mattn/go-colorable?status.svg)](http://godoc.org/github.com/mattn/go-colorable) 6 | [![Go Report Card](https://goreportcard.com/badge/mattn/go-colorable)](https://goreportcard.com/report/mattn/go-colorable) 7 | 8 | Colorable writer for windows. 9 | 10 | For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.) 11 | This package is possible to handle escape sequence for ansi color on windows. 12 | 13 | ## Too Bad! 14 | 15 | ![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/bad.png) 16 | 17 | 18 | ## So Good! 19 | 20 | ![](https://raw.githubusercontent.com/mattn/go-colorable/gh-pages/good.png) 21 | 22 | ## Usage 23 | 24 | ```go 25 | logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) 26 | logrus.SetOutput(colorable.NewColorableStdout()) 27 | 28 | logrus.Info("succeeded") 29 | logrus.Warn("not correct") 30 | logrus.Error("something error") 31 | logrus.Fatal("panic") 32 | ``` 33 | 34 | You can compile above code on non-windows OSs. 35 | 36 | ## Installation 37 | 38 | ``` 39 | $ go get github.com/mattn/go-colorable 40 | ``` 41 | 42 | # License 43 | 44 | MIT 45 | 46 | # Author 47 | 48 | Yasuhiro Matsumoto (a.k.a mattn) 49 | -------------------------------------------------------------------------------- /_example/escape-seq/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | 7 | "github.com/mattn/go-colorable" 8 | ) 9 | 10 | func main() { 11 | stdOut := bufio.NewWriter(colorable.NewColorableStdout()) 12 | 13 | fmt.Fprint(stdOut, "\x1B[3GMove to 3rd Column\n") 14 | fmt.Fprint(stdOut, "\x1B[1;2HMove to 2nd Column on 1st Line\n") 15 | stdOut.Flush() 16 | } 17 | -------------------------------------------------------------------------------- /_example/logrus/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/mattn/go-colorable" 5 | "github.com/sirupsen/logrus" 6 | ) 7 | 8 | func main() { 9 | logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) 10 | logrus.SetOutput(colorable.NewColorableStdout()) 11 | 12 | logrus.Info("succeeded") 13 | logrus.Warn("not correct") 14 | logrus.Error("something error") 15 | logrus.Fatal("panic") 16 | } 17 | -------------------------------------------------------------------------------- /_example/title/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | . "github.com/mattn/go-colorable" 8 | ) 9 | 10 | func main() { 11 | out := NewColorableStdout() 12 | fmt.Fprint(out, "\x1B]0;TITLE Changed\007(See title and hit any key)") 13 | var c [1]byte 14 | os.Stdin.Read(c[:]) 15 | } 16 | -------------------------------------------------------------------------------- /cmd/colorable/colorable.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "os" 6 | 7 | "github.com/mattn/go-colorable" 8 | ) 9 | 10 | func main() { 11 | io.Copy(colorable.NewColorableStdout(), os.Stdin) 12 | } 13 | -------------------------------------------------------------------------------- /colorable_others.go: -------------------------------------------------------------------------------- 1 | //go:build !windows || appengine 2 | // +build !windows appengine 3 | 4 | package colorable 5 | 6 | import ( 7 | "io" 8 | "os" 9 | 10 | _ "github.com/mattn/go-isatty" 11 | ) 12 | 13 | // NewColorable returns new instance of Writer which handles escape sequence. 14 | func NewColorable(file *os.File) io.Writer { 15 | if file == nil { 16 | panic("nil passed instead of *os.File to NewColorable()") 17 | } 18 | 19 | return file 20 | } 21 | 22 | // NewColorableStdout returns new instance of Writer which handles escape sequence for stdout. 23 | func NewColorableStdout() io.Writer { 24 | return os.Stdout 25 | } 26 | 27 | // NewColorableStderr returns new instance of Writer which handles escape sequence for stderr. 28 | func NewColorableStderr() io.Writer { 29 | return os.Stderr 30 | } 31 | 32 | // EnableColorsStdout enable colors if possible. 33 | func EnableColorsStdout(enabled *bool) func() { 34 | if enabled != nil { 35 | *enabled = true 36 | } 37 | return func() {} 38 | } 39 | -------------------------------------------------------------------------------- /colorable_test.go: -------------------------------------------------------------------------------- 1 | package colorable 2 | 3 | import ( 4 | "bytes" 5 | "os" 6 | "runtime" 7 | "testing" 8 | ) 9 | 10 | // checkEncoding checks that colorable is output encoding agnostic as long as 11 | // the encoding is a superset of ASCII. This implies that one byte not part of 12 | // an ANSI sequence must give exactly one byte in output 13 | func checkEncoding(t *testing.T, data []byte) { 14 | // Send non-UTF8 data to colorable 15 | b := bytes.NewBuffer(make([]byte, 0, 10)) 16 | if b.Len() != 0 { 17 | t.FailNow() 18 | } 19 | // TODO move colorable wrapping outside the test 20 | NewNonColorable(b).Write(data) 21 | if b.Len() != len(data) { 22 | t.Fatalf("%d bytes expected, got %d", len(data), b.Len()) 23 | } 24 | } 25 | 26 | func TestEncoding(t *testing.T) { 27 | checkEncoding(t, []byte{}) // Empty 28 | checkEncoding(t, []byte(`abc`)) // "abc" 29 | checkEncoding(t, []byte(`é`)) // "é" in UTF-8 30 | checkEncoding(t, []byte{233}) // 'é' in Latin-1 31 | } 32 | 33 | func TestNonColorable(t *testing.T) { 34 | var buf bytes.Buffer 35 | want := "hello" 36 | NewNonColorable(&buf).Write([]byte("\x1b[0m" + want + "\x1b[2J")) 37 | got := buf.String() 38 | if got != "hello" { 39 | t.Fatalf("want %q but %q", want, got) 40 | } 41 | 42 | buf.Reset() 43 | NewNonColorable(&buf).Write([]byte("\x1b[")) 44 | got = buf.String() 45 | if got != "" { 46 | t.Fatalf("want %q but %q", "", got) 47 | } 48 | } 49 | 50 | func TestNonColorableNil(t *testing.T) { 51 | panicked := false 52 | func() { 53 | defer func() { 54 | recover() 55 | panicked = true 56 | }() 57 | NewNonColorable(nil) 58 | NewColorable(nil) 59 | }() 60 | 61 | if !panicked { 62 | t.Fatalf("should panic") 63 | } 64 | } 65 | 66 | func TestNonColorableESC(t *testing.T) { 67 | var b bytes.Buffer 68 | NewNonColorable(&b).Write([]byte{0x1b}) 69 | if b.Len() > 0 { 70 | t.Fatalf("0 bytes expected, got %d", b.Len()) 71 | } 72 | } 73 | 74 | func TestNonColorableBadESC(t *testing.T) { 75 | var b bytes.Buffer 76 | NewNonColorable(&b).Write([]byte{0x1b, 0x1b}) 77 | if b.Len() > 0 { 78 | t.Fatalf("0 bytes expected, got %d", b.Len()) 79 | } 80 | } 81 | 82 | func TestColorable(t *testing.T) { 83 | if runtime.GOOS == "windows" { 84 | t.Skipf("skip this test on windows") 85 | } 86 | _, ok := NewColorableStdout().(*os.File) 87 | if !ok { 88 | t.Fatalf("should os.Stdout on UNIX") 89 | } 90 | _, ok = NewColorableStderr().(*os.File) 91 | if !ok { 92 | t.Fatalf("should os.Stdout on UNIX") 93 | } 94 | _, ok = NewColorable(os.Stdout).(*os.File) 95 | if !ok { 96 | t.Fatalf("should os.Stdout on UNIX") 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /colorable_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows && !appengine 2 | // +build windows,!appengine 3 | 4 | package colorable 5 | 6 | import ( 7 | "bytes" 8 | "io" 9 | "math" 10 | "os" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | syscall "golang.org/x/sys/windows" 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.NewLazySystemDLL("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 | // writer provides colorable Writer to the console 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 | // NewColorable returns new instance of writer which handles escape sequence from File. 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 | // NewColorableStdout returns new instance of writer which handles escape sequence for stdout. 121 | func NewColorableStdout() io.Writer { 122 | return NewColorable(os.Stdout) 123 | } 124 | 125 | // NewColorableStderr returns new instance of writer which handles escape sequence for stderr. 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 | // `\033]0;TITLESTR\007` 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 | // returns Atoi(s) unless s == "" in which case it returns def 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 | // Write writes data on console 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 | // https://github.com/mattn/go-colorable/issues/27 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 | // execute part after switch 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 = atoiWithDefault(buf.String(), 1) 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 = atoiWithDefault(buf.String(), 1) 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: // set foreground color. 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: // reset foreground color. 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: // set background color. 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: // reset foreground color. 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 | // EnableColorsStdout enable colors if possible. 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 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/mattn/go-colorable 2 | 3 | require ( 4 | github.com/mattn/go-isatty v0.0.20 5 | golang.org/x/sys v0.29.0 6 | ) 7 | 8 | go 1.18 9 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 2 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 3 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 4 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= 5 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 6 | -------------------------------------------------------------------------------- /go.test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | echo "" > coverage.txt 5 | 6 | for d in $(go list ./... | grep -v vendor); do 7 | go test -race -coverprofile=profile.out -covermode=atomic "$d" 8 | if [ -f profile.out ]; then 9 | cat profile.out >> coverage.txt 10 | rm profile.out 11 | fi 12 | done 13 | -------------------------------------------------------------------------------- /noncolorable.go: -------------------------------------------------------------------------------- 1 | package colorable 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | ) 7 | 8 | // NonColorable holds writer but removes escape sequence. 9 | type NonColorable struct { 10 | out io.Writer 11 | } 12 | 13 | // NewNonColorable returns new instance of Writer which removes escape sequence from Writer. 14 | func NewNonColorable(w io.Writer) io.Writer { 15 | return &NonColorable{out: w} 16 | } 17 | 18 | // Write writes data on console 19 | func (w *NonColorable) Write(data []byte) (n int, err error) { 20 | er := bytes.NewReader(data) 21 | var plaintext bytes.Buffer 22 | loop: 23 | for { 24 | c1, err := er.ReadByte() 25 | if err != nil { 26 | plaintext.WriteTo(w.out) 27 | break loop 28 | } 29 | if c1 != 0x1b { 30 | plaintext.WriteByte(c1) 31 | continue 32 | } 33 | _, err = plaintext.WriteTo(w.out) 34 | if err != nil { 35 | break loop 36 | } 37 | c2, err := er.ReadByte() 38 | if err != nil { 39 | break loop 40 | } 41 | if c2 != 0x5b { 42 | continue 43 | } 44 | 45 | for { 46 | c, err := er.ReadByte() 47 | if err != nil { 48 | break loop 49 | } 50 | if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { 51 | break 52 | } 53 | } 54 | } 55 | 56 | return len(data), nil 57 | } 58 | --------------------------------------------------------------------------------