-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Pedro Castillo <[email protected]>
- Loading branch information
Showing
13 changed files
with
815 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
package termutil | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
) | ||
|
||
// ASCII list the possible supported ASCII key sequence | ||
var ASCII = []string{ | ||
"ctrl-@", | ||
"ctrl-a", | ||
"ctrl-b", | ||
"ctrl-c", | ||
"ctrl-d", | ||
"ctrl-e", | ||
"ctrl-f", | ||
"ctrl-g", | ||
"ctrl-h", | ||
"ctrl-i", | ||
"ctrl-j", | ||
"ctrl-k", | ||
"ctrl-l", | ||
"ctrl-m", | ||
"ctrl-n", | ||
"ctrl-o", | ||
"ctrl-p", | ||
"ctrl-q", | ||
"ctrl-r", | ||
"ctrl-s", | ||
"ctrl-t", | ||
"ctrl-u", | ||
"ctrl-v", | ||
"ctrl-w", | ||
"ctrl-x", | ||
"ctrl-y", | ||
"ctrl-z", | ||
"ctrl-[", | ||
"ctrl-\\", | ||
"ctrl-]", | ||
"ctrl-^", | ||
"ctrl-_", | ||
} | ||
|
||
// ToBytes converts a string representing a suite of key-sequence to the corresponding ASCII code. | ||
func ToBytes(keys string) ([]byte, error) { | ||
codes := []byte{} | ||
next: | ||
for _, key := range strings.Split(keys, ",") { | ||
if len(key) != 1 { | ||
for code, ctrl := range ASCII { | ||
if ctrl == key { | ||
codes = append(codes, byte(code)) | ||
continue next | ||
} | ||
} | ||
if key == "DEL" { | ||
codes = append(codes, 127) | ||
} else { | ||
return nil, fmt.Errorf("Unknown character: '%s'", key) | ||
} | ||
} else { | ||
codes = append(codes, key[0]) | ||
} | ||
} | ||
return codes, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package termutil | ||
|
||
import ( | ||
"testing" | ||
|
||
"gotest.tools/assert" | ||
is "gotest.tools/assert/cmp" | ||
) | ||
|
||
func TestToBytes(t *testing.T) { | ||
codes, err := ToBytes("ctrl-a,a") | ||
assert.NilError(t, err) | ||
assert.Check(t, is.DeepEqual([]byte{1, 97}, codes)) | ||
|
||
_, err = ToBytes("shift-z") | ||
assert.Check(t, is.ErrorContains(err, "")) | ||
|
||
codes, err = ToBytes("ctrl-@,ctrl-[,~,ctrl-o") | ||
assert.NilError(t, err) | ||
assert.Check(t, is.DeepEqual([]byte{0, 27, 126, 15}, codes)) | ||
|
||
codes, err = ToBytes("DEL,+") | ||
assert.NilError(t, err) | ||
assert.Check(t, is.DeepEqual([]byte{127, 43}, codes)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
package termutil | ||
|
||
import ( | ||
"io" | ||
) | ||
|
||
// EscapeError is special error which returned by a TTY proxy reader's Read() | ||
// method in case its detach escape sequence is read. | ||
type EscapeError struct{} | ||
|
||
func (EscapeError) Error() string { | ||
return "read escape sequence" | ||
} | ||
|
||
// escapeProxy is used only for attaches with a TTY. It is used to proxy | ||
// stdin keypresses from the underlying reader and look for the passed in | ||
// escape key sequence to signal a detach. | ||
type escapeProxy struct { | ||
escapeKeys []byte | ||
escapeKeyPos int | ||
r io.Reader | ||
} | ||
|
||
// NewEscapeProxy returns a new TTY proxy reader which wraps the given reader | ||
// and detects when the specified escape keys are read, in which case the Read | ||
// method will return an error of type EscapeError. | ||
func NewEscapeProxy(r io.Reader, escapeKeys []byte) io.Reader { | ||
return &escapeProxy{ | ||
escapeKeys: escapeKeys, | ||
r: r, | ||
} | ||
} | ||
|
||
func (r *escapeProxy) Read(buf []byte) (int, error) { | ||
nr, err := r.r.Read(buf) | ||
|
||
if len(r.escapeKeys) == 0 { | ||
return nr, err | ||
} | ||
|
||
preserve := func() { | ||
// this preserves the original key presses in the passed in buffer | ||
nr += r.escapeKeyPos | ||
preserve := make([]byte, 0, r.escapeKeyPos+len(buf)) | ||
preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...) | ||
preserve = append(preserve, buf...) | ||
r.escapeKeyPos = 0 | ||
copy(buf[0:nr], preserve) | ||
} | ||
|
||
if nr != 1 || err != nil { | ||
if r.escapeKeyPos > 0 { | ||
preserve() | ||
} | ||
return nr, err | ||
} | ||
|
||
if buf[0] != r.escapeKeys[r.escapeKeyPos] { | ||
if r.escapeKeyPos > 0 { | ||
preserve() | ||
} | ||
return nr, nil | ||
} | ||
|
||
if r.escapeKeyPos == len(r.escapeKeys)-1 { | ||
return 0, EscapeError{} | ||
} | ||
|
||
// Looks like we've got an escape key, but we need to match again on the next | ||
// read. | ||
// Store the current escape key we found so we can look for the next one on | ||
// the next read. | ||
// Since this is an escape key, make sure we don't let the caller read it | ||
// If later on we find that this is not the escape sequence, we'll add the | ||
// keys back | ||
r.escapeKeyPos++ | ||
return nr - r.escapeKeyPos, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
package termutil | ||
|
||
import ( | ||
"bytes" | ||
"fmt" | ||
"testing" | ||
|
||
"gotest.tools/assert" | ||
is "gotest.tools/assert/cmp" | ||
) | ||
|
||
func TestEscapeProxyRead(t *testing.T) { | ||
escapeKeys, _ := ToBytes("") | ||
keys, _ := ToBytes("a") | ||
reader := NewEscapeProxy(bytes.NewReader(keys), escapeKeys) | ||
buf := make([]byte, len(keys)) | ||
nr, err := reader.Read(buf) | ||
assert.NilError(t, err) | ||
assert.Equal(t, nr, len(keys), fmt.Sprintf("nr %d should be equal to the number of %d", nr, len(keys))) | ||
assert.DeepEqual(t, keys, buf) | ||
|
||
keys, _ = ToBytes("a,b,c") | ||
reader = NewEscapeProxy(bytes.NewReader(keys), escapeKeys) | ||
buf = make([]byte, len(keys)) | ||
nr, err = reader.Read(buf) | ||
assert.NilError(t, err) | ||
assert.Equal(t, nr, len(keys), fmt.Sprintf("nr %d should be equal to the number of %d", nr, len(keys))) | ||
assert.DeepEqual(t, keys, buf) | ||
|
||
keys, _ = ToBytes("") | ||
reader = NewEscapeProxy(bytes.NewReader(keys), escapeKeys) | ||
buf = make([]byte, len(keys)) | ||
nr, err = reader.Read(buf) | ||
assert.Assert(t, is.ErrorContains(err, ""), "Should throw error when no keys are to read") | ||
assert.Equal(t, nr, 0, "nr should be zero") | ||
assert.Check(t, is.Len(keys, 0)) | ||
assert.Check(t, is.Len(buf, 0)) | ||
|
||
escapeKeys, _ = ToBytes("DEL") | ||
keys, _ = ToBytes("a,b,c,+") | ||
reader = NewEscapeProxy(bytes.NewReader(keys), escapeKeys) | ||
buf = make([]byte, len(keys)) | ||
nr, err = reader.Read(buf) | ||
assert.NilError(t, err) | ||
assert.Equal(t, nr, len(keys), fmt.Sprintf("nr %d should be equal to the number of %d", nr, len(keys))) | ||
assert.DeepEqual(t, keys, buf) | ||
|
||
keys, _ = ToBytes("") | ||
reader = NewEscapeProxy(bytes.NewReader(keys), escapeKeys) | ||
buf = make([]byte, len(keys)) | ||
nr, err = reader.Read(buf) | ||
assert.Assert(t, is.ErrorContains(err, ""), "Should throw error when no keys are to read") | ||
assert.Equal(t, nr, 0, "nr should be zero") | ||
assert.Check(t, is.Len(keys, 0)) | ||
assert.Check(t, is.Len(buf, 0)) | ||
|
||
escapeKeys, _ = ToBytes("ctrl-x,ctrl-@") | ||
keys, _ = ToBytes("DEL") | ||
reader = NewEscapeProxy(bytes.NewReader(keys), escapeKeys) | ||
buf = make([]byte, len(keys)) | ||
nr, err = reader.Read(buf) | ||
assert.NilError(t, err) | ||
assert.Equal(t, nr, 1, fmt.Sprintf("nr %d should be equal to the number of 1", nr)) | ||
assert.DeepEqual(t, keys, buf) | ||
|
||
escapeKeys, _ = ToBytes("ctrl-c") | ||
keys, _ = ToBytes("ctrl-c") | ||
reader = NewEscapeProxy(bytes.NewReader(keys), escapeKeys) | ||
buf = make([]byte, len(keys)) | ||
nr, err = reader.Read(buf) | ||
assert.Error(t, err, "read escape sequence") | ||
assert.Equal(t, nr, 0, "nr should be equal to 0") | ||
assert.DeepEqual(t, keys, buf) | ||
|
||
escapeKeys, _ = ToBytes("ctrl-c,ctrl-z") | ||
keys, _ = ToBytes("ctrl-c,ctrl-z") | ||
reader = NewEscapeProxy(bytes.NewReader(keys), escapeKeys) | ||
buf = make([]byte, 1) | ||
nr, err = reader.Read(buf) | ||
assert.NilError(t, err) | ||
assert.Equal(t, nr, 0, "nr should be equal to 0") | ||
assert.DeepEqual(t, keys[0:1], buf) | ||
nr, err = reader.Read(buf) | ||
assert.Error(t, err, "read escape sequence") | ||
assert.Equal(t, nr, 0, "nr should be equal to 0") | ||
assert.DeepEqual(t, keys[1:], buf) | ||
|
||
escapeKeys, _ = ToBytes("ctrl-c,ctrl-z") | ||
keys, _ = ToBytes("ctrl-c,DEL,+") | ||
reader = NewEscapeProxy(bytes.NewReader(keys), escapeKeys) | ||
buf = make([]byte, 1) | ||
nr, err = reader.Read(buf) | ||
assert.NilError(t, err) | ||
assert.Equal(t, nr, 0, "nr should be equal to 0") | ||
assert.DeepEqual(t, keys[0:1], buf) | ||
buf = make([]byte, len(keys)) | ||
nr, err = reader.Read(buf) | ||
assert.NilError(t, err) | ||
assert.Equal(t, nr, len(keys), fmt.Sprintf("nr should be equal to %d", len(keys))) | ||
assert.DeepEqual(t, keys, buf) | ||
|
||
escapeKeys, _ = ToBytes("ctrl-c,ctrl-z") | ||
keys, _ = ToBytes("ctrl-c,DEL") | ||
reader = NewEscapeProxy(bytes.NewReader(keys), escapeKeys) | ||
buf = make([]byte, 1) | ||
nr, err = reader.Read(buf) | ||
assert.NilError(t, err) | ||
assert.Equal(t, nr, 0, "nr should be equal to 0") | ||
assert.DeepEqual(t, keys[0:1], buf) | ||
buf = make([]byte, len(keys)) | ||
nr, err = reader.Read(buf) | ||
assert.NilError(t, err) | ||
assert.Equal(t, nr, len(keys), fmt.Sprintf("nr should be equal to %d", len(keys))) | ||
assert.DeepEqual(t, keys, buf) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package termutil | ||
|
||
import ( | ||
"os" | ||
) | ||
|
||
var stdTerm = NewTerminal(os.Stdin, os.Stdout, os.Stderr) | ||
|
||
func StdTerminal() *Terminal { | ||
return stdTerm | ||
} | ||
|
||
func In() *os.File { | ||
return StdTerminal().In() | ||
} | ||
|
||
func Out() *os.File { | ||
return StdTerminal().Out() | ||
} | ||
|
||
func Err() *os.File { | ||
return StdTerminal().Err() | ||
} | ||
|
||
func IsTTY() bool { | ||
return StdTerminal().IsTTY() | ||
} | ||
|
||
func MakeRaw() error { | ||
return StdTerminal().MakeRaw() | ||
} | ||
|
||
func Restore() error { | ||
return StdTerminal().Restore() | ||
} | ||
|
||
func GetWinsize() (*Winsize, error) { | ||
return StdTerminal().GetWinsize() | ||
} | ||
|
||
func SetWinsize(ws *Winsize) error { | ||
return StdTerminal().SetWinsize(ws) | ||
} | ||
|
||
func GetState() (*Termios, error) { | ||
return StdTerminal().GetState() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// +build !windows | ||
|
||
package termutil | ||
|
||
import ( | ||
"syscall" | ||
"unsafe" | ||
|
||
"golang.org/x/sys/unix" | ||
) | ||
|
||
func tcget(fd uintptr, p *Termios) syscall.Errno { | ||
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(getTermiosOp), uintptr(unsafe.Pointer(p))) | ||
return err | ||
} | ||
|
||
func tcset(fd uintptr, p *Termios) syscall.Errno { | ||
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, setTermiosOp, uintptr(unsafe.Pointer(p))) | ||
return err | ||
} |
Oops, something went wrong.