forked from james4k/terminal
-
Notifications
You must be signed in to change notification settings - Fork 7
/
vt_posix.go
155 lines (140 loc) · 3.04 KB
/
vt_posix.go
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
145
146
147
148
149
150
151
152
153
154
155
//go:build linux || darwin || dragonfly || solaris || openbsd || netbsd || freebsd
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
package terminal
import (
"bufio"
"bytes"
"io"
"os"
"os/exec"
"unicode"
"unicode/utf8"
"github.com/creack/pty"
)
// 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) {
var err error
t := &VT{
dest: state,
}
t.pty, err = pty.Start(cmd)
if err != nil {
return nil, nil, err
}
t.rc = t.pty
t.init()
return t, t.pty, nil
}
// 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()
}