1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
|
//go:build plan9 || nacl || windows
// +build plan9 nacl windows
package terminal
import (
"bufio"
"bytes"
"errors"
"io"
"os"
"os/exec"
"unicode"
"unicode/utf8"
)
// VT represents the virtual terminal emulator.
type VT struct {
dest *State
rc io.ReadCloser
br *bufio.Reader
pty *os.File
}
// Start initializes a virtual terminal emulator with the target state
// and a new pty file by starting the *exec.Command. The returned
// *os.File is the pty file.
func Start(state *State, cmd *exec.Cmd) (*VT, *os.File, error) {
return nil, nil, errors.New("Unsupported operating system")
}
// Create initializes a virtual terminal emulator with the target state
// and io.ReadCloser input.
func Create(state *State, rc io.ReadCloser) (*VT, error) {
t := &VT{
dest: state,
rc: rc,
}
t.init()
return t, nil
}
func (t *VT) init() {
t.br = bufio.NewReader(t.rc)
t.dest.numlock = true
t.dest.state = t.dest.parse
t.dest.cur.attr.fg = DefaultFG
t.dest.cur.attr.bg = DefaultBG
t.Resize(80, 24)
t.dest.reset()
}
// File returns the pty file.
func (t *VT) File() *os.File {
return t.pty
}
// Write parses input and writes terminal changes to state.
func (t *VT) Write(p []byte) (int, error) {
var written int
r := bytes.NewReader(p)
t.dest.lock()
defer t.dest.unlock()
for {
c, sz, err := r.ReadRune()
if err != nil {
if err == io.EOF {
break
}
return written, err
}
written += sz
if c == unicode.ReplacementChar && sz == 1 {
if r.Len() == 0 {
// not enough bytes for a full rune
return written - 1, nil
}
t.dest.logln("invalid utf8 sequence")
continue
}
t.dest.put(c)
}
return written, nil
}
// Close closes the pty or io.ReadCloser.
func (t *VT) Close() error {
return t.rc.Close()
}
// Parse blocks on read on pty or io.ReadCloser, then parses sequences until
// buffer empties. State is locked as soon as first rune is read, and unlocked
// when buffer is empty.
// TODO: add tests for expected blocking behavior
func (t *VT) Parse() error {
var locked bool
defer func() {
if locked {
t.dest.unlock()
}
}()
for {
c, sz, err := t.br.ReadRune()
if err != nil {
return err
}
if c == unicode.ReplacementChar && sz == 1 {
t.dest.logln("invalid utf8 sequence")
break
}
if !locked {
t.dest.lock()
locked = true
}
// put rune for parsing and update state
t.dest.put(c)
// break if our buffer is empty, or if buffer contains an
// incomplete rune.
n := t.br.Buffered()
if n == 0 || (n < 4 && !fullRuneBuffered(t.br)) {
break
}
}
return nil
}
func fullRuneBuffered(br *bufio.Reader) bool {
n := br.Buffered()
buf, err := br.Peek(n)
if err != nil {
return false
}
return utf8.FullRune(buf)
}
// Resize reports new size to pty and updates state.
func (t *VT) Resize(cols, rows int) {
t.dest.lock()
defer t.dest.unlock()
_ = t.dest.resize(cols, rows)
t.ptyResize()
}
|