Merge pull request #31 from c-bata/windows

Windows support.
This commit is contained in:
Masashi SHIBATA 2018-02-12 19:37:40 +09:00 committed by GitHub
commit dda4d96c46
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 677 additions and 118 deletions

View File

@ -4,7 +4,7 @@ import "github.com/c-bata/go-prompt"
func main() {
l := 20
out := prompt.NewVT100StandardOutputWriter()
out := prompt.NewStandardOutputWriter()
out.EraseScreen()
for i := 0; i < l; i++ {
out.CursorGoTo(i, 0)

View File

@ -38,7 +38,7 @@ func main() {
bufCh := make(chan []byte, 128)
go readBuffer(bufCh)
fmt.Print("> ")
parser := prompt.NewVT100StandardInputParser()
parser := prompt.NewStandardInputParser()
for {
b := <-bufCh

View File

@ -40,6 +40,8 @@ type ConsoleParser interface {
GetKey(b []byte) Key
// GetWinSize returns winsize struct which is the response of ioctl(2).
GetWinSize() *WinSize
// Read returns byte array.
Read() ([]byte, error)
}
type ConsoleWriter interface {

View File

@ -1,9 +1,5 @@
package prompt
import (
"syscall"
)
/*
========
@ -114,7 +110,7 @@ var emacsKeyBindings = []KeyBind{
{
Key: ControlL,
Fn: func(buf *Buffer) {
out := &VT100Writer{fd: syscall.Stdout}
out := NewStandardOutputWriter()
out.EraseScreen()
out.CursorGoTo(0, 0)
},

View File

@ -1,7 +1,5 @@
package prompt
import "syscall"
// Option is the type to replace default parameters.
// prompt.New accepts any number of options (this is functional option pattern).
type Option func(prompt *Prompt) error
@ -176,10 +174,10 @@ func OptionAddKeyBind(b ...KeyBind) Option {
// New returns a Prompt with powerful auto-completion.
func New(executor Executor, completer Completer, opts ...Option) *Prompt {
pt := &Prompt{
in: &VT100Parser{fd: syscall.Stdin},
in: NewStandardInputParser(),
renderer: &Render{
prefix: "> ",
out: &VT100Writer{fd: syscall.Stdout},
out: NewStandardOutputWriter(),
prefixTextColor: Blue,
prefixBGColor: DefaultColor,
inputTextColor: DefaultColor,

View File

@ -1,3 +1,5 @@
// +build !windows
package prompt
import (
@ -9,12 +11,14 @@ import (
"github.com/pkg/term/termios"
)
type VT100Parser struct {
const maxReadBytes = 1024
type PosixParser struct {
fd int
origTermios syscall.Termios
}
func (t *VT100Parser) Setup() error {
func (t *PosixParser) Setup() error {
// Set NonBlocking mode because if syscall.Read block this goroutine, it cannot receive data from stopCh.
if err := syscall.SetNonblock(t.fd, true); err != nil {
log.Println("[ERROR] Cannot set non blocking mode.")
@ -27,7 +31,7 @@ func (t *VT100Parser) Setup() error {
return nil
}
func (t *VT100Parser) TearDown() error {
func (t *PosixParser) TearDown() error {
if err := syscall.SetNonblock(t.fd, false); err != nil {
log.Println("[ERROR] Cannot set blocking mode.")
return err
@ -39,7 +43,16 @@ func (t *VT100Parser) TearDown() error {
return nil
}
func (t *VT100Parser) setRawMode() error {
func (t *PosixParser) Read() ([]byte, error) {
buf := make([]byte, maxReadBytes)
n, err := syscall.Read(syscall.Stdin, buf)
if err != nil {
return []byte{}, err
}
return buf[:n], nil
}
func (t *PosixParser) setRawMode() error {
x := t.origTermios.Lflag
if x &^= syscall.ICANON; x != 0 && x == t.origTermios.Lflag {
// fd is already raw mode
@ -58,14 +71,14 @@ func (t *VT100Parser) setRawMode() error {
return nil
}
func (t *VT100Parser) resetRawMode() error {
func (t *PosixParser) resetRawMode() error {
if t.origTermios.Lflag == 0 {
return nil
}
return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, &t.origTermios)
}
func (t *VT100Parser) GetKey(b []byte) Key {
func (t *PosixParser) GetKey(b []byte) Key {
for _, k := range asciiSequences {
if bytes.Equal(k.ASCIICode, b) {
return k.Key
@ -83,7 +96,7 @@ type ioctlWinsize struct {
}
// GetWinSize returns winsize struct which is the response of ioctl(2).
func (t *VT100Parser) GetWinSize() *WinSize {
func (t *PosixParser) GetWinSize() *WinSize {
ws := &ioctlWinsize{}
retCode, _, errno := syscall.Syscall(
syscall.SYS_IOCTL,
@ -237,10 +250,10 @@ var asciiSequences []*ASCIICode = []*ASCIICode{
{Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x46}}, // Linux console
}
var _ ConsoleParser = &VT100Parser{}
var _ ConsoleParser = &PosixParser{}
func NewVT100StandardInputParser() *VT100Parser {
return &VT100Parser{
func NewStandardInputParser() *PosixParser {
return &PosixParser{
fd: syscall.Stdin,
}
}

View File

@ -1,3 +1,5 @@
// +build !windows
package prompt
import (
@ -5,34 +7,34 @@ import (
"syscall"
)
type VT100Writer struct {
type PosixWriter struct {
fd int
buffer []byte
}
func (w *VT100Writer) WriteRaw(data []byte) {
func (w *PosixWriter) WriteRaw(data []byte) {
w.buffer = append(w.buffer, data...)
// Flush because sometimes the render is broken when a large amount data in buffer.
w.Flush()
return
}
func (w *VT100Writer) Write(data []byte) {
func (w *PosixWriter) Write(data []byte) {
w.WriteRaw(byteFilter(data, writeFilter))
return
}
func (w *VT100Writer) WriteRawStr(data string) {
func (w *PosixWriter) WriteRawStr(data string) {
w.WriteRaw([]byte(data))
return
}
func (w *VT100Writer) WriteStr(data string) {
func (w *PosixWriter) WriteStr(data string) {
w.Write([]byte(data))
return
}
func (w *VT100Writer) Flush() error {
func (w *PosixWriter) Flush() error {
_, err := syscall.Write(w.fd, w.buffer)
if err != nil {
return err
@ -43,48 +45,48 @@ func (w *VT100Writer) Flush() error {
/* Erase */
func (w *VT100Writer) EraseScreen() {
func (w *PosixWriter) EraseScreen() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4a})
return
}
func (w *VT100Writer) EraseUp() {
func (w *PosixWriter) EraseUp() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4a})
return
}
func (w *VT100Writer) EraseDown() {
func (w *PosixWriter) EraseDown() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x4a})
return
}
func (w *VT100Writer) EraseStartOfLine() {
func (w *PosixWriter) EraseStartOfLine() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4b})
return
}
func (w *VT100Writer) EraseEndOfLine() {
func (w *PosixWriter) EraseEndOfLine() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x4b})
return
}
func (w *VT100Writer) EraseLine() {
func (w *PosixWriter) EraseLine() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4b})
return
}
/* Cursor */
func (w *VT100Writer) ShowCursor() {
func (w *PosixWriter) ShowCursor() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x31, 0x32, 0x6c, 0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x68})
}
func (w *VT100Writer) HideCursor() {
func (w *PosixWriter) HideCursor() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x6c})
return
}
func (w *VT100Writer) CursorGoTo(row, col int) {
func (w *PosixWriter) CursorGoTo(row, col int) {
r := strconv.Itoa(row)
c := strconv.Itoa(col)
w.WriteRaw([]byte{0x1b, 0x5b})
@ -95,7 +97,7 @@ func (w *VT100Writer) CursorGoTo(row, col int) {
return
}
func (w *VT100Writer) CursorUp(n int) {
func (w *PosixWriter) CursorUp(n int) {
if n == 0 {
return
} else if n < 0 {
@ -109,7 +111,7 @@ func (w *VT100Writer) CursorUp(n int) {
return
}
func (w *VT100Writer) CursorDown(n int) {
func (w *PosixWriter) CursorDown(n int) {
if n == 0 {
return
} else if n < 0 {
@ -123,7 +125,7 @@ func (w *VT100Writer) CursorDown(n int) {
return
}
func (w *VT100Writer) CursorForward(n int) {
func (w *PosixWriter) CursorForward(n int) {
if n == 0 {
return
} else if n < 0 {
@ -137,7 +139,7 @@ func (w *VT100Writer) CursorForward(n int) {
return
}
func (w *VT100Writer) CursorBackward(n int) {
func (w *PosixWriter) CursorBackward(n int) {
if n == 0 {
return
} else if n < 0 {
@ -151,52 +153,52 @@ func (w *VT100Writer) CursorBackward(n int) {
return
}
func (w *VT100Writer) AskForCPR() {
func (w *PosixWriter) AskForCPR() {
// CPR: Cursor Position Request.
w.WriteRaw([]byte{0x1b, 0x5b, 0x36, 0x6e})
w.Flush()
return
}
func (w *VT100Writer) SaveCursor() {
func (w *PosixWriter) SaveCursor() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x73})
return
}
func (w *VT100Writer) UnSaveCursor() {
func (w *PosixWriter) UnSaveCursor() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x75})
return
}
/* Scrolling */
func (w *VT100Writer) ScrollDown() {
func (w *PosixWriter) ScrollDown() {
w.WriteRaw([]byte{0x1b, 0x44})
return
}
func (w *VT100Writer) ScrollUp() {
func (w *PosixWriter) ScrollUp() {
w.WriteRaw([]byte{0x1b, 0x4d})
return
}
/* Title */
func (w *VT100Writer) SetTitle(title string) {
func (w *PosixWriter) SetTitle(title string) {
w.WriteRaw([]byte{0x1b, 0x5d, 0x32, 0x3b})
w.WriteRaw(byteFilter([]byte(title), setTextFilter))
w.WriteRaw([]byte{0x07})
return
}
func (w *VT100Writer) ClearTitle() {
func (w *PosixWriter) ClearTitle() {
w.WriteRaw([]byte{0x1b, 0x5d, 0x32, 0x3b, 0x07})
return
}
/* Font */
func (w *VT100Writer) SetColor(fg, bg Color, bold bool) {
func (w *PosixWriter) SetColor(fg, bg Color, bold bool) {
f, ok := foregroundANSIColors[fg]
if !ok {
f = foregroundANSIColors[DefaultColor]
@ -290,10 +292,10 @@ func byteFilter(buf []byte, fn ...func(b byte) bool) []byte {
return byteFilter(ret, fn[1:]...)
}
var _ ConsoleWriter = &VT100Writer{}
var _ ConsoleWriter = &PosixWriter{}
func NewVT100StandardOutputWriter() *VT100Writer {
return &VT100Writer{
func NewStandardOutputWriter() *PosixWriter {
return &PosixWriter{
fd: syscall.Stdout,
}
}

View File

@ -4,16 +4,12 @@ import (
"io/ioutil"
"log"
"os"
"os/signal"
"syscall"
"time"
)
const (
logfile = "/tmp/go-prompt-debug.log"
envEnableLog = "GO_PROMPT_ENABLE_LOG"
maxReadBytes = 1024
)
// Executor is called when user input something text.
@ -59,12 +55,12 @@ func (p *Prompt) Run() {
bufCh := make(chan []byte, 128)
stopReadBufCh := make(chan struct{})
go readBuffer(bufCh, stopReadBufCh)
go p.readBuffer(bufCh, stopReadBufCh)
exitCh := make(chan int)
winSizeCh := make(chan *WinSize)
stopHandleSignalCh := make(chan struct{})
go handleSignals(p.in, exitCh, winSizeCh, stopHandleSignalCh)
go p.handleSignals(exitCh, winSizeCh, stopHandleSignalCh)
for {
select {
@ -87,8 +83,8 @@ func (p *Prompt) Run() {
// Set raw mode
p.in.Setup()
go readBuffer(bufCh, stopReadBufCh)
go handleSignals(p.in, exitCh, winSizeCh, stopHandleSignalCh)
go p.readBuffer(bufCh, stopReadBufCh)
go p.handleSignals(exitCh, winSizeCh, stopHandleSignalCh)
} else {
p.completion.Update(*p.buf.Document())
p.renderer.Render(p.buf, p.completion)
@ -124,6 +120,8 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
}
case BackTab:
p.completion.Previous()
case ControlSpace:
return
default:
if s, ok := p.completion.GetSelectedSuggestion(); ok {
w := p.buf.Document().GetWordBeforeCursor()
@ -136,7 +134,7 @@ func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) {
}
switch key {
case Enter, ControlJ:
case Enter, ControlJ, ControlM:
p.renderer.BreakLine(p.buf)
exec = &Exec{input: p.buf.Text()}
@ -217,7 +215,7 @@ func (p *Prompt) Input() string {
p.renderer.Render(p.buf, p.completion)
bufCh := make(chan []byte, 128)
stopReadBufCh := make(chan struct{})
go readBuffer(bufCh, stopReadBufCh)
go p.readBuffer(bufCh, stopReadBufCh)
for {
select {
@ -239,6 +237,22 @@ func (p *Prompt) Input() string {
}
}
func (p *Prompt) readBuffer(bufCh chan []byte, stopCh chan struct{}) {
log.Printf("[INFO] readBuffer start")
for {
time.Sleep(10 * time.Millisecond)
select {
case <-stopCh:
log.Print("[INFO] stop readBuffer")
return
default:
if b, err := p.in.Read(); err == nil {
bufCh <- b
}
}
}
}
func (p *Prompt) setUp() {
p.in.Setup()
p.renderer.Setup()
@ -249,60 +263,3 @@ func (p *Prompt) tearDown() {
p.in.TearDown()
p.renderer.TearDown()
}
func readBuffer(bufCh chan []byte, stopCh chan struct{}) {
buf := make([]byte, maxReadBytes)
log.Printf("[INFO] readBuffer start")
for {
time.Sleep(10 * time.Millisecond)
select {
case <-stopCh:
log.Print("[INFO] stop readBuffer")
return
default:
if n, err := syscall.Read(syscall.Stdin, buf); err == nil {
cbuf := make([]byte, n)
copy(cbuf, buf[:n])
bufCh <- cbuf
}
}
}
}
func handleSignals(in ConsoleParser, exitCh chan int, winSizeCh chan *WinSize, stop chan struct{}) {
sigCh := make(chan os.Signal, 1)
signal.Notify(
sigCh,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
syscall.SIGWINCH,
)
for {
select {
case <-stop:
log.Println("[INFO] stop handleSignals")
return
case s := <-sigCh:
switch s {
case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c
log.Println("[SIGNAL] Catch SIGINT")
exitCh <- 0
case syscall.SIGTERM: // kill -SIGTERM XXXX
log.Println("[SIGNAL] Catch SIGTERM")
exitCh <- 1
case syscall.SIGQUIT: // kill -SIGQUIT XXXX
log.Println("[SIGNAL] Catch SIGQUIT")
exitCh <- 0
case syscall.SIGWINCH:
log.Println("[SIGNAL] Catch SIGWINCH")
winSizeCh <- in.GetWinSize()
}
}
}
}

48
signal_posix.go Normal file
View File

@ -0,0 +1,48 @@
// +build !windows
package prompt
import (
"log"
"os"
"os/signal"
"syscall"
)
func (p *Prompt) handleSignals(exitCh chan int, winSizeCh chan *WinSize, stop chan struct{}) {
in := p.in
sigCh := make(chan os.Signal, 1)
signal.Notify(
sigCh,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
syscall.SIGWINCH,
)
for {
select {
case <-stop:
log.Println("[INFO] stop handleSignals")
return
case s := <-sigCh:
switch s {
case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c
log.Println("[SIGNAL] Catch SIGINT")
exitCh <- 0
case syscall.SIGTERM: // kill -SIGTERM XXXX
log.Println("[SIGNAL] Catch SIGTERM")
exitCh <- 1
case syscall.SIGQUIT: // kill -SIGQUIT XXXX
log.Println("[SIGNAL] Catch SIGQUIT")
exitCh <- 0
case syscall.SIGWINCH:
log.Println("[SIGNAL] Catch SIGWINCH")
winSizeCh <- in.GetWinSize()
}
}
}
}

43
signal_windows.go Normal file
View File

@ -0,0 +1,43 @@
// +build windows
package prompt
import (
"log"
"os"
"os/signal"
"syscall"
)
func (p *Prompt) handleSignals(exitCh chan int, winSizeCh chan *WinSize, stop chan struct{}) {
sigCh := make(chan os.Signal, 1)
signal.Notify(
sigCh,
syscall.SIGINT,
syscall.SIGTERM,
syscall.SIGQUIT,
)
for {
select {
case <-stop:
log.Println("[INFO] stop handleSignals")
return
case s := <-sigCh:
switch s {
case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c
log.Println("[SIGNAL] Catch SIGINT")
exitCh <- 0
case syscall.SIGTERM: // kill -SIGTERM XXXX
log.Println("[SIGNAL] Catch SIGTERM")
exitCh <- 1
case syscall.SIGQUIT: // kill -SIGQUIT XXXX
log.Println("[SIGNAL] Catch SIGQUIT")
exitCh <- 0
}
}
}
}

201
windows_input.go Normal file
View File

@ -0,0 +1,201 @@
// +build windows
package prompt
import (
"bytes"
"unicode/utf8"
"github.com/mattn/go-tty"
)
const maxReadBytes = 1024
type WindowsParser struct {
tty *tty.TTY
}
func (p *WindowsParser) Setup() error {
t, err := tty.Open()
if err != nil {
return err
}
p.tty = t
return nil
}
func (p *WindowsParser) TearDown() error {
return p.tty.Close()
}
func (p *WindowsParser) GetKey(b []byte) Key {
for _, k := range asciiSequences {
if bytes.Compare(k.ASCIICode, b) == 0 {
return k.Key
}
}
return NotDefined
}
func (p *WindowsParser) Read() ([]byte, error) {
buf := make([]byte, maxReadBytes)
r, err := p.tty.ReadRune()
if err != nil {
return []byte{}, err
}
n := utf8.EncodeRune(buf[:], r)
return buf[:n], nil
}
// GetWinSize returns winsize struct which is the response of ioctl(2).
func (p *WindowsParser) GetWinSize() *WinSize {
w, h, err := p.tty.Size()
if err != nil {
panic(err)
}
return &WinSize{
Row: uint16(h),
Col: uint16(w),
}
}
var asciiSequences []*ASCIICode = []*ASCIICode{
{Key: Escape, ASCIICode: []byte{0x1b}},
{Key: ControlSpace, ASCIICode: []byte{0x00}},
{Key: ControlA, ASCIICode: []byte{0x1}},
{Key: ControlB, ASCIICode: []byte{0x2}},
{Key: ControlC, ASCIICode: []byte{0x3}},
{Key: ControlD, ASCIICode: []byte{0x4}},
{Key: ControlE, ASCIICode: []byte{0x5}},
{Key: ControlF, ASCIICode: []byte{0x6}},
{Key: ControlG, ASCIICode: []byte{0x7}},
{Key: ControlH, ASCIICode: []byte{0x8}},
//{Key: ControlI, ASCIICode: []byte{0x9}},
//{Key: ControlJ, ASCIICode: []byte{0xa}},
{Key: ControlK, ASCIICode: []byte{0xb}},
{Key: ControlL, ASCIICode: []byte{0xc}},
{Key: ControlM, ASCIICode: []byte{0xd}},
{Key: ControlN, ASCIICode: []byte{0xe}},
{Key: ControlO, ASCIICode: []byte{0xf}},
{Key: ControlP, ASCIICode: []byte{0x10}},
{Key: ControlQ, ASCIICode: []byte{0x11}},
{Key: ControlR, ASCIICode: []byte{0x12}},
{Key: ControlS, ASCIICode: []byte{0x13}},
{Key: ControlT, ASCIICode: []byte{0x14}},
{Key: ControlU, ASCIICode: []byte{0x15}},
{Key: ControlV, ASCIICode: []byte{0x16}},
{Key: ControlW, ASCIICode: []byte{0x17}},
{Key: ControlX, ASCIICode: []byte{0x18}},
{Key: ControlY, ASCIICode: []byte{0x19}},
{Key: ControlZ, ASCIICode: []byte{0x1a}},
{Key: ControlBackslash, ASCIICode: []byte{0x1c}},
{Key: ControlSquareClose, ASCIICode: []byte{0x1d}},
{Key: ControlCircumflex, ASCIICode: []byte{0x1e}},
{Key: ControlUnderscore, ASCIICode: []byte{0x1f}},
{Key: Backspace, ASCIICode: []byte{0x7f}},
{Key: Up, ASCIICode: []byte{0x1b, 0x5b, 0x41}},
{Key: Down, ASCIICode: []byte{0x1b, 0x5b, 0x42}},
{Key: Right, ASCIICode: []byte{0x1b, 0x5b, 0x43}},
{Key: Left, ASCIICode: []byte{0x1b, 0x5b, 0x44}},
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x48}},
{Key: Home, ASCIICode: []byte{0x1b, 0x4f, 0x48}},
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x70}},
{Key: End, ASCIICode: []byte{0x1b, 0x4f, 0x70}},
{Key: Enter, ASCIICode: []byte{0xa}},
{Key: Delete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x7e}},
{Key: ShiftDelete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x3b, 0x02, 0x7e}},
{Key: ControlDelete, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x3b, 0x05, 0x7e}},
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x7e}},
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x04, 0x7e}},
{Key: PageUp, ASCIICode: []byte{0x1b, 0x5b, 0x05, 0x7e}},
{Key: PageDown, ASCIICode: []byte{0x1b, 0x5b, 0x06, 0x7e}},
{Key: Home, ASCIICode: []byte{0x1b, 0x5b, 0x07, 0x7e}},
{Key: End, ASCIICode: []byte{0x1b, 0x5b, 0x09, 0x7e}},
{Key: Tab, ASCIICode: []byte{0x9}},
{Key: BackTab, ASCIICode: []byte{0x1b, 0x5b, 0x5a}},
{Key: Insert, ASCIICode: []byte{0x1b, 0x5b, 0x02, 0x7e}},
{Key: F1, ASCIICode: []byte{0x1b, 0x4f, 0x50}},
{Key: F2, ASCIICode: []byte{0x1b, 0x4f, 0x51}},
{Key: F3, ASCIICode: []byte{0x1b, 0x4f, 0x52}},
{Key: F4, ASCIICode: []byte{0x1b, 0x4f, 0x53}},
{Key: F1, ASCIICode: []byte{0x1b, 0x4f, 0x50, 0x41}}, // Linux console
{Key: F2, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x42}}, // Linux console
{Key: F3, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x43}}, // Linux console
{Key: F4, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x44}}, // Linux console
{Key: F5, ASCIICode: []byte{0x1b, 0x5b, 0x5b, 0x45}}, // Linux console
{Key: F1, ASCIICode: []byte{0x1b, 0x5b, 0x11, 0x7e}}, // rxvt-unicode
{Key: F2, ASCIICode: []byte{0x1b, 0x5b, 0x12, 0x7e}}, // rxvt-unicode
{Key: F3, ASCIICode: []byte{0x1b, 0x5b, 0x13, 0x7e}}, // rxvt-unicode
{Key: F4, ASCIICode: []byte{0x1b, 0x5b, 0x14, 0x7e}}, // rxvt-unicode
{Key: F5, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x35, 0x7e}},
{Key: F6, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x37, 0x7e}},
{Key: F7, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x38, 0x7e}},
{Key: F8, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x39, 0x7e}},
{Key: F9, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x30, 0x7e}},
{Key: F10, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x31, 0x7e}},
{Key: F11, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x32, 0x7e}},
{Key: F12, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x34, 0x7e, 0x8}},
{Key: F13, ASCIICode: []byte{0x1b, 0x5b, 0x25, 0x7e}},
{Key: F14, ASCIICode: []byte{0x1b, 0x5b, 0x26, 0x7e}},
{Key: F15, ASCIICode: []byte{0x1b, 0x5b, 0x28, 0x7e}},
{Key: F16, ASCIICode: []byte{0x1b, 0x5b, 0x29, 0x7e}},
{Key: F17, ASCIICode: []byte{0x1b, 0x5b, 0x31, 0x7e}},
{Key: F18, ASCIICode: []byte{0x1b, 0x5b, 0x32, 0x7e}},
{Key: F19, ASCIICode: []byte{0x1b, 0x5b, 0x33, 0x7e}},
{Key: F20, ASCIICode: []byte{0x1b, 0x5b, 0x34, 0x7e}},
// Xterm
{Key: F13, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x02, 0x50}},
{Key: F14, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x02, 0x51}},
// &ASCIICode{Key: F15, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x02, 0x52}}, // Conflicts with CPR response
{Key: F16, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x02, 0x52}},
{Key: F17, ASCIICode: []byte{0x1b, 0x5b, 0x15, 0x3b, 0x02, 0x7e}},
{Key: F18, ASCIICode: []byte{0x1b, 0x5b, 0x17, 0x3b, 0x02, 0x7e}},
{Key: F19, ASCIICode: []byte{0x1b, 0x5b, 0x18, 0x3b, 0x02, 0x7e}},
{Key: F20, ASCIICode: []byte{0x1b, 0x5b, 0x19, 0x3b, 0x02, 0x7e}},
{Key: F21, ASCIICode: []byte{0x1b, 0x5b, 0x20, 0x3b, 0x02, 0x7e}},
{Key: F22, ASCIICode: []byte{0x1b, 0x5b, 0x21, 0x3b, 0x02, 0x7e}},
{Key: F23, ASCIICode: []byte{0x1b, 0x5b, 0x23, 0x3b, 0x02, 0x7e}},
{Key: F24, ASCIICode: []byte{0x1b, 0x5b, 0x24, 0x3b, 0x02, 0x7e}},
{Key: ControlUp, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x5a}},
{Key: ControlDown, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x5b}},
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x5c}},
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x3b, 0x5d}},
{Key: ShiftUp, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x2a}},
{Key: ShiftDown, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x2b}},
{Key: ShiftRight, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x2c}},
{Key: ShiftLeft, ASCIICode: []byte{0x1b, 0x5b, 0x01, 0x2d}},
// Tmux sends following keystrokes when control+arrow is pressed, but for
// Emacs ansi-term sends the same sequences for normal arrow keys. Consider
// it a normal arrow press, because that's more important.
{Key: Up, ASCIICode: []byte{0x1b, 0x4f, 0x41}},
{Key: Down, ASCIICode: []byte{0x1b, 0x4f, 0x42}},
{Key: Right, ASCIICode: []byte{0x1b, 0x4f, 0x43}},
{Key: Left, ASCIICode: []byte{0x1b, 0x4f, 0x44}},
{Key: ControlUp, ASCIICode: []byte{0x1b, 0x5b, 0x05, 0x41}},
{Key: ControlDown, ASCIICode: []byte{0x1b, 0x5b, 0x05, 0x42}},
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x05, 0x43}},
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x05, 0x44}},
{Key: ControlRight, ASCIICode: []byte{0x1b, 0x5b, 0x4f, 0x63}}, // rxvt
{Key: ControlLeft, ASCIICode: []byte{0x1b, 0x5b, 0x4f, 0x64}}, // rxvt
{Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x45}}, // Xterm
{Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x46}}, // Linux console
}
func NewStandardInputParser() *WindowsParser {
return &WindowsParser{}
}

299
windows_output.go Normal file
View File

@ -0,0 +1,299 @@
// +build windows
package prompt
import (
"io"
"strconv"
"github.com/mattn/go-colorable"
)
type WindowsWriter struct {
out io.Writer
buffer []byte
}
func (w *WindowsWriter) WriteRaw(data []byte) {
w.buffer = append(w.buffer, data...)
// Flush because sometimes the render is broken when a large amount data in buffer.
w.Flush()
return
}
func (w *WindowsWriter) Write(data []byte) {
w.WriteRaw(byteFilter(data, writeFilter))
return
}
func (w *WindowsWriter) WriteRawStr(data string) {
w.WriteRaw([]byte(data))
return
}
func (w *WindowsWriter) WriteStr(data string) {
w.Write([]byte(data))
return
}
func (w *WindowsWriter) Flush() error {
_, err := w.out.Write(w.buffer)
if err != nil {
return err
}
w.buffer = []byte{}
return nil
}
/* Erase */
func (w *WindowsWriter) EraseScreen() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4a})
return
}
func (w *WindowsWriter) EraseUp() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4a})
return
}
func (w *WindowsWriter) EraseDown() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x4a})
return
}
func (w *WindowsWriter) EraseStartOfLine() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4b})
return
}
func (w *WindowsWriter) EraseEndOfLine() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x4b})
return
}
func (w *WindowsWriter) EraseLine() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4b})
return
}
/* Cursor */
func (w *WindowsWriter) ShowCursor() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x31, 0x32, 0x6c, 0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x68})
}
func (w *WindowsWriter) HideCursor() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x6c})
return
}
func (w *WindowsWriter) CursorGoTo(row, col int) {
r := strconv.Itoa(row)
c := strconv.Itoa(col)
w.WriteRaw([]byte{0x1b, 0x5b})
w.WriteRaw([]byte(r))
w.WriteRaw([]byte{0x3b})
w.WriteRaw([]byte(c))
w.WriteRaw([]byte{0x48})
return
}
func (w *WindowsWriter) CursorUp(n int) {
if n < 0 {
w.CursorDown(n)
return
}
s := strconv.Itoa(n)
w.WriteRaw([]byte{0x1b, 0x5b})
w.WriteRaw([]byte(s))
w.WriteRaw([]byte{0x41})
return
}
func (w *WindowsWriter) CursorDown(n int) {
if n < 0 {
w.CursorUp(n)
return
}
s := strconv.Itoa(n)
w.WriteRaw([]byte{0x1b, 0x5b})
w.WriteRaw([]byte(s))
w.WriteRaw([]byte{0x42})
return
}
func (w *WindowsWriter) CursorForward(n int) {
if n == 0 {
return
} else if n < 0 {
w.CursorBackward(-n)
return
}
s := strconv.Itoa(n)
w.WriteRaw([]byte{0x1b, 0x5b})
w.WriteRaw([]byte(s))
w.WriteRaw([]byte{0x43})
return
}
func (w *WindowsWriter) CursorBackward(n int) {
if n == 0 {
return
} else if n < 0 {
w.CursorForward(-n)
return
}
s := strconv.Itoa(n)
w.WriteRaw([]byte{0x1b, 0x5b})
w.WriteRaw([]byte(s))
w.WriteRaw([]byte{0x44})
return
}
func (w *WindowsWriter) AskForCPR() {
// CPR: Cursor Position Request.
w.WriteRaw([]byte{0x1b, 0x5b, 0x36, 0x6e})
w.Flush()
return
}
func (w *WindowsWriter) SaveCursor() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x73})
return
}
func (w *WindowsWriter) UnSaveCursor() {
w.WriteRaw([]byte{0x1b, 0x5b, 0x75})
return
}
/* Scrolling */
func (w *WindowsWriter) ScrollDown() {
w.WriteRaw([]byte{0x1b, 0x44})
return
}
func (w *WindowsWriter) ScrollUp() {
w.WriteRaw([]byte{0x1b, 0x4d})
return
}
/* Title */
func (w *WindowsWriter) SetTitle(title string) {
w.WriteRaw([]byte{0x1b, 0x5d, 0x32, 0x3b})
w.WriteRaw(byteFilter([]byte(title), setTextFilter))
w.WriteRaw([]byte{0x07})
return
}
func (w *WindowsWriter) ClearTitle() {
w.WriteRaw([]byte{0x1b, 0x5d, 0x32, 0x3b, 0x07})
return
}
/* Font */
func (w *WindowsWriter) SetColor(fg, bg Color, bold bool) {
f, ok := foregroundANSIColors[fg]
if !ok {
f, _ = foregroundANSIColors[DefaultColor]
}
b, ok := backgroundANSIColors[bg]
if !ok {
b, _ = backgroundANSIColors[DefaultColor]
}
w.out.Write([]byte{0x1b, 0x5b, 0x33, 0x39, 0x3b, 0x34, 0x39, 0x6d})
w.WriteRaw([]byte{0x1b, 0x5b})
if !bold {
w.WriteRaw([]byte{0x30, 0x3b})
}
w.WriteRaw(f)
w.WriteRaw([]byte{0x3b})
w.WriteRaw(b)
if bold {
w.WriteRaw([]byte{0x3b, 0x31})
}
w.WriteRaw([]byte{0x6d})
return
}
var foregroundANSIColors = map[Color][]byte{
DefaultColor: {0x33, 0x39}, // 39
// Low intensity.
Black: {0x33, 0x30}, // 30
DarkRed: {0x33, 0x31}, // 31
DarkGreen: {0x33, 0x32}, // 32
Brown: {0x33, 0x33}, // 33
DarkBlue: {0x33, 0x34}, // 34
Purple: {0x33, 0x35}, // 35
Cyan: {0x33, 0x36}, //36
LightGray: {0x33, 0x37}, //37
// High intensity.
DarkGray: {0x39, 0x30}, // 90
Red: {0x39, 0x31}, // 91
Green: {0x39, 0x32}, // 92
Yellow: {0x39, 0x33}, // 93
Blue: {0x39, 0x34}, // 94
Fuchsia: {0x39, 0x35}, // 95
Turquoise: {0x39, 0x36}, // 96
White: {0x39, 0x37}, // 97
}
var backgroundANSIColors = map[Color][]byte{
DefaultColor: {0x34, 0x39}, // 49
// Low intensity.
Black: {0x34, 0x30}, // 40
DarkRed: {0x34, 0x31}, // 41
DarkGreen: {0x34, 0x32}, // 42
Brown: {0x34, 0x33}, // 43
DarkBlue: {0x34, 0x34}, // 44
Purple: {0x34, 0x35}, // 45
Cyan: {0x34, 0x36}, // 46
LightGray: {0x34, 0x37}, // 47
// High intensity
DarkGray: {0x31, 0x30, 0x30}, // 100
Red: {0x31, 0x30, 0x31}, // 101
Green: {0x31, 0x30, 0x32}, // 102
Yellow: {0x31, 0x30, 0x33}, // 103
Blue: {0x31, 0x30, 0x34}, // 104
Fuchsia: {0x31, 0x30, 0x35}, // 105
Turquoise: {0x31, 0x30, 0x36}, // 106
White: {0x31, 0x30, 0x37}, // 107
}
func writeFilter(buf byte) bool {
return buf != 0x1b && buf != 0x3f
}
func setTextFilter(buf byte) bool {
return buf != 0x1b && buf != 0x07
}
func byteFilter(buf []byte, fn ...func(b byte) bool) []byte {
if len(fn) == 0 {
return buf
}
ret := make([]byte, 0, len(buf))
f := fn[0]
for i, n := range buf {
if f(n) {
ret = append(ret, buf[i])
}
}
return byteFilter(ret, fn[1:]...)
}
var _ ConsoleWriter = &WindowsWriter{}
func NewStandardOutputWriter() *WindowsWriter {
return &WindowsWriter{
out: colorable.NewColorableStdout(),
}
}