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..a3e1545 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) @@ -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 { @@ -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..5c34a63 --- /dev/null +++ b/signal_windows.go @@ -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 + } + } + } +} 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(), + } +}