Merge pull request #120 from c-bata/refactor-input-parser
Refactor input parser
This commit is contained in:
commit
422a0f0d6a
@ -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 {
|
||||
|
17
input.go
17
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}},
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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
26
internal/term/raw.go
Normal 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
34
internal/term/term.go
Normal 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)
|
||||
}
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user