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