forked from hinshun/vt10x
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathvt_posix.go
More file actions
164 lines (144 loc) · 2.87 KB
/
vt_posix.go
File metadata and controls
164 lines (144 loc) · 2.87 KB
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
156
157
158
159
160
161
162
163
164
// +build linux darwin dragonfly solaris openbsd netbsd freebsd
package vt10x
import (
"bufio"
"bytes"
"io"
"slices"
"unicode"
"unicode/utf8"
)
type terminal struct {
*State
}
func newTerminal(info TerminalInfo) *terminal {
t := &terminal{newState(info.w)}
t.init(info.cols, info.rows)
return t
}
func (t *terminal) init(cols, rows int) {
t.numlock = true
t.state = t.parse
t.cur.Attr.FG = DefaultFG
t.cur.Attr.BG = DefaultBG
t.Resize(cols, rows)
t.reset()
}
// Write parses input and writes terminal changes to state.
func (t *terminal) Write(p []byte) (int, error) {
var written int
r := bytes.NewReader(p)
t.lock()
defer t.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.logln("invalid utf8 sequence")
continue
}
t.put(c)
}
return written, nil
}
// WriteWithChanges writes to the terminal state and returns the line numbers that changed.
func (t *terminal) WriteWithChanges(p []byte) ([]int, error) {
var dirtyLines = make(map[int]bool)
r := bytes.NewReader(p)
t.lock()
prevRow := t.cur.Y
defer t.unlock()
for {
c, sz, err := r.ReadRune()
if err != nil {
if err == io.EOF {
break
}
return uniqueSorted(dirtyLines), err
}
if c == unicode.ReplacementChar && sz == 1 {
if r.Len() == 0 {
return uniqueSorted(dirtyLines), nil
}
t.logln("invalid utf8 sequence")
continue
}
beforeRow := t.cur.Y
t.put(c)
afterRow := t.cur.Y
dirtyLines[beforeRow] = true
if afterRow != beforeRow {
dirtyLines[afterRow] = true
}
if t.cur.Y != prevRow {
prevRow = t.cur.Y
}
}
return uniqueSorted(dirtyLines), nil
}
func uniqueSorted(m map[int]bool) []int {
lines := make([]int, 0, len(m))
for line := range m {
lines = append(lines, line)
}
if len(lines) == 0 {
return lines
}
slices.Sort(lines)
return lines
}
// TODO: add tests for expected blocking behavior
func (t *terminal) Parse(br *bufio.Reader) error {
var locked bool
defer func() {
if locked {
t.unlock()
}
}()
for {
c, sz, err := br.ReadRune()
if err != nil {
return err
}
if c == unicode.ReplacementChar && sz == 1 {
t.logln("invalid utf8 sequence")
break
}
if !locked {
t.lock()
locked = true
}
// put rune for parsing and update state
t.put(c)
// break if our buffer is empty, or if buffer contains an
// incomplete rune.
n := br.Buffered()
if n == 0 || (n < 4 && !fullRuneBuffered(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)
}
func (t *terminal) Resize(cols, rows int) {
t.lock()
defer t.unlock()
_ = t.resize(cols, rows)
}