| // +build windows |
| |
| package readline |
| |
| import ( |
| "bufio" |
| "io" |
| "strconv" |
| "strings" |
| "sync" |
| "unicode/utf8" |
| "unsafe" |
| ) |
| |
| const ( |
| _ = uint16(0) |
| COLOR_FBLUE = 0x0001 |
| COLOR_FGREEN = 0x0002 |
| COLOR_FRED = 0x0004 |
| COLOR_FINTENSITY = 0x0008 |
| |
| COLOR_BBLUE = 0x0010 |
| COLOR_BGREEN = 0x0020 |
| COLOR_BRED = 0x0040 |
| COLOR_BINTENSITY = 0x0080 |
| |
| COMMON_LVB_UNDERSCORE = 0x8000 |
| COMMON_LVB_BOLD = 0x0007 |
| ) |
| |
| var ColorTableFg = []word{ |
| 0, // 30: Black |
| COLOR_FRED, // 31: Red |
| COLOR_FGREEN, // 32: Green |
| COLOR_FRED | COLOR_FGREEN, // 33: Yellow |
| COLOR_FBLUE, // 34: Blue |
| COLOR_FRED | COLOR_FBLUE, // 35: Magenta |
| COLOR_FGREEN | COLOR_FBLUE, // 36: Cyan |
| COLOR_FRED | COLOR_FBLUE | COLOR_FGREEN, // 37: White |
| } |
| |
| var ColorTableBg = []word{ |
| 0, // 40: Black |
| COLOR_BRED, // 41: Red |
| COLOR_BGREEN, // 42: Green |
| COLOR_BRED | COLOR_BGREEN, // 43: Yellow |
| COLOR_BBLUE, // 44: Blue |
| COLOR_BRED | COLOR_BBLUE, // 45: Magenta |
| COLOR_BGREEN | COLOR_BBLUE, // 46: Cyan |
| COLOR_BRED | COLOR_BBLUE | COLOR_BGREEN, // 47: White |
| } |
| |
| type ANSIWriter struct { |
| target io.Writer |
| wg sync.WaitGroup |
| ctx *ANSIWriterCtx |
| sync.Mutex |
| } |
| |
| func NewANSIWriter(w io.Writer) *ANSIWriter { |
| a := &ANSIWriter{ |
| target: w, |
| ctx: NewANSIWriterCtx(w), |
| } |
| return a |
| } |
| |
| func (a *ANSIWriter) Close() error { |
| a.wg.Wait() |
| return nil |
| } |
| |
| type ANSIWriterCtx struct { |
| isEsc bool |
| isEscSeq bool |
| arg []string |
| target *bufio.Writer |
| wantFlush bool |
| } |
| |
| func NewANSIWriterCtx(target io.Writer) *ANSIWriterCtx { |
| return &ANSIWriterCtx{ |
| target: bufio.NewWriter(target), |
| } |
| } |
| |
| func (a *ANSIWriterCtx) Flush() { |
| a.target.Flush() |
| } |
| |
| func (a *ANSIWriterCtx) process(r rune) bool { |
| if a.wantFlush { |
| if r == 0 || r == CharEsc { |
| a.wantFlush = false |
| a.target.Flush() |
| } |
| } |
| if a.isEscSeq { |
| a.isEscSeq = a.ioloopEscSeq(a.target, r, &a.arg) |
| return true |
| } |
| |
| switch r { |
| case CharEsc: |
| a.isEsc = true |
| case '[': |
| if a.isEsc { |
| a.arg = nil |
| a.isEscSeq = true |
| a.isEsc = false |
| break |
| } |
| fallthrough |
| default: |
| a.target.WriteRune(r) |
| a.wantFlush = true |
| } |
| return true |
| } |
| |
| func (a *ANSIWriterCtx) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool { |
| arg := *argptr |
| var err error |
| |
| if r >= 'A' && r <= 'D' { |
| count := short(GetInt(arg, 1)) |
| info, err := GetConsoleScreenBufferInfo() |
| if err != nil { |
| return false |
| } |
| switch r { |
| case 'A': // up |
| info.dwCursorPosition.y -= count |
| case 'B': // down |
| info.dwCursorPosition.y += count |
| case 'C': // right |
| info.dwCursorPosition.x += count |
| case 'D': // left |
| info.dwCursorPosition.x -= count |
| } |
| SetConsoleCursorPosition(&info.dwCursorPosition) |
| return false |
| } |
| |
| switch r { |
| case 'J': |
| killLines() |
| case 'K': |
| eraseLine() |
| case 'm': |
| color := word(0) |
| for _, item := range arg { |
| var c int |
| c, err = strconv.Atoi(item) |
| if err != nil { |
| w.WriteString("[" + strings.Join(arg, ";") + "m") |
| break |
| } |
| if c >= 30 && c < 40 { |
| color ^= COLOR_FINTENSITY |
| color |= ColorTableFg[c-30] |
| } else if c >= 40 && c < 50 { |
| color ^= COLOR_BINTENSITY |
| color |= ColorTableBg[c-40] |
| } else if c == 4 { |
| color |= COMMON_LVB_UNDERSCORE | ColorTableFg[7] |
| } else if c == 1 { |
| color |= COMMON_LVB_BOLD | COLOR_FINTENSITY |
| } else { // unknown code treat as reset |
| color = ColorTableFg[7] |
| } |
| } |
| if err != nil { |
| break |
| } |
| kernel.SetConsoleTextAttribute(stdout, uintptr(color)) |
| case '\007': // set title |
| case ';': |
| if len(arg) == 0 || arg[len(arg)-1] != "" { |
| arg = append(arg, "") |
| *argptr = arg |
| } |
| return true |
| default: |
| if len(arg) == 0 { |
| arg = append(arg, "") |
| } |
| arg[len(arg)-1] += string(r) |
| *argptr = arg |
| return true |
| } |
| *argptr = nil |
| return false |
| } |
| |
| func (a *ANSIWriter) Write(b []byte) (int, error) { |
| a.Lock() |
| defer a.Unlock() |
| |
| off := 0 |
| for len(b) > off { |
| r, size := utf8.DecodeRune(b[off:]) |
| if size == 0 { |
| return off, io.ErrShortWrite |
| } |
| off += size |
| a.ctx.process(r) |
| } |
| a.ctx.Flush() |
| return off, nil |
| } |
| |
| func killLines() error { |
| sbi, err := GetConsoleScreenBufferInfo() |
| if err != nil { |
| return err |
| } |
| |
| size := (sbi.dwCursorPosition.y - sbi.dwSize.y) * sbi.dwSize.x |
| size += sbi.dwCursorPosition.x |
| |
| var written int |
| kernel.FillConsoleOutputAttribute(stdout, uintptr(ColorTableFg[7]), |
| uintptr(size), |
| sbi.dwCursorPosition.ptr(), |
| uintptr(unsafe.Pointer(&written)), |
| ) |
| return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), |
| uintptr(size), |
| sbi.dwCursorPosition.ptr(), |
| uintptr(unsafe.Pointer(&written)), |
| ) |
| } |
| |
| func eraseLine() error { |
| sbi, err := GetConsoleScreenBufferInfo() |
| if err != nil { |
| return err |
| } |
| |
| size := sbi.dwSize.x |
| sbi.dwCursorPosition.x = 0 |
| var written int |
| return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), |
| uintptr(size), |
| sbi.dwCursorPosition.ptr(), |
| uintptr(unsafe.Pointer(&written)), |
| ) |
| } |