From df1341fe00b059080400e2f868dbcd1fcc5f89ad Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Wed, 16 Aug 2017 12:22:38 +0900 Subject: [PATCH 1/5] windows support --- emacs.go | 6 +- option.go | 6 +- prompt.go | 69 +--------- prompt_posix.go | 29 ++++ prompt_windows.go | 30 ++++ signal_posix.go | 48 +++++++ signal_windows.go | 41 ++++++ vt100_input.go | 2 + vt100_input_windows.go | 192 ++++++++++++++++++++++++++ vt100_output.go | 2 + vt100_output_windows.go | 299 ++++++++++++++++++++++++++++++++++++++++ 11 files changed, 651 insertions(+), 73 deletions(-) create mode 100644 prompt_posix.go create mode 100644 prompt_windows.go create mode 100644 signal_posix.go create mode 100644 signal_windows.go create mode 100644 vt100_input_windows.go create mode 100644 vt100_output_windows.go diff --git a/emacs.go b/emacs.go index 87138a3..458d060 100644 --- a/emacs.go +++ b/emacs.go @@ -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 := NewVT100StandardOutputWriter() out.EraseScreen() out.CursorGoTo(0, 0) }, diff --git a/option.go b/option.go index 043f4e9..492ebd6 100644 --- a/option.go +++ b/option.go @@ -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: NewVT100StandardInputParser(), renderer: &Render{ prefix: "> ", - out: &VT100Writer{fd: syscall.Stdout}, + out: NewVT100StandardOutputWriter(), prefixTextColor: Blue, prefixBGColor: DefaultColor, inputTextColor: DefaultColor, diff --git a/prompt.go b/prompt.go index 4eb4cf9..9f26280 100644 --- a/prompt.go +++ b/prompt.go @@ -4,8 +4,6 @@ import ( "io/ioutil" "log" "os" - "os/signal" - "syscall" "time" ) @@ -59,12 +57,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 +85,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) @@ -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 { @@ -249,60 +247,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() - } - } - } -} diff --git a/prompt_posix.go b/prompt_posix.go new file mode 100644 index 0000000..947c41d --- /dev/null +++ b/prompt_posix.go @@ -0,0 +1,29 @@ +// +build !windows + +package prompt + +import ( + "log" + "syscall" + "time" +) + +func (p *Prompt) 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 + } + } + } +} diff --git a/prompt_windows.go b/prompt_windows.go new file mode 100644 index 0000000..deb66f4 --- /dev/null +++ b/prompt_windows.go @@ -0,0 +1,30 @@ +// +build windows + +package prompt + +import ( + "log" + "time" + "unicode/utf8" +) + +func (p *Prompt) 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 r, err := p.in.(*VT100Parser).tty.ReadRune(); err == nil { + n := utf8.EncodeRune(buf[:], r) + cbuf := make([]byte, n) + copy(cbuf, buf[:n]) + bufCh <- cbuf + } + } + } +} diff --git a/signal_posix.go b/signal_posix.go new file mode 100644 index 0000000..cff1327 --- /dev/null +++ b/signal_posix.go @@ -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() + } + } + } +} diff --git a/signal_windows.go b/signal_windows.go new file mode 100644 index 0000000..30498c3 --- /dev/null +++ b/signal_windows.go @@ -0,0 +1,41 @@ +// +build windows + +package prompt + +import ( + "log" + "os" + "os/signal" + "syscall" + "time" +) + +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 { + 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 + + default: + time.Sleep(10 * time.Millisecond) + } + } +} diff --git a/vt100_input.go b/vt100_input.go index 0105888..c00aafa 100644 --- a/vt100_input.go +++ b/vt100_input.go @@ -1,3 +1,5 @@ +// +build !windows + package prompt import ( diff --git a/vt100_input_windows.go b/vt100_input_windows.go new file mode 100644 index 0000000..2913e20 --- /dev/null +++ b/vt100_input_windows.go @@ -0,0 +1,192 @@ +// +build windows + +package prompt + +import ( + "bytes" + "syscall" + + "github.com/mattn/go-tty" +) + +type VT100Parser struct { + fd syscall.Handle + tty *tty.TTY +} + +func (t *VT100Parser) Setup() error { + tty, err := tty.Open() + if err != nil { + return err + } + t.tty = tty + return nil +} + +func (t *VT100Parser) TearDown() error { + return t.tty.Close() +} + +func (t *VT100Parser) GetKey(b []byte) Key { + for _, k := range asciiSequences { + if bytes.Compare(k.ASCIICode, b) == 0 { + return k.Key + } + } + return NotDefined +} + +// GetWinSize returns winsize struct which is the response of ioctl(2). +func (t *VT100Parser) GetWinSize() *WinSize { + w, h, err := t.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 NewVT100StandardInputParser() *VT100Parser { + return &VT100Parser{ + fd: syscall.Stdin, + } +} diff --git a/vt100_output.go b/vt100_output.go index ebb6a3d..08a9e64 100644 --- a/vt100_output.go +++ b/vt100_output.go @@ -1,3 +1,5 @@ +// +build !windows + package prompt import ( diff --git a/vt100_output_windows.go b/vt100_output_windows.go new file mode 100644 index 0000000..08d4dda --- /dev/null +++ b/vt100_output_windows.go @@ -0,0 +1,299 @@ +// +build windows + +package prompt + +import ( + "io" + "strconv" + + "github.com/mattn/go-colorable" +) + +type VT100Writer struct { + out io.Writer + buffer []byte +} + +func (w *VT100Writer) 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) { + w.WriteRaw(byteFilter(data, writeFilter)) + return +} + +func (w *VT100Writer) WriteRawStr(data string) { + w.WriteRaw([]byte(data)) + return +} + +func (w *VT100Writer) WriteStr(data string) { + w.Write([]byte(data)) + return +} + +func (w *VT100Writer) Flush() error { + _, err := w.out.Write(w.buffer) + if err != nil { + return err + } + w.buffer = []byte{} + return nil +} + +/* Erase */ + +func (w *VT100Writer) EraseScreen() { + w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4a}) + return +} + +func (w *VT100Writer) EraseUp() { + w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4a}) + return +} + +func (w *VT100Writer) EraseDown() { + w.WriteRaw([]byte{0x1b, 0x5b, 0x4a}) + return +} + +func (w *VT100Writer) EraseStartOfLine() { + w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4b}) + return +} + +func (w *VT100Writer) EraseEndOfLine() { + w.WriteRaw([]byte{0x1b, 0x5b, 0x4b}) + return +} + +func (w *VT100Writer) EraseLine() { + w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4b}) + return +} + +/* Cursor */ + +func (w *VT100Writer) ShowCursor() { + w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x31, 0x32, 0x6c, 0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x68}) +} + +func (w *VT100Writer) HideCursor() { + w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x6c}) + return +} + +func (w *VT100Writer) 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 *VT100Writer) 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 *VT100Writer) 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 *VT100Writer) 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 *VT100Writer) 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 *VT100Writer) AskForCPR() { + // CPR: Cursor Position Request. + w.WriteRaw([]byte{0x1b, 0x5b, 0x36, 0x6e}) + w.Flush() + return +} + +func (w *VT100Writer) SaveCursor() { + w.WriteRaw([]byte{0x1b, 0x5b, 0x73}) + return +} + +func (w *VT100Writer) UnSaveCursor() { + w.WriteRaw([]byte{0x1b, 0x5b, 0x75}) + return +} + +/* Scrolling */ + +func (w *VT100Writer) ScrollDown() { + w.WriteRaw([]byte{0x1b, 0x44}) + return +} + +func (w *VT100Writer) ScrollUp() { + w.WriteRaw([]byte{0x1b, 0x4d}) + return +} + +/* Title */ + +func (w *VT100Writer) 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() { + w.WriteRaw([]byte{0x1b, 0x5d, 0x32, 0x3b, 0x07}) + return +} + +/* Font */ + +func (w *VT100Writer) 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 = &VT100Writer{} + +func NewVT100StandardOutputWriter() *VT100Writer { + return &VT100Writer{ + out: colorable.NewColorableStdout(), + } +} From 2d3927aa20e31af8e1a631b260bcbd23d6211265 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Thu, 7 Sep 2017 09:00:06 +0900 Subject: [PATCH 2/5] remove Sleep --- signal_windows.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/signal_windows.go b/signal_windows.go index 30498c3..0ad5180 100644 --- a/signal_windows.go +++ b/signal_windows.go @@ -7,7 +7,6 @@ import ( "os" "os/signal" "syscall" - "time" ) func (p *Prompt) handleSignals(exitCh chan int, winSizeCh chan *WinSize, stop chan struct{}) { @@ -33,9 +32,6 @@ func (p *Prompt) handleSignals(exitCh chan int, winSizeCh chan *WinSize, stop ch case syscall.SIGQUIT: // kill -SIGQUIT XXXX log.Println("[SIGNAL] Catch SIGQUIT") exitCh <- 0 - - default: - time.Sleep(10 * time.Millisecond) } } } From 2c030818adbe553d7c66a251036722d9f5711c04 Mon Sep 17 00:00:00 2001 From: Yusuke Nakamura Date: Mon, 12 Feb 2018 18:20:04 +0900 Subject: [PATCH 3/5] Catch keycode 'ControlM' In windows, send ControlM when press enter key. --- prompt.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prompt.go b/prompt.go index 9f26280..a3e1545 100644 --- a/prompt.go +++ b/prompt.go @@ -134,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()} From 04883edce454aa58876854ef4ff74354a99ba1fd Mon Sep 17 00:00:00 2001 From: Yusuke Nakamura Date: Mon, 12 Feb 2018 17:45:05 +0900 Subject: [PATCH 4/5] Add hundling to stopHandleSignalCh --- signal_windows.go | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/signal_windows.go b/signal_windows.go index 0ad5180..5c34a63 100644 --- a/signal_windows.go +++ b/signal_windows.go @@ -19,19 +19,25 @@ func (p *Prompt) handleSignals(exitCh chan int, winSizeCh chan *WinSize, stop ch ) for { - s := <-sigCh - switch s { - case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c - log.Println("[SIGNAL] Catch SIGINT") - exitCh <- 0 + select { + case <-stop: + log.Println("[INFO] stop handleSignals") + return + case s := <-sigCh: + switch s { - case syscall.SIGTERM: // kill -SIGTERM XXXX - log.Println("[SIGNAL] Catch SIGTERM") - exitCh <- 1 + case syscall.SIGINT: // kill -SIGINT XXXX or Ctrl+c + log.Println("[SIGNAL] Catch SIGINT") + exitCh <- 0 - case syscall.SIGQUIT: // kill -SIGQUIT XXXX - log.Println("[SIGNAL] Catch SIGQUIT") - 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 + } } } } From 661e5e0c0b949d7d200078ce0b49ed786e40e4a0 Mon Sep 17 00:00:00 2001 From: c-bata Date: Mon, 12 Feb 2018 19:12:31 +0900 Subject: [PATCH 5/5] Add some refactor changes --- _tools/erasing.go | 2 +- _tools/vt100_debug.go | 2 +- console_interface.go | 2 + emacs.go | 2 +- option.go | 4 +- vt100_input.go => posix_input.go | 31 ++++++---- vt100_output.go => posix_output.go | 60 ++++++++++---------- prompt.go | 20 ++++++- prompt_posix.go | 29 ---------- prompt_windows.go | 30 ---------- vt100_input_windows.go => windows_input.go | 39 ++++++++----- vt100_output_windows.go => windows_output.go | 60 ++++++++++---------- 12 files changed, 130 insertions(+), 151 deletions(-) rename vt100_input.go => posix_input.go (93%) rename vt100_output.go => posix_output.go (79%) delete mode 100644 prompt_posix.go delete mode 100644 prompt_windows.go rename vt100_input_windows.go => windows_input.go (92%) rename vt100_output_windows.go => windows_output.go (78%) diff --git a/_tools/erasing.go b/_tools/erasing.go index 2047227..9e7ee3b 100644 --- a/_tools/erasing.go +++ b/_tools/erasing.go @@ -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) diff --git a/_tools/vt100_debug.go b/_tools/vt100_debug.go index 6625ed5..8a0dd35 100644 --- a/_tools/vt100_debug.go +++ b/_tools/vt100_debug.go @@ -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 diff --git a/console_interface.go b/console_interface.go index bfa7f1e..79d3105 100644 --- a/console_interface.go +++ b/console_interface.go @@ -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 { diff --git a/emacs.go b/emacs.go index 458d060..f38d3f7 100644 --- a/emacs.go +++ b/emacs.go @@ -110,7 +110,7 @@ var emacsKeyBindings = []KeyBind{ { Key: ControlL, Fn: func(buf *Buffer) { - out := NewVT100StandardOutputWriter() + out := NewStandardOutputWriter() out.EraseScreen() out.CursorGoTo(0, 0) }, diff --git a/option.go b/option.go index 492ebd6..94be448 100644 --- a/option.go +++ b/option.go @@ -174,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: NewVT100StandardInputParser(), + in: NewStandardInputParser(), renderer: &Render{ prefix: "> ", - out: NewVT100StandardOutputWriter(), + out: NewStandardOutputWriter(), prefixTextColor: Blue, prefixBGColor: DefaultColor, inputTextColor: DefaultColor, diff --git a/vt100_input.go b/posix_input.go similarity index 93% rename from vt100_input.go rename to posix_input.go index c00aafa..14782dd 100644 --- a/vt100_input.go +++ b/posix_input.go @@ -11,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.") @@ -29,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 @@ -41,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 @@ -60,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 @@ -85,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, @@ -239,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, } } diff --git a/vt100_output.go b/posix_output.go similarity index 79% rename from vt100_output.go rename to posix_output.go index 08a9e64..8887d6b 100644 --- a/vt100_output.go +++ b/posix_output.go @@ -7,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 @@ -45,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}) @@ -97,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 { @@ -111,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 { @@ -125,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 { @@ -139,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 { @@ -153,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] @@ -292,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, } } diff --git a/prompt.go b/prompt.go index a3e1545..cb86609 100644 --- a/prompt.go +++ b/prompt.go @@ -10,8 +10,6 @@ import ( const ( logfile = "/tmp/go-prompt-debug.log" envEnableLog = "GO_PROMPT_ENABLE_LOG" - - maxReadBytes = 1024 ) // Executor is called when user input something text. @@ -122,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() @@ -237,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() diff --git a/prompt_posix.go b/prompt_posix.go deleted file mode 100644 index 947c41d..0000000 --- a/prompt_posix.go +++ /dev/null @@ -1,29 +0,0 @@ -// +build !windows - -package prompt - -import ( - "log" - "syscall" - "time" -) - -func (p *Prompt) 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 - } - } - } -} diff --git a/prompt_windows.go b/prompt_windows.go deleted file mode 100644 index deb66f4..0000000 --- a/prompt_windows.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build windows - -package prompt - -import ( - "log" - "time" - "unicode/utf8" -) - -func (p *Prompt) 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 r, err := p.in.(*VT100Parser).tty.ReadRune(); err == nil { - n := utf8.EncodeRune(buf[:], r) - cbuf := make([]byte, n) - copy(cbuf, buf[:n]) - bufCh <- cbuf - } - } - } -} diff --git a/vt100_input_windows.go b/windows_input.go similarity index 92% rename from vt100_input_windows.go rename to windows_input.go index 2913e20..c570447 100644 --- a/vt100_input_windows.go +++ b/windows_input.go @@ -4,30 +4,31 @@ package prompt import ( "bytes" - "syscall" + "unicode/utf8" "github.com/mattn/go-tty" ) -type VT100Parser struct { - fd syscall.Handle +const maxReadBytes = 1024 + +type WindowsParser struct { tty *tty.TTY } -func (t *VT100Parser) Setup() error { - tty, err := tty.Open() +func (p *WindowsParser) Setup() error { + t, err := tty.Open() if err != nil { return err } - t.tty = tty + p.tty = t return nil } -func (t *VT100Parser) TearDown() error { - return t.tty.Close() +func (p *WindowsParser) TearDown() error { + return p.tty.Close() } -func (t *VT100Parser) GetKey(b []byte) Key { +func (p *WindowsParser) GetKey(b []byte) Key { for _, k := range asciiSequences { if bytes.Compare(k.ASCIICode, b) == 0 { return k.Key @@ -36,9 +37,19 @@ func (t *VT100Parser) GetKey(b []byte) 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 (t *VT100Parser) GetWinSize() *WinSize { - w, h, err := t.tty.Size() +func (p *WindowsParser) GetWinSize() *WinSize { + w, h, err := p.tty.Size() if err != nil { panic(err) } @@ -185,8 +196,6 @@ var asciiSequences []*ASCIICode = []*ASCIICode{ {Key: Ignore, ASCIICode: []byte{0x1b, 0x5b, 0x46}}, // Linux console } -func NewVT100StandardInputParser() *VT100Parser { - return &VT100Parser{ - fd: syscall.Stdin, - } +func NewStandardInputParser() *WindowsParser { + return &WindowsParser{} } diff --git a/vt100_output_windows.go b/windows_output.go similarity index 78% rename from vt100_output_windows.go rename to windows_output.go index 08d4dda..4f4827b 100644 --- a/vt100_output_windows.go +++ b/windows_output.go @@ -9,34 +9,34 @@ import ( "github.com/mattn/go-colorable" ) -type VT100Writer struct { +type WindowsWriter struct { out io.Writer buffer []byte } -func (w *VT100Writer) WriteRaw(data []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 *VT100Writer) Write(data []byte) { +func (w *WindowsWriter) Write(data []byte) { w.WriteRaw(byteFilter(data, writeFilter)) return } -func (w *VT100Writer) WriteRawStr(data string) { +func (w *WindowsWriter) WriteRawStr(data string) { w.WriteRaw([]byte(data)) return } -func (w *VT100Writer) WriteStr(data string) { +func (w *WindowsWriter) WriteStr(data string) { w.Write([]byte(data)) return } -func (w *VT100Writer) Flush() error { +func (w *WindowsWriter) Flush() error { _, err := w.out.Write(w.buffer) if err != nil { return err @@ -47,48 +47,48 @@ func (w *VT100Writer) Flush() error { /* Erase */ -func (w *VT100Writer) EraseScreen() { +func (w *WindowsWriter) EraseScreen() { w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4a}) return } -func (w *VT100Writer) EraseUp() { +func (w *WindowsWriter) EraseUp() { w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4a}) return } -func (w *VT100Writer) EraseDown() { +func (w *WindowsWriter) EraseDown() { w.WriteRaw([]byte{0x1b, 0x5b, 0x4a}) return } -func (w *VT100Writer) EraseStartOfLine() { +func (w *WindowsWriter) EraseStartOfLine() { w.WriteRaw([]byte{0x1b, 0x5b, 0x31, 0x4b}) return } -func (w *VT100Writer) EraseEndOfLine() { +func (w *WindowsWriter) EraseEndOfLine() { w.WriteRaw([]byte{0x1b, 0x5b, 0x4b}) return } -func (w *VT100Writer) EraseLine() { +func (w *WindowsWriter) EraseLine() { w.WriteRaw([]byte{0x1b, 0x5b, 0x32, 0x4b}) return } /* Cursor */ -func (w *VT100Writer) ShowCursor() { +func (w *WindowsWriter) ShowCursor() { w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x31, 0x32, 0x6c, 0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x68}) } -func (w *VT100Writer) HideCursor() { +func (w *WindowsWriter) HideCursor() { w.WriteRaw([]byte{0x1b, 0x5b, 0x3f, 0x32, 0x35, 0x6c}) return } -func (w *VT100Writer) CursorGoTo(row, col int) { +func (w *WindowsWriter) CursorGoTo(row, col int) { r := strconv.Itoa(row) c := strconv.Itoa(col) w.WriteRaw([]byte{0x1b, 0x5b}) @@ -99,7 +99,7 @@ func (w *VT100Writer) CursorGoTo(row, col int) { return } -func (w *VT100Writer) CursorUp(n int) { +func (w *WindowsWriter) CursorUp(n int) { if n < 0 { w.CursorDown(n) return @@ -111,7 +111,7 @@ func (w *VT100Writer) CursorUp(n int) { return } -func (w *VT100Writer) CursorDown(n int) { +func (w *WindowsWriter) CursorDown(n int) { if n < 0 { w.CursorUp(n) return @@ -123,7 +123,7 @@ func (w *VT100Writer) CursorDown(n int) { return } -func (w *VT100Writer) CursorForward(n int) { +func (w *WindowsWriter) CursorForward(n int) { if n == 0 { return } else if n < 0 { @@ -137,7 +137,7 @@ func (w *VT100Writer) CursorForward(n int) { return } -func (w *VT100Writer) CursorBackward(n int) { +func (w *WindowsWriter) CursorBackward(n int) { if n == 0 { return } else if n < 0 { @@ -151,52 +151,52 @@ func (w *VT100Writer) CursorBackward(n int) { return } -func (w *VT100Writer) AskForCPR() { +func (w *WindowsWriter) AskForCPR() { // CPR: Cursor Position Request. w.WriteRaw([]byte{0x1b, 0x5b, 0x36, 0x6e}) w.Flush() return } -func (w *VT100Writer) SaveCursor() { +func (w *WindowsWriter) SaveCursor() { w.WriteRaw([]byte{0x1b, 0x5b, 0x73}) return } -func (w *VT100Writer) UnSaveCursor() { +func (w *WindowsWriter) UnSaveCursor() { w.WriteRaw([]byte{0x1b, 0x5b, 0x75}) return } /* Scrolling */ -func (w *VT100Writer) ScrollDown() { +func (w *WindowsWriter) ScrollDown() { w.WriteRaw([]byte{0x1b, 0x44}) return } -func (w *VT100Writer) ScrollUp() { +func (w *WindowsWriter) ScrollUp() { w.WriteRaw([]byte{0x1b, 0x4d}) return } /* Title */ -func (w *VT100Writer) SetTitle(title string) { +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 *VT100Writer) ClearTitle() { +func (w *WindowsWriter) ClearTitle() { w.WriteRaw([]byte{0x1b, 0x5d, 0x32, 0x3b, 0x07}) return } /* Font */ -func (w *VT100Writer) SetColor(fg, bg Color, bold bool) { +func (w *WindowsWriter) SetColor(fg, bg Color, bold bool) { f, ok := foregroundANSIColors[fg] if !ok { f, _ = foregroundANSIColors[DefaultColor] @@ -290,10 +290,10 @@ func byteFilter(buf []byte, fn ...func(b byte) bool) []byte { return byteFilter(ret, fn[1:]...) } -var _ ConsoleWriter = &VT100Writer{} +var _ ConsoleWriter = &WindowsWriter{} -func NewVT100StandardOutputWriter() *VT100Writer { - return &VT100Writer{ +func NewStandardOutputWriter() *WindowsWriter { + return &WindowsWriter{ out: colorable.NewColorableStdout(), } }