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 // the cursor will in the first line, otherwise will in the second line
// this situation only occurs in golang's Stdout // this situation only occurs in golang's Stdout
// TODO: figure out why // TODO: figure out why
if totalWidth%w == 0 && len(r.buf) == r.idx { if totalWidth%w == 0 && len(r.buf) == r.idx && !isWindows {
line-- line--
} }
@ -328,17 +328,15 @@ func (r *RuneBuffer) cleanOutput() []byte {
buf.Write([]byte("\033[J")) // just like ^k :) buf.Write([]byte("\033[J")) // just like ^k :)
idxLine := r.IdxLine() idxLine := r.IdxLine()
if idxLine == 0 { if idxLine == 0 {
buf.WriteString("\033[2K\r") buf.WriteString("\033[2K\r")
return buf.Bytes() return buf.Bytes()
} }
for i := 0; i < idxLine; i++ { for i := 0; i < idxLine; i++ {
buf.WriteString("\033[2K\r\033[A")
}
buf.WriteString("\033[2K\r") buf.WriteString("\033[2K\r")
if i != idxLine-1 {
buf.WriteByte('\b')
}
}
return buf.Bytes() return buf.Bytes()
} }

View File

@ -3,14 +3,13 @@
package readline package readline
import ( import (
"bufio"
"io" "io"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"unicode/utf8" "unicode/utf8"
"unsafe" "unsafe"
"gopkg.in/bufio.v1"
) )
func init() { func init() {
@ -28,7 +27,7 @@ type ANSIWriter struct {
func NewANSIWriter(w io.Writer) *ANSIWriter { func NewANSIWriter(w io.Writer) *ANSIWriter {
a := &ANSIWriter{ a := &ANSIWriter{
target: w, target: w,
ch: make(chan rune, 1024), ch: make(chan rune),
} }
go a.ioloop() go a.ioloop()
return a return a
@ -105,9 +104,30 @@ read:
func (a *ANSIWriter) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool { func (a *ANSIWriter) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool {
arg := *argptr arg := *argptr
var err error 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 { switch r {
case 'J': case 'J':
eraseLine() killLines()
case 'K': case 'K':
eraseLine() eraseLine()
case 'm': case 'm':
@ -121,7 +141,11 @@ func (a *ANSIWriter) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) boo
} }
if c >= 30 && c < 40 { if c >= 30 && c < 40 {
color |= ColorTableFg[c-30] 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] color = ColorTableFg[7]
} }
} }
@ -129,16 +153,13 @@ func (a *ANSIWriter) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) boo
break break
} }
kernel.SetConsoleTextAttribute(stdout, uintptr(color)) kernel.SetConsoleTextAttribute(stdout, uintptr(color))
case 'A': case '\007': // set title
case 'B':
case 'C':
case 'D':
case '\007':
case ';': case ';':
if len(arg) == 0 || arg[len(arg)-1] != "" { if len(arg) == 0 || arg[len(arg)-1] != "" {
arg = append(arg, "") arg = append(arg, "")
*argptr = arg
} }
fallthrough return true
default: default:
if len(arg) == 0 { if len(arg) == 0 {
arg = append(arg, "") arg = append(arg, "")
@ -167,15 +188,39 @@ func (a *ANSIWriter) Write(b []byte) (int, error) {
return off, nil 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 { func eraseLine() error {
sbi, err := GetConsoleScreenBufferInfo() sbi, err := GetConsoleScreenBufferInfo()
if err != nil { if err != nil {
return err return err
} }
size := sbi.dwSize.x
sbi.dwCursorPosition.x = 0
var written int var written int
return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '), return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
uintptr(sbi.dwSize.x-sbi.dwCursorPosition.x), uintptr(size),
sbi.dwCursorPosition.ptr(), sbi.dwCursorPosition.ptr(),
uintptr(unsafe.Pointer(&written)), uintptr(unsafe.Pointer(&written)),
) )
@ -192,6 +237,8 @@ const (
COLOR_BGREEN = 0x0020 COLOR_BGREEN = 0x0020
COLOR_BRED = 0x0040 COLOR_BRED = 0x0040
COLOR_BINTENSITY = 0x0080 COLOR_BINTENSITY = 0x0080
COMMON_LVB_UNDERSCORE = 0x8000
) )
var ColorTableFg = []word{ var ColorTableFg = []word{

View File

@ -1,6 +1,7 @@
package readline package readline
import ( import (
"strconv"
"syscall" "syscall"
"unicode" "unicode"
@ -9,6 +10,7 @@ import (
var ( var (
StdinFd = int(uintptr(syscall.Stdin)) StdinFd = int(uintptr(syscall.Stdin))
isWindows = false
) )
// IsTerminal returns true if the given file descriptor is a terminal. // 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) 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 package readline
func init() {
isWindows = true
}
// get width of the terminal // get width of the terminal
func getWidth() int { func getWidth() int {
info, _ := GetConsoleScreenBufferInfo() info, _ := GetConsoleScreenBufferInfo()

View File

@ -18,11 +18,14 @@ type Kernel struct {
SetConsoleTextAttribute, SetConsoleTextAttribute,
GetConsoleScreenBufferInfo, GetConsoleScreenBufferInfo,
FillConsoleOutputCharacterW, FillConsoleOutputCharacterW,
FillConsoleOutputAttribute,
GetConsoleCursorInfo,
GetStdHandle CallFunc GetStdHandle CallFunc
} }
type short int16 type short int16
type word uint16 type word uint16
type dword uint32
type _COORD struct { type _COORD struct {
x short x short
@ -48,6 +51,11 @@ type _SMALL_RECT struct {
bottom short bottom short
} }
type _CONSOLE_CURSOR_INFO struct {
dwSize dword
bVisible bool
}
type CallFunc func(u ...uintptr) error type CallFunc func(u ...uintptr) error
func NewKernel() *Kernel { func NewKernel() *Kernel {
@ -101,3 +109,13 @@ func GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) {
) )
return t, err 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())
}