1// +build windows
2
3package readline
4
5import (
6	"bufio"
7	"io"
8	"strconv"
9	"strings"
10	"sync"
11	"unicode/utf8"
12	"unsafe"
13)
14
15const (
16	_                = uint16(0)
17	COLOR_FBLUE      = 0x0001
18	COLOR_FGREEN     = 0x0002
19	COLOR_FRED       = 0x0004
20	COLOR_FINTENSITY = 0x0008
21
22	COLOR_BBLUE      = 0x0010
23	COLOR_BGREEN     = 0x0020
24	COLOR_BRED       = 0x0040
25	COLOR_BINTENSITY = 0x0080
26
27	COMMON_LVB_UNDERSCORE = 0x8000
28	COMMON_LVB_BOLD       = 0x0007
29)
30
31var ColorTableFg = []word{
32	0,                                       // 30: Black
33	COLOR_FRED,                              // 31: Red
34	COLOR_FGREEN,                            // 32: Green
35	COLOR_FRED | COLOR_FGREEN,               // 33: Yellow
36	COLOR_FBLUE,                             // 34: Blue
37	COLOR_FRED | COLOR_FBLUE,                // 35: Magenta
38	COLOR_FGREEN | COLOR_FBLUE,              // 36: Cyan
39	COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White
40}
41
42var ColorTableBg = []word{
43	0,                                       // 40: Black
44	COLOR_BRED,                              // 41: Red
45	COLOR_BGREEN,                            // 42: Green
46	COLOR_BRED | COLOR_BGREEN,               // 43: Yellow
47	COLOR_BBLUE,                             // 44: Blue
48	COLOR_BRED | COLOR_BBLUE,                // 45: Magenta
49	COLOR_BGREEN | COLOR_BBLUE,              // 46: Cyan
50	COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White
51}
52
53type ANSIWriter struct {
54	target io.Writer
55	wg     sync.WaitGroup
56	ctx    *ANSIWriterCtx
57	sync.Mutex
58}
59
60func NewANSIWriter(w io.Writer) *ANSIWriter {
61	a := &ANSIWriter{
62		target: w,
63		ctx:    NewANSIWriterCtx(w),
64	}
65	return a
66}
67
68func (a *ANSIWriter) Close() error {
69	a.wg.Wait()
70	return nil
71}
72
73type ANSIWriterCtx struct {
74	isEsc     bool
75	isEscSeq  bool
76	arg       []string
77	target    *bufio.Writer
78	wantFlush bool
79}
80
81func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx {
82	return &ANSIWriterCtx{
83		target: bufio.NewWriter(target),
84	}
85}
86
87func (a *ANSIWriterCtx) Flush() {
88	a.target.Flush()
89}
90
91func (a *ANSIWriterCtx) process(r rune) bool {
92	if a.wantFlush {
93		if r == 0 || r == CharEsc {
94			a.wantFlush = false
95			a.target.Flush()
96		}
97	}
98	if a.isEscSeq {
99		a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg)
100		return true
101	}
102
103	switch r {
104	case CharEsc:
105		a.isEsc = true
106	case '[':
107		if a.isEsc {
108			a.arg = nil
109			a.isEscSeq = true
110			a.isEsc = false
111			break
112		}
113		fallthrough
114	default:
115		a.target.WriteRune(r)
116		a.wantFlush = true
117	}
118	return true
119}
120
121func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool {
122	arg := *argptr
123	var err error
124
125	if r >= 'A' && r <= 'D' {
126		count := short(GetInt(arg, 1))
127		info, err := GetConsoleScreenBufferInfo()
128		if err != nil {
129			return false
130		}
131		switch r {
132		case 'A': // up
133			info.dwCursorPosition.y -= count
134		case 'B': // down
135			info.dwCursorPosition.y += count
136		case 'C': // right
137			info.dwCursorPosition.x += count
138		case 'D': // left
139			info.dwCursorPosition.x -= count
140		}
141		SetConsoleCursorPosition(&info.dwCursorPosition)
142		return false
143	}
144
145	switch r {
146	case 'J':
147		killLines()
148	case 'K':
149		eraseLine()
150	case 'm':
151		color := word(0)
152		for _, item := range arg {
153			var c int
154			c, err = strconv.Atoi(item)
155			if err != nil {
156				w.WriteString("[" + strings.Join(arg, ";") + "m")
157				break
158			}
159			if c >= 30 && c < 40 {
160				color ^= COLOR_FINTENSITY
161				color |= ColorTableFg[c-30]
162			} else if c >= 40 && c < 50 {
163				color ^= COLOR_BINTENSITY
164				color |= ColorTableBg[c-40]
165			} else if c == 4 {
166				color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7]
167			} else if c == 1 {
168				color |= COMMON_LVB_BOLD | COLOR_FINTENSITY
169			} else { // unknown code treat as reset
170				color = ColorTableFg[7]
171			}
172		}
173		if err != nil {
174			break
175		}
176		kernel.SetConsoleTextAttribute(stdout, uintptr(color))
177	case '\007': // set title
178	case ';':
179		if len(arg) == 0 || arg[len(arg)-1] != "" {
180			arg = append(arg, "")
181			*argptr = arg
182		}
183		return true
184	default:
185		if len(arg) == 0 {
186			arg = append(arg, "")
187		}
188		arg[len(arg)-1] += string(r)
189		*argptr = arg
190		return true
191	}
192	*argptr = nil
193	return false
194}
195
196func (a *ANSIWriter) Write(b []byte) (int, error) {
197	a.Lock()
198	defer a.Unlock()
199
200	off := 0
201	for len(b) > off {
202		r, size := utf8.DecodeRune(b[off:])
203		if size == 0 {
204			return off, io.ErrShortWrite
205		}
206		off += size
207		a.ctx.process(r)
208	}
209	a.ctx.Flush()
210	return off, nil
211}
212
213func killLines() error {
214	sbi, err := GetConsoleScreenBufferInfo()
215	if err != nil {
216		return err
217	}
218
219	size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x
220	size += sbi.dwCursorPosition.x
221
222	var written int
223	kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]),
224		uintptr(size),
225		sbi.dwCursorPosition.ptr(),
226		uintptr(unsafe.Pointer(&written)),
227	)
228	return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
229		uintptr(size),
230		sbi.dwCursorPosition.ptr(),
231		uintptr(unsafe.Pointer(&written)),
232	)
233}
234
235func eraseLine() error {
236	sbi, err := GetConsoleScreenBufferInfo()
237	if err != nil {
238		return err
239	}
240
241	size := sbi.dwSize.x
242	sbi.dwCursorPosition.x = 0
243	var written int
244	return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
245		uintptr(size),
246		sbi.dwCursorPosition.ptr(),
247		uintptr(unsafe.Pointer(&written)),
248	)
249}
250