From e231f2940f90e3eb6b0604c84a63c3a0a4dbd4df Mon Sep 17 00:00:00 2001 From: c-bata Date: Tue, 18 Dec 2018 02:59:59 +0900 Subject: [PATCH] Refactor input parser --- _tools/vt100_debug/main.go | 35 ++++++---------------- input.go | 17 +++++++++-- input_posix.go | 43 ++-------------------------- input_posix_test.go => input_test.go | 16 ++++++----- input_windows.go | 11 ------- internal/term/raw.go | 26 +++++++++++++++++ internal/term/term.go | 34 ++++++++++++++++++++++ prompt.go | 2 +- 8 files changed, 95 insertions(+), 89 deletions(-) rename input_posix_test.go => input_test.go (57%) create mode 100644 internal/term/raw.go create mode 100644 internal/term/term.go diff --git a/_tools/vt100_debug/main.go b/_tools/vt100_debug/main.go index c6cd955..c6036d4 100644 --- a/_tools/vt100_debug/main.go +++ b/_tools/vt100_debug/main.go @@ -1,3 +1,5 @@ +// +build !windows + package main import ( @@ -5,44 +7,23 @@ import ( "syscall" prompt "github.com/c-bata/go-prompt" - "github.com/pkg/term/termios" + "github.com/c-bata/go-prompt/internal/term" ) -const fd = 0 - -var orig syscall.Termios - -func SetRawMode() { - var n syscall.Termios - if err := termios.Tcgetattr(uintptr(fd), &orig); err != nil { - fmt.Println("Failed to get attribute") +func main() { + if err := term.SetRaw(syscall.Stdin); err != nil { + fmt.Println(err) return } - n = orig - // "&^=" used like: https://play.golang.org/p/8eJw3JxS4O - n.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG - n.Cc[syscall.VMIN] = 1 - n.Cc[syscall.VTIME] = 0 - termios.Tcsetattr(uintptr(fd), termios.TCSANOW, (*syscall.Termios)(&n)) -} - -func Restore() { - termios.Tcsetattr(uintptr(fd), termios.TCSANOW, &orig) -} - -func main() { - SetRawMode() - defer Restore() - defer fmt.Println("exited!") + defer term.Restore() bufCh := make(chan []byte, 128) go readBuffer(bufCh) fmt.Print("> ") - parser := prompt.NewStandardInputParser() for { b := <-bufCh - if key := parser.GetKey(b); key == prompt.NotDefined { + if key := prompt.GetKey(b); key == prompt.NotDefined { fmt.Printf("Key '%s' data:'%#v'\n", string(b), b) } else { if key == prompt.ControlC { diff --git a/input.go b/input.go index 4c90b0f..307e747 100644 --- a/input.go +++ b/input.go @@ -1,5 +1,7 @@ package prompt +import "bytes" + // WinSize represents the width and height of terminal. type WinSize struct { Row uint16 @@ -12,15 +14,24 @@ type ConsoleParser interface { Setup() error // TearDown should be called after stopping input TearDown() error - // GetKey returns Key correspond to input byte codes. - GetKey(b []byte) Key // GetWinSize returns WinSize object to represent width and height of terminal. GetWinSize() *WinSize // Read returns byte array. Read() ([]byte, error) } -var asciiSequences = []*ASCIICode{ +// GetKey returns Key correspond to input byte codes. +func GetKey(b []byte) Key { + for _, k := range ASCIISequences { + if bytes.Equal(k.ASCIICode, b) { + return k.Key + } + } + return NotDefined +} + +// ASCIISequences holds mappings of the key and byte array. +var ASCIISequences = []*ASCIICode{ {Key: Escape, ASCIICode: []byte{0x1b}}, {Key: ControlSpace, ASCIICode: []byte{0x00}}, diff --git a/input_posix.go b/input_posix.go index 08666dd..14cf8c3 100644 --- a/input_posix.go +++ b/input_posix.go @@ -3,11 +3,10 @@ package prompt import ( - "bytes" "syscall" "unsafe" - "github.com/pkg/term/termios" + "github.com/c-bata/go-prompt/internal/term" ) const maxReadBytes = 1024 @@ -24,7 +23,7 @@ func (t *PosixParser) Setup() error { if err := syscall.SetNonblock(t.fd, true); err != nil { return err } - if err := t.setRawMode(); err != nil { + if err := term.SetRaw(t.fd); err != nil { return err } return nil @@ -35,7 +34,7 @@ func (t *PosixParser) TearDown() error { if err := syscall.SetNonblock(t.fd, false); err != nil { return err } - if err := t.resetRawMode(); err != nil { + if err := term.Restore(); err != nil { return err } return nil @@ -51,42 +50,6 @@ func (t *PosixParser) Read() ([]byte, error) { 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 - return nil - } - var n syscall.Termios - if err := termios.Tcgetattr(uintptr(t.fd), &t.origTermios); err != nil { - return err - } - n = t.origTermios - // "&^=" used like: https://play.golang.org/p/8eJw3JxS4O - n.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG - n.Cc[syscall.VMIN] = 1 - n.Cc[syscall.VTIME] = 0 - termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, &n) - return nil -} - -func (t *PosixParser) resetRawMode() error { - if t.origTermios.Lflag == 0 { - return nil - } - return termios.Tcsetattr(uintptr(t.fd), termios.TCSANOW, &t.origTermios) -} - -// GetKey returns Key correspond to input byte codes. -func (t *PosixParser) GetKey(b []byte) Key { - for _, k := range asciiSequences { - if bytes.Equal(k.ASCIICode, b) { - return k.Key - } - } - return NotDefined -} - // winsize is winsize struct got from the ioctl(2) system call. type ioctlWinsize struct { Row uint16 diff --git a/input_posix_test.go b/input_test.go similarity index 57% rename from input_posix_test.go rename to input_test.go index 4ab0d8c..cf064a5 100644 --- a/input_posix_test.go +++ b/input_test.go @@ -1,5 +1,3 @@ -// +build !windows - package prompt import ( @@ -7,25 +5,29 @@ import ( ) func TestPosixParserGetKey(t *testing.T) { - pp := &PosixParser{} scenarioTable := []struct { + name string input []byte expected Key }{ { + name: "escape", input: []byte{0x1b}, expected: Escape, }, { + name: "undefined", input: []byte{'a'}, expected: NotDefined, }, } for _, s := range scenarioTable { - key := pp.GetKey(s.input) - if key != s.expected { - t.Errorf("Should be %s, but got %s", key, s.expected) - } + t.Run(s.name, func(t *testing.T) { + key := GetKey(s.input) + if key != s.expected { + t.Errorf("Should be %s, but got %s", key, s.expected) + } + }) } } diff --git a/input_windows.go b/input_windows.go index 11be65c..11b7679 100644 --- a/input_windows.go +++ b/input_windows.go @@ -3,7 +3,6 @@ package prompt import ( - "bytes" "errors" "syscall" "unicode/utf8" @@ -38,16 +37,6 @@ func (p *WindowsParser) TearDown() error { return p.tty.Close() } -// GetKey returns Key correspond to input byte codes. -func (p *WindowsParser) GetKey(b []byte) Key { - for _, k := range asciiSequences { - if bytes.Compare(k.ASCIICode, b) == 0 { - return k.Key - } - } - return NotDefined -} - // Read returns byte array. func (p *WindowsParser) Read() ([]byte, error) { var ev uint32 diff --git a/internal/term/raw.go b/internal/term/raw.go new file mode 100644 index 0000000..5f6f133 --- /dev/null +++ b/internal/term/raw.go @@ -0,0 +1,26 @@ +// +build !windows + +package term + +import ( + "syscall" + + "github.com/pkg/term/termios" +) + +// SetRaw put terminal into a raw mode +func SetRaw(fd int) error { + n, err := getOriginalTermios(fd) + if err != nil { + return err + } + + n.Iflag &^= syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | + syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | + syscall.ICRNL | syscall.IXON + n.Lflag &^= syscall.ECHO | syscall.ICANON | syscall.IEXTEN | syscall.ISIG | syscall.ECHONL + n.Cflag &^= syscall.CSIZE | syscall.PARENB + n.Cc[syscall.VMIN] = 1 + n.Cc[syscall.VTIME] = 0 + return termios.Tcsetattr(uintptr(fd), termios.TCSANOW, (*syscall.Termios)(&n)) +} diff --git a/internal/term/term.go b/internal/term/term.go new file mode 100644 index 0000000..9bf17b5 --- /dev/null +++ b/internal/term/term.go @@ -0,0 +1,34 @@ +// +build !windows + +package term + +import ( + "sync" + "syscall" + + "github.com/pkg/term/termios" +) + +var ( + saveTermios syscall.Termios + saveTermiosFD int + saveTermiosOnce sync.Once +) + +func getOriginalTermios(fd int) (syscall.Termios, error) { + var err error + saveTermiosOnce.Do(func() { + saveTermiosFD = fd + err = termios.Tcgetattr(uintptr(fd), &saveTermios) + }) + return saveTermios, err +} + +// Restore terminal's mode. +func Restore() error { + o, err := getOriginalTermios(saveTermiosFD) + if err != nil { + return err + } + return termios.Tcsetattr(uintptr(saveTermiosFD), termios.TCSANOW, &o) +} diff --git a/prompt.go b/prompt.go index 81264cf..4e3ef43 100644 --- a/prompt.go +++ b/prompt.go @@ -97,7 +97,7 @@ func (p *Prompt) Run() { } func (p *Prompt) feed(b []byte) (shouldExit bool, exec *Exec) { - key := p.in.GetKey(b) + key := GetKey(b) // completion completing := p.completion.Completing()