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