diff --git a/runebuf.go b/runebuf.go index d5116bb..39c2b39 100644 --- a/runebuf.go +++ b/runebuf.go @@ -246,7 +246,7 @@ func (r *RuneBuffer) IdxLine() int { // the cursor will in the first line, otherwise will in the second line // this situation only occurs in golang's Stdout // TODO: figure out why - if totalWidth%w == 0 && len(r.buf) == r.idx { + if totalWidth%w == 0 && len(r.buf) == r.idx && !isWindows { line-- } @@ -328,17 +328,15 @@ func (r *RuneBuffer) cleanOutput() []byte { buf.Write([]byte("\033[J")) // just like ^k :) idxLine := r.IdxLine() + if idxLine == 0 { buf.WriteString("\033[2K\r") return buf.Bytes() } - for i := 0; i < idxLine; i++ { - buf.WriteString("\033[2K\r") - if i != idxLine-1 { - buf.WriteByte('\b') - } + buf.WriteString("\033[2K\r\033[A") } + buf.WriteString("\033[2K\r") return buf.Bytes() } diff --git a/std_ansi.go b/std_ansi.go index 783b61c..322f24c 100644 --- a/std_ansi.go +++ b/std_ansi.go @@ -3,14 +3,13 @@ package readline import ( + "bufio" "io" "strconv" "strings" "sync" "unicode/utf8" "unsafe" - - "gopkg.in/bufio.v1" ) func init() { @@ -28,7 +27,7 @@ type ANSIWriter struct { func NewANSIWriter(w io.Writer) *ANSIWriter { a := &ANSIWriter{ target: w, - ch: make(chan rune, 1024), + ch: make(chan rune), } go a.ioloop() return a @@ -105,9 +104,30 @@ read: func (a *ANSIWriter) 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': - eraseLine() + killLines() case 'K': eraseLine() case 'm': @@ -121,7 +141,11 @@ func (a *ANSIWriter) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) boo } if c >= 30 && c < 40 { color |= ColorTableFg[c-30] - } else if c == 0 { + } else if c >= 40 && c < 50 { + color |= ColorTableBg[c-40] + } else if c == 4 { + color |= COMMON_LVB_UNDERSCORE + } else { // unknown code treat as reset color = ColorTableFg[7] } } @@ -129,16 +153,13 @@ func (a *ANSIWriter) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) boo break } kernel.SetConsoleTextAttribute(stdout, uintptr(color)) - case 'A': - case 'B': - case 'C': - case 'D': - case '\007': + case '\007': // set title case ';': if len(arg) == 0 || arg[len(arg)-1] != "" { arg = append(arg, "") + *argptr = arg } - fallthrough + return true default: if len(arg) == 0 { arg = append(arg, "") @@ -167,15 +188,39 @@ func (a *ANSIWriter) Write(b []byte) (int, error) { 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(sbi.dwSize.x-sbi.dwCursorPosition.x), + uintptr(size), sbi.dwCursorPosition.ptr(), uintptr(unsafe.Pointer(&written)), ) @@ -192,6 +237,8 @@ const ( COLOR_BGREEN = 0x0020 COLOR_BRED = 0x0040 COLOR_BINTENSITY = 0x0080 + + COMMON_LVB_UNDERSCORE = 0x8000 ) var ColorTableFg = []word{ diff --git a/utils.go b/utils.go index c880be3..f3b3eb6 100644 --- a/utils.go +++ b/utils.go @@ -1,6 +1,7 @@ package readline import ( + "strconv" "syscall" "unicode" @@ -8,7 +9,8 @@ import ( ) var ( - StdinFd = int(uintptr(syscall.Stdin)) + StdinFd = int(uintptr(syscall.Stdin)) + isWindows = false ) // IsTerminal returns true if the given file descriptor is a terminal. @@ -231,3 +233,14 @@ func RunesHasPrefix(r, prefix []rune) bool { } return RunesEqual(r[:len(prefix)], prefix) } + +func GetInt(s []string, def int) int { + if len(s) == 0 { + return def + } + c, err := strconv.Atoi(s[0]) + if err != nil { + return def + } + return c +} diff --git a/utils_windows.go b/utils_windows.go index 55b8eea..4cee4b6 100644 --- a/utils_windows.go +++ b/utils_windows.go @@ -2,6 +2,10 @@ package readline +func init() { + isWindows = true +} + // get width of the terminal func getWidth() int { info, _ := GetConsoleScreenBufferInfo() diff --git a/windows_api.go b/windows_api.go index 5f65eef..0a059b3 100644 --- a/windows_api.go +++ b/windows_api.go @@ -18,11 +18,14 @@ type Kernel struct { SetConsoleTextAttribute, GetConsoleScreenBufferInfo, FillConsoleOutputCharacterW, + FillConsoleOutputAttribute, + GetConsoleCursorInfo, GetStdHandle CallFunc } type short int16 type word uint16 +type dword uint32 type _COORD struct { x short @@ -48,6 +51,11 @@ type _SMALL_RECT struct { bottom short } +type _CONSOLE_CURSOR_INFO struct { + dwSize dword + bVisible bool +} + type CallFunc func(u ...uintptr) error func NewKernel() *Kernel { @@ -101,3 +109,13 @@ func GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) { ) return t, err } + +func GetConsoleCursorInfo() (*_CONSOLE_CURSOR_INFO, error) { + t := new(_CONSOLE_CURSOR_INFO) + err := kernel.GetConsoleCursorInfo(stdout, uintptr(unsafe.Pointer(t))) + return t, err +} + +func SetConsoleCursorPosition(c *_COORD) error { + return kernel.SetConsoleCursorPosition(stdout, c.ptr()) +}