support ANSI escape sequence for windows

This commit is contained in:
Cheney 2015-09-29 21:01:17 +08:00
parent 9edb463230
commit 6eb29567f6
5 changed files with 99 additions and 19 deletions

View File

@ -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()
}

View File

@ -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{

View File

@ -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
}

View File

@ -2,6 +2,10 @@
package readline
func init() {
isWindows = true
}
// get width of the terminal
func getWidth() int {
info, _ := GetConsoleScreenBufferInfo()

View File

@ -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())
}