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