forked from mirror/readline
add windows api, ansi writer
This commit is contained in:
parent
d4826eb059
commit
9edb463230
|
@ -7,6 +7,7 @@ type Operation struct {
|
|||
t *Terminal
|
||||
buf *RuneBuffer
|
||||
outchan chan []rune
|
||||
w io.Writer
|
||||
|
||||
*opHistory
|
||||
*opSearch
|
||||
|
@ -49,6 +50,7 @@ func NewOperation(t *Terminal, cfg *Config) *Operation {
|
|||
outchan: make(chan []rune),
|
||||
opHistory: newOpHistory(cfg.HistoryFile),
|
||||
}
|
||||
op.w = op.buf.w
|
||||
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
|
||||
op.opCompleter = newOpCompleter(op.buf.w, op)
|
||||
go op.ioloop()
|
||||
|
@ -238,6 +240,10 @@ func (o *Operation) Runes() ([]rune, error) {
|
|||
return r, nil
|
||||
}
|
||||
|
||||
func (o *Operation) SetTitle(t string) {
|
||||
o.w.Write([]byte("\033[2;" + t + "\007"))
|
||||
}
|
||||
|
||||
func (o *Operation) Slice() ([]byte, error) {
|
||||
r, err := o.Runes()
|
||||
if err != nil {
|
||||
|
|
|
@ -302,7 +302,7 @@ func (r *RuneBuffer) SetStyle(start, end int, style string) {
|
|||
} else {
|
||||
r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move)))
|
||||
}
|
||||
r.w.Write([]byte("\033[" + style))
|
||||
r.w.Write([]byte("\033[" + style + "m"))
|
||||
r.w.Write([]byte(string(r.buf[start:end])))
|
||||
r.w.Write([]byte("\033[0m"))
|
||||
// TODO: move back
|
||||
|
@ -334,9 +334,11 @@ func (r *RuneBuffer) cleanOutput() []byte {
|
|||
}
|
||||
|
||||
for i := 0; i < idxLine; i++ {
|
||||
buf.WriteString("\033[2K\r\b")
|
||||
}
|
||||
buf.WriteString("\033[2K\r")
|
||||
if i != idxLine-1 {
|
||||
buf.WriteByte('\b')
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
|
|
|
@ -126,7 +126,7 @@ func (o *opSearch) SearchRefresh(x int) {
|
|||
x = x % getWidth()
|
||||
|
||||
if o.markStart > 0 {
|
||||
o.buf.SetStyle(o.markStart, o.markEnd, "4m")
|
||||
o.buf.SetStyle(o.markStart, o.markEnd, "4")
|
||||
}
|
||||
|
||||
lineCnt := o.buf.CursorLineCount()
|
||||
|
|
4
std.go
4
std.go
|
@ -6,6 +6,6 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
Stdout io.Writer = os.Stdout
|
||||
Stderr io.Writer = os.Stderr
|
||||
Stdout io.WriteCloser = os.Stdout
|
||||
Stderr io.WriteCloser = os.Stderr
|
||||
)
|
||||
|
|
|
@ -0,0 +1,217 @@
|
|||
// +build windows
|
||||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode/utf8"
|
||||
"unsafe"
|
||||
|
||||
"gopkg.in/bufio.v1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Stdout = NewANSIWriter(Stdout)
|
||||
Stderr = NewANSIWriter(Stderr)
|
||||
}
|
||||
|
||||
type ANSIWriter struct {
|
||||
target io.Writer
|
||||
ch chan rune
|
||||
wg sync.WaitGroup
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewANSIWriter(w io.Writer) *ANSIWriter {
|
||||
a := &ANSIWriter{
|
||||
target: w,
|
||||
ch: make(chan rune, 1024),
|
||||
}
|
||||
go a.ioloop()
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ANSIWriter) Close() error {
|
||||
close(a.ch)
|
||||
a.wg.Wait()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *ANSIWriter) ioloop() {
|
||||
a.wg.Add(1)
|
||||
defer a.wg.Done()
|
||||
|
||||
var (
|
||||
ok bool
|
||||
isEsc bool
|
||||
isEscSeq bool
|
||||
|
||||
char rune
|
||||
arg []string
|
||||
|
||||
target = bufio.NewWriter(a.target)
|
||||
)
|
||||
|
||||
peek := func() rune {
|
||||
select {
|
||||
case ch := <-a.ch:
|
||||
return ch
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
read:
|
||||
r := char
|
||||
if char == 0 {
|
||||
r, ok = <-a.ch
|
||||
if !ok {
|
||||
target.Flush()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
char = 0
|
||||
}
|
||||
|
||||
if isEscSeq {
|
||||
isEscSeq = a.ioloopEscSeq(target, r, &arg)
|
||||
goto read
|
||||
}
|
||||
|
||||
switch r {
|
||||
case CharEsc:
|
||||
isEsc = true
|
||||
case '[':
|
||||
if isEsc {
|
||||
arg = nil
|
||||
isEscSeq = true
|
||||
isEsc = false
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
target.WriteRune(r)
|
||||
char = peek()
|
||||
if char == 0 || char == CharEsc {
|
||||
target.Flush()
|
||||
}
|
||||
}
|
||||
goto read
|
||||
}
|
||||
|
||||
func (a *ANSIWriter) ioloopEscSeq(w *bufio.Writer, r rune, argptr *[]string) bool {
|
||||
arg := *argptr
|
||||
var err error
|
||||
switch r {
|
||||
case 'J':
|
||||
eraseLine()
|
||||
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 |= ColorTableFg[c-30]
|
||||
} else if c == 0 {
|
||||
color = ColorTableFg[7]
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
kernel.SetConsoleTextAttribute(stdout, uintptr(color))
|
||||
case 'A':
|
||||
case 'B':
|
||||
case 'C':
|
||||
case 'D':
|
||||
case '\007':
|
||||
case ';':
|
||||
if len(arg) == 0 || arg[len(arg)-1] != "" {
|
||||
arg = append(arg, "")
|
||||
}
|
||||
fallthrough
|
||||
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.ch <- r
|
||||
}
|
||||
return off, nil
|
||||
}
|
||||
|
||||
func eraseLine() error {
|
||||
sbi, err := GetConsoleScreenBufferInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var written int
|
||||
return kernel.FillConsoleOutputCharacterW(stdout, uintptr(' '),
|
||||
uintptr(sbi.dwSize.x-sbi.dwCursorPosition.x),
|
||||
sbi.dwCursorPosition.ptr(),
|
||||
uintptr(unsafe.Pointer(&written)),
|
||||
)
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
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
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
// +build windows
|
||||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"gopkg.in/bufio.v1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Stdout = NewANSIWriter(Stdout)
|
||||
Stderr = NewANSIWriter(Stderr)
|
||||
}
|
||||
|
||||
type ANSIWriter struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
func NewANSIWriter(w io.Writer) *ANSIWriter {
|
||||
a := &ANSIWriter{
|
||||
Writer: w,
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func (a *ANSIWriter) Write(b []byte) (int, error) {
|
||||
var (
|
||||
isEsc bool
|
||||
isEscSeq bool
|
||||
)
|
||||
buf := bufio.NewBuffer(nil)
|
||||
for i := 0; i < len(b); i++ {
|
||||
if isEscSeq {
|
||||
isEsc = false
|
||||
isEscSeq = false
|
||||
continue
|
||||
}
|
||||
if isEsc {
|
||||
isEsc = false
|
||||
continue
|
||||
}
|
||||
|
||||
switch b[i] {
|
||||
case CharEsc:
|
||||
isEsc = true
|
||||
continue
|
||||
case '[':
|
||||
if isEsc {
|
||||
isEscSeq = true
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
default:
|
||||
}
|
||||
}
|
||||
n, err := buf.WriteTo(a.Writer)
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
return len(b), nil
|
||||
}
|
|
@ -2,65 +2,11 @@
|
|||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
type (
|
||||
short int16
|
||||
word uint16
|
||||
|
||||
small_rect struct {
|
||||
left short
|
||||
top short
|
||||
right short
|
||||
bottom short
|
||||
}
|
||||
|
||||
coord struct {
|
||||
x short
|
||||
y short
|
||||
}
|
||||
|
||||
console_screen_buffer_info struct {
|
||||
size coord
|
||||
cursor_position coord
|
||||
attributes word
|
||||
window small_rect
|
||||
maximum_window_size coord
|
||||
}
|
||||
)
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
tmp_info console_screen_buffer_info
|
||||
|
||||
proc_get_console_screen_buffer_info = kernel32.NewProc("GetConsoleScreenBufferInfo")
|
||||
)
|
||||
|
||||
func get_console_screen_buffer_info(h syscall.Handle, info *console_screen_buffer_info) (err error) {
|
||||
r0, _, e1 := syscall.Syscall(proc_get_console_screen_buffer_info.Addr(),
|
||||
2, uintptr(h), uintptr(unsafe.Pointer(info)), 0)
|
||||
if int(r0) == 0 {
|
||||
if e1 != 0 {
|
||||
err = error(e1)
|
||||
} else {
|
||||
err = syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func get_term_size(out syscall.Handle) coord {
|
||||
err := get_console_screen_buffer_info(out, &tmp_info)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return tmp_info.size
|
||||
}
|
||||
|
||||
// get width of the terminal
|
||||
func getWidth() int {
|
||||
return int(get_term_size(syscall.Stdout).x)
|
||||
info, _ := GetConsoleScreenBufferInfo()
|
||||
if info == nil {
|
||||
return 0
|
||||
}
|
||||
return int(info.dwSize.x)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,103 @@
|
|||
// +build windows
|
||||
|
||||
package readline
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
var (
|
||||
kernel = NewKernel()
|
||||
stdout = uintptr(syscall.Stdout)
|
||||
)
|
||||
|
||||
type Kernel struct {
|
||||
SetConsoleCursorPosition,
|
||||
SetConsoleTextAttribute,
|
||||
GetConsoleScreenBufferInfo,
|
||||
FillConsoleOutputCharacterW,
|
||||
GetStdHandle CallFunc
|
||||
}
|
||||
|
||||
type short int16
|
||||
type word uint16
|
||||
|
||||
type _COORD struct {
|
||||
x short
|
||||
y short
|
||||
}
|
||||
|
||||
func (c *_COORD) ptr() uintptr {
|
||||
return uintptr(*(*int32)(unsafe.Pointer(c)))
|
||||
}
|
||||
|
||||
type _CONSOLE_SCREEN_BUFFER_INFO struct {
|
||||
dwSize _COORD
|
||||
dwCursorPosition _COORD
|
||||
wAttributes word
|
||||
srWindow _SMALL_RECT
|
||||
dwMaximumWindowSize _COORD
|
||||
}
|
||||
|
||||
type _SMALL_RECT struct {
|
||||
left short
|
||||
top short
|
||||
right short
|
||||
bottom short
|
||||
}
|
||||
|
||||
type CallFunc func(u ...uintptr) error
|
||||
|
||||
func NewKernel() *Kernel {
|
||||
k := &Kernel{}
|
||||
kernel32 := syscall.NewLazyDLL("kernel32.dll")
|
||||
v := reflect.ValueOf(k).Elem()
|
||||
t := v.Type()
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
name := t.Field(i).Name
|
||||
f := kernel32.NewProc(name)
|
||||
v.Field(i).Set(reflect.ValueOf(k.Wrap(f)))
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
func (k *Kernel) Wrap(p *syscall.LazyProc) CallFunc {
|
||||
return func(args ...uintptr) error {
|
||||
var r0 uintptr
|
||||
var e1 syscall.Errno
|
||||
size := uintptr(len(args))
|
||||
if len(args) <= 3 {
|
||||
buf := make([]uintptr, 3)
|
||||
copy(buf, args)
|
||||
r0, _, e1 = syscall.Syscall(p.Addr(), size,
|
||||
buf[0], buf[1], buf[2])
|
||||
} else {
|
||||
buf := make([]uintptr, 6)
|
||||
copy(buf, args)
|
||||
r0, _, e1 = syscall.Syscall6(p.Addr(), size,
|
||||
buf[0], buf[1], buf[2], buf[3], buf[4], buf[5],
|
||||
)
|
||||
}
|
||||
|
||||
if int(r0) == 0 {
|
||||
if e1 != 0 {
|
||||
return error(e1)
|
||||
} else {
|
||||
return syscall.EINVAL
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func GetConsoleScreenBufferInfo() (*_CONSOLE_SCREEN_BUFFER_INFO, error) {
|
||||
t := new(_CONSOLE_SCREEN_BUFFER_INFO)
|
||||
err := kernel.GetConsoleScreenBufferInfo(
|
||||
stdout,
|
||||
uintptr(unsafe.Pointer(t)),
|
||||
)
|
||||
return t, err
|
||||
}
|
Loading…
Reference in New Issue