Refactor input parser

This commit is contained in:
c-bata 2018-12-18 02:59:59 +09:00
parent e818e3c1e6
commit e231f2940f
8 changed files with 95 additions and 89 deletions

View File

@ -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 {

View File

@ -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}},

View File

@ -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

View File

@ -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)
}
})
}
}

View File

@ -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

26
internal/term/raw.go Normal file
View File

@ -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))
}

34
internal/term/term.go Normal file
View File

@ -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)
}

View File

@ -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()