forked from mirror/readline
Merge pull request #6 from chzyer/feature/support_windows
support windows
This commit is contained in:
commit
2dbff40748
|
@ -1,9 +1,10 @@
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.4
|
|
||||||
- 1.5
|
- 1.5
|
||||||
before_install:
|
before_install:
|
||||||
- go get golang.org/x/crypto/ssh/terminal
|
- go get golang.org/x/crypto/ssh/terminal
|
||||||
script:
|
script:
|
||||||
- go install github.com/chzyer/readline/example
|
- GOOS=windows go install github.com/chzyer/readline/example
|
||||||
|
- GOOS=linux go install github.com/chzyer/readline/example
|
||||||
|
- GOOS=darwin go install github.com/chzyer/readline/example
|
||||||
- go test -v
|
- go test -v
|
||||||
|
|
|
@ -19,7 +19,6 @@ You can read the source code in [example/main.go](https://github.com/chzyer/read
|
||||||
|
|
||||||
# Todo
|
# Todo
|
||||||
|
|
||||||
* Add support for windows
|
|
||||||
* Vim mode
|
* Vim mode
|
||||||
* More funny examples
|
* More funny examples
|
||||||
|
|
||||||
|
@ -203,6 +202,7 @@ Users can change that in terminal simulator(i.e. iTerm2) to `Alt`+`B`
|
||||||
| Mac OS X iTerm2 Tmux | screen |
|
| Mac OS X iTerm2 Tmux | screen |
|
||||||
| Ubuntu Server 14.04 LTS | linux |
|
| Ubuntu Server 14.04 LTS | linux |
|
||||||
| Centos 7 | linux |
|
| Centos 7 | linux |
|
||||||
|
| Windows 10 | - |
|
||||||
|
|
||||||
### Notice:
|
### Notice:
|
||||||
* `Ctrl`+`A` is not working in screen because it used as a control command by default
|
* `Ctrl`+`A` is not working in screen because it used as a control command by default
|
||||||
|
|
|
@ -0,0 +1,261 @@
|
||||||
|
// +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
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
ch chan rune
|
||||||
|
wg sync.WaitGroup
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewANSIWriter(w io.Writer) *ANSIWriter {
|
||||||
|
a := &ANSIWriter{
|
||||||
|
target: w,
|
||||||
|
ch: make(chan rune),
|
||||||
|
}
|
||||||
|
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
|
||||||
|
|
||||||
|
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 { // 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.ch <- r
|
||||||
|
}
|
||||||
|
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)),
|
||||||
|
)
|
||||||
|
}
|
2
char.go
2
char.go
|
@ -26,7 +26,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
MetaPrev = -iota - 1
|
MetaPrev rune = -iota - 1
|
||||||
MetaNext
|
MetaNext
|
||||||
MetaDelete
|
MetaDelete
|
||||||
MetaBackspace
|
MetaBackspace
|
||||||
|
|
22
operation.go
22
operation.go
|
@ -7,6 +7,7 @@ type Operation struct {
|
||||||
t *Terminal
|
t *Terminal
|
||||||
buf *RuneBuffer
|
buf *RuneBuffer
|
||||||
outchan chan []rune
|
outchan chan []rune
|
||||||
|
w io.Writer
|
||||||
|
|
||||||
*opHistory
|
*opHistory
|
||||||
*opSearch
|
*opSearch
|
||||||
|
@ -20,12 +21,18 @@ type wrapWriter struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *wrapWriter) Write(b []byte) (int, error) {
|
func (w *wrapWriter) Write(b []byte) (int, error) {
|
||||||
buf := w.r.buf
|
if !w.t.IsReading() {
|
||||||
buf.Clean()
|
return w.target.Write(b)
|
||||||
n, err := w.target.Write(b)
|
|
||||||
if w.t.IsReading() {
|
|
||||||
w.r.buf.Refresh(nil)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
n int
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
w.r.buf.Refresh(func() {
|
||||||
|
n, err = w.target.Write(b)
|
||||||
|
})
|
||||||
|
|
||||||
if w.r.IsSearchMode() {
|
if w.r.IsSearchMode() {
|
||||||
w.r.SearchRefresh(-1)
|
w.r.SearchRefresh(-1)
|
||||||
}
|
}
|
||||||
|
@ -43,6 +50,7 @@ func NewOperation(t *Terminal, cfg *Config) *Operation {
|
||||||
outchan: make(chan []rune),
|
outchan: make(chan []rune),
|
||||||
opHistory: newOpHistory(cfg.HistoryFile),
|
opHistory: newOpHistory(cfg.HistoryFile),
|
||||||
}
|
}
|
||||||
|
op.w = op.buf.w
|
||||||
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
|
op.opSearch = newOpSearch(op.buf.w, op.buf, op.opHistory)
|
||||||
op.opCompleter = newOpCompleter(op.buf.w, op)
|
op.opCompleter = newOpCompleter(op.buf.w, op)
|
||||||
go op.ioloop()
|
go op.ioloop()
|
||||||
|
@ -232,6 +240,10 @@ func (o *Operation) Runes() ([]rune, error) {
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Operation) SetTitle(t string) {
|
||||||
|
o.w.Write([]byte("\033[2;" + t + "\007"))
|
||||||
|
}
|
||||||
|
|
||||||
func (o *Operation) Slice() ([]byte, error) {
|
func (o *Operation) Slice() ([]byte, error) {
|
||||||
r, err := o.Runes()
|
r, err := o.Runes()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package readline
|
||||||
|
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
const (
|
||||||
|
VK_CANCEL = 0x03
|
||||||
|
VK_BACK = 0x08
|
||||||
|
VK_TAB = 0x09
|
||||||
|
VK_RETURN = 0x0D
|
||||||
|
VK_SHIFT = 0x10
|
||||||
|
VK_CONTROL = 0x11
|
||||||
|
VK_MENU = 0x12
|
||||||
|
VK_ESCAPE = 0x1B
|
||||||
|
VK_LEFT = 0x25
|
||||||
|
VK_UP = 0x26
|
||||||
|
VK_RIGHT = 0x27
|
||||||
|
VK_DOWN = 0x28
|
||||||
|
VK_DELETE = 0x2E
|
||||||
|
VK_LSHIFT = 0xA0
|
||||||
|
VK_RSHIFT = 0xA1
|
||||||
|
VK_LCONTROL = 0xA2
|
||||||
|
VK_RCONTROL = 0xA3
|
||||||
|
)
|
||||||
|
|
||||||
|
type RawReader struct {
|
||||||
|
ctrlKey bool
|
||||||
|
altKey bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRawReader() *RawReader {
|
||||||
|
r := new(RawReader)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RawReader) Read(buf []byte) (int, error) {
|
||||||
|
ir := new(_INPUT_RECORD)
|
||||||
|
var read int
|
||||||
|
var err error
|
||||||
|
next:
|
||||||
|
err = kernel.ReadConsoleInputW(stdin,
|
||||||
|
uintptr(unsafe.Pointer(ir)),
|
||||||
|
1,
|
||||||
|
uintptr(unsafe.Pointer(&read)),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if ir.EventType != EVENT_KEY {
|
||||||
|
goto next
|
||||||
|
}
|
||||||
|
ker := (*_KEY_EVENT_RECORD)(unsafe.Pointer(&ir.Event[0]))
|
||||||
|
if ker.bKeyDown == 0 { // keyup
|
||||||
|
if r.ctrlKey || r.altKey {
|
||||||
|
switch ker.wVirtualKeyCode {
|
||||||
|
case VK_RCONTROL, VK_LCONTROL:
|
||||||
|
r.ctrlKey = false
|
||||||
|
case VK_MENU: //alt
|
||||||
|
r.altKey = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
goto next
|
||||||
|
}
|
||||||
|
|
||||||
|
if ker.unicodeChar == 0 {
|
||||||
|
var target rune
|
||||||
|
switch ker.wVirtualKeyCode {
|
||||||
|
case VK_RCONTROL, VK_LCONTROL:
|
||||||
|
r.ctrlKey = true
|
||||||
|
case VK_MENU: //alt
|
||||||
|
r.altKey = true
|
||||||
|
case VK_LEFT:
|
||||||
|
target = CharBackward
|
||||||
|
case VK_RIGHT:
|
||||||
|
target = CharForward
|
||||||
|
case VK_UP:
|
||||||
|
target = CharPrev
|
||||||
|
case VK_DOWN:
|
||||||
|
target = CharNext
|
||||||
|
}
|
||||||
|
if target != 0 {
|
||||||
|
return r.write(buf, target)
|
||||||
|
}
|
||||||
|
goto next
|
||||||
|
}
|
||||||
|
char := rune(ker.unicodeChar)
|
||||||
|
if r.ctrlKey {
|
||||||
|
switch char {
|
||||||
|
case 'A':
|
||||||
|
char = CharLineStart
|
||||||
|
case 'E':
|
||||||
|
char = CharLineEnd
|
||||||
|
case 'R':
|
||||||
|
char = CharBckSearch
|
||||||
|
case 'S':
|
||||||
|
char = CharFwdSearch
|
||||||
|
}
|
||||||
|
} else if r.altKey {
|
||||||
|
switch char {
|
||||||
|
case VK_BACK:
|
||||||
|
char = CharBackspace
|
||||||
|
}
|
||||||
|
return r.writeEsc(buf, char)
|
||||||
|
}
|
||||||
|
return r.write(buf, char)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RawReader) writeEsc(b []byte, char rune) (int, error) {
|
||||||
|
b[0] = '\033'
|
||||||
|
n := copy(b[1:], []byte(string(char)))
|
||||||
|
return n + 1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RawReader) write(b []byte, char rune) (int, error) {
|
||||||
|
n := copy(b, []byte(string(char)))
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RawReader) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -1,9 +1,6 @@
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import (
|
import "io"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
t *Terminal
|
t *Terminal
|
||||||
|
@ -26,10 +23,10 @@ func (c *Config) Init() error {
|
||||||
}
|
}
|
||||||
c.inited = true
|
c.inited = true
|
||||||
if c.Stdout == nil {
|
if c.Stdout == nil {
|
||||||
c.Stdout = os.Stdout
|
c.Stdout = Stdout
|
||||||
}
|
}
|
||||||
if c.Stderr == nil {
|
if c.Stderr == nil {
|
||||||
c.Stderr = os.Stderr
|
c.Stderr = Stderr
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
62
runebuf.go
62
runebuf.go
|
@ -22,10 +22,6 @@ func NewRuneBuffer(w io.Writer, prompt string) *RuneBuffer {
|
||||||
return rb
|
return rb
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) SetPrompt(prompt string) {
|
|
||||||
r.prompt = []rune(prompt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RuneBuffer) CurrentWidth(x int) int {
|
func (r *RuneBuffer) CurrentWidth(x int) int {
|
||||||
return RunesWidth(r.buf[:x])
|
return RunesWidth(r.buf[:x])
|
||||||
}
|
}
|
||||||
|
@ -250,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--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,31 +276,6 @@ func (r *RuneBuffer) output() []byte {
|
||||||
return buf.Bytes()
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) CleanOutput() []byte {
|
|
||||||
buf := bytes.NewBuffer(nil)
|
|
||||||
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\b")
|
|
||||||
}
|
|
||||||
buf.WriteString("\033[2K\r")
|
|
||||||
return buf.Bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RuneBuffer) Clean() {
|
|
||||||
if r.cleanInScreen {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
r.cleanInScreen = true
|
|
||||||
r.w.Write(r.CleanOutput())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RuneBuffer) Reset() []rune {
|
func (r *RuneBuffer) Reset() []rune {
|
||||||
ret := r.buf
|
ret := r.buf
|
||||||
r.buf = r.buf[:0]
|
r.buf = r.buf[:0]
|
||||||
|
@ -331,7 +302,7 @@ func (r *RuneBuffer) SetStyle(start, end int, style string) {
|
||||||
} else {
|
} else {
|
||||||
r.w.Write(bytes.Repeat([]byte("\b"), r.calWidth(move)))
|
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(string(r.buf[start:end])))
|
||||||
r.w.Write([]byte("\033[0m"))
|
r.w.Write([]byte("\033[0m"))
|
||||||
// TODO: move back
|
// TODO: move back
|
||||||
|
@ -347,3 +318,32 @@ func (r *RuneBuffer) SetWithIdx(idx int, buf []rune) {
|
||||||
func (r *RuneBuffer) Set(buf []rune) {
|
func (r *RuneBuffer) Set(buf []rune) {
|
||||||
r.SetWithIdx(len(buf), buf)
|
r.SetWithIdx(len(buf), buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RuneBuffer) SetPrompt(prompt string) {
|
||||||
|
r.prompt = []rune(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuneBuffer) cleanOutput() []byte {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
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\033[A")
|
||||||
|
}
|
||||||
|
buf.WriteString("\033[2K\r")
|
||||||
|
return buf.Bytes()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *RuneBuffer) Clean() {
|
||||||
|
if r.cleanInScreen {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.cleanInScreen = true
|
||||||
|
r.w.Write(r.cleanOutput())
|
||||||
|
}
|
||||||
|
|
|
@ -126,7 +126,7 @@ func (o *opSearch) SearchRefresh(x int) {
|
||||||
x = x % getWidth()
|
x = x % getWidth()
|
||||||
|
|
||||||
if o.markStart > 0 {
|
if o.markStart > 0 {
|
||||||
o.buf.SetStyle(o.markStart, o.markEnd, "4m")
|
o.buf.SetStyle(o.markStart, o.markEnd, "4")
|
||||||
}
|
}
|
||||||
|
|
||||||
lineCnt := o.buf.CursorLineCount()
|
lineCnt := o.buf.CursorLineCount()
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
package readline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Stdin io.ReadCloser = os.Stdin
|
||||||
|
Stdout io.WriteCloser = os.Stdout
|
||||||
|
Stderr io.WriteCloser = os.Stderr
|
||||||
|
)
|
|
@ -0,0 +1,9 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package readline
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Stdin = NewRawReader()
|
||||||
|
Stdout = NewANSIWriter(Stdout)
|
||||||
|
Stderr = NewANSIWriter(Stderr)
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ package readline
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
@ -81,7 +80,7 @@ func (t *Terminal) ioloop() {
|
||||||
expectNextChar bool
|
expectNextChar bool
|
||||||
)
|
)
|
||||||
|
|
||||||
buf := bufio.NewReader(os.Stdin)
|
buf := bufio.NewReader(Stdin)
|
||||||
for {
|
for {
|
||||||
if !expectNextChar {
|
if !expectNextChar {
|
||||||
atomic.StoreInt64(&t.isReading, 0)
|
atomic.StoreInt64(&t.isReading, 0)
|
||||||
|
|
13
utils.go
13
utils.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -2,65 +2,15 @@
|
||||||
|
|
||||||
package readline
|
package readline
|
||||||
|
|
||||||
import (
|
func init() {
|
||||||
"syscall"
|
isWindows = true
|
||||||
"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
|
// get width of the terminal
|
||||||
func getWidth() int {
|
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,152 @@
|
||||||
|
// +build windows
|
||||||
|
|
||||||
|
package readline
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"syscall"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kernel = NewKernel()
|
||||||
|
stdout = uintptr(syscall.Stdout)
|
||||||
|
stdin = uintptr(syscall.Stdin)
|
||||||
|
)
|
||||||
|
|
||||||
|
type Kernel struct {
|
||||||
|
SetConsoleCursorPosition,
|
||||||
|
SetConsoleTextAttribute,
|
||||||
|
FillConsoleOutputCharacterW,
|
||||||
|
FillConsoleOutputAttribute,
|
||||||
|
ReadConsoleInputW,
|
||||||
|
GetConsoleScreenBufferInfo,
|
||||||
|
GetConsoleCursorInfo,
|
||||||
|
GetStdHandle CallFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
type short int16
|
||||||
|
type word uint16
|
||||||
|
type dword uint32
|
||||||
|
type wchar uint16
|
||||||
|
|
||||||
|
type _COORD struct {
|
||||||
|
x short
|
||||||
|
y short
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *_COORD) ptr() uintptr {
|
||||||
|
return uintptr(*(*int32)(unsafe.Pointer(c)))
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
EVENT_KEY = 0x0001
|
||||||
|
EVENT_MOUSE = 0x0002
|
||||||
|
EVENT_WINDOW_BUFFER_SIZE = 0x0004
|
||||||
|
EVENT_MENU = 0x0008
|
||||||
|
EVENT_FOCUS = 0x0010
|
||||||
|
)
|
||||||
|
|
||||||
|
type _KEY_EVENT_RECORD struct {
|
||||||
|
bKeyDown int32
|
||||||
|
wRepeatCount word
|
||||||
|
wVirtualKeyCode word
|
||||||
|
wVirtualScanCode word
|
||||||
|
unicodeChar wchar
|
||||||
|
dwControlKeyState dword
|
||||||
|
}
|
||||||
|
|
||||||
|
// KEY_EVENT_RECORD KeyEvent;
|
||||||
|
// MOUSE_EVENT_RECORD MouseEvent;
|
||||||
|
// WINDOW_BUFFER_SIZE_RECORD WindowBufferSizeEvent;
|
||||||
|
// MENU_EVENT_RECORD MenuEvent;
|
||||||
|
// FOCUS_EVENT_RECORD FocusEvent;
|
||||||
|
type _INPUT_RECORD struct {
|
||||||
|
EventType word
|
||||||
|
Padding uint16
|
||||||
|
Event [16]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
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 _CONSOLE_CURSOR_INFO struct {
|
||||||
|
dwSize dword
|
||||||
|
bVisible bool
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
}
|
Loading…
Reference in New Issue