-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathscreen.go
226 lines (194 loc) · 6.15 KB
/
screen.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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
package anansi
import (
"errors"
"image"
"io"
"syscall"
"github.com/jcorbin/anansi/ansi"
)
// TermScreen supports attaching a ScreenDiffer to a Term's Context.
type TermScreen struct {
ScreenDiffer
}
// ScreenDiffer supports deferred screen updating by tracking desired virtual screen
// state vs last known (Real) screen state. It also supports tracking final
// desired user cursor state, separate from any cursor state used to
// update the virtual screen. Primitive vt100 emulation is provided through
// Write* methods and Buffer processing.
type ScreenDiffer struct {
UserCursor Cursor
VirtualScreen
Real Screen
}
// VirtualScreen implements minimal terminal emulation around ScreenState.
//
// Normal textual output and control sequences may be written to it, using
// either high-level convenience methods, or the standard low-level
// byte/string/rune/byte writing methods. All such output is collected and
// processed through an internal Buffer, and ScreenState updated accordingly.
//
// TODO the vt100 emulation is certainly too minimal at present; it needs to be
// completed, and validated. E.g. cursor position is currently clamped to the
// screen size, rather than wrapped.
type VirtualScreen struct {
Screen
buf Buffer
}
// Reset calls Clear() and restores virtual cursor state.
func (sc *ScreenDiffer) Reset() {
sc.Clear()
sc.VirtualScreen.Cursor = sc.Real.Cursor
}
// Clear the virtual screen, user cursor state, and internal buffer.
func (sc *ScreenDiffer) Clear() {
sc.VirtualScreen.Clear()
sc.UserCursor = Cursor{}
sc.buf.Reset()
}
// Resize the current screen state, and invalidate to cause a full redraw.
func (sc *ScreenDiffer) Resize(size image.Point) bool {
if sc.VirtualScreen.Resize(size) {
sc.Invalidate()
return true
}
return false
}
// Invalidate forces the next WriteTo() to perform a full redraw.
func (sc *ScreenDiffer) Invalidate() {
sc.Real.Resize(image.ZP)
}
// TODO support sub-screen diffs
var errSubScreenDiff = errors.New("anansi.ScreenDiffer does not support sub-screens")
// WriteTo writes the content and ansi control sequences necessary to
// synchronize Real screen state to match VirtualScreen state. performs a
// differential update if possible, falling back to a full redraw if necessary
// or forced by Invalidate().
//
// If the given io.Writer implements higher level ansi writing methods they
// are used directly; otherwise an internal Buffer is used to first assemble
// the needed output, then delegating to Buffer.WriteTo to flush output.
//
// When using an internal Buffer, resuming a partial write after EWOULDBLOCK is
// supported, skipping the assembly step described above.
func (sc *ScreenDiffer) WriteTo(w io.Writer) (n int64, err error) {
if sc.IsSub() {
return 0, errSubScreenDiff
}
state := sc.Real
defer func() {
if err == nil {
sc.Real = state
}
}()
aw, haveAW := w.(ansiWriter)
// if caller didn't pass a buffered ansi writer, use internal buffer and
// then flush to the given io.Writer
if !haveAW {
defer func() {
n, err = sc.buf.WriteTo(w)
if err != nil && unwrapOSError(err) != syscall.EWOULDBLOCK {
sc.Reset()
sc.Invalidate()
}
}()
// continue prior write (e.g. after isEWouldBlock(err) above)
if sc.buf.Len() > 0 {
return
}
aw = &sc.buf
}
// enforce final user cursor state
sc.VirtualScreen.Cursor = sc.UserCursor
// perform (full or differential) update
var m int
m, state = sc.VirtualScreen.update(aw, state)
return int64(m), err
}
// Write writes bytes to the internal buffer, updating screen state as describe
// on VirtualScreen. Always returns nil error.
func (vsc *VirtualScreen) Write(p []byte) (n int, err error) {
n, _ = vsc.buf.Write(p)
vsc.process()
return n, nil
}
// WriteString writes a string to the internal buffer, updating screen state as
// describe on VirtualScreen. Always returns nil error.
func (vsc *VirtualScreen) WriteString(s string) (n int, err error) {
n, _ = vsc.buf.WriteString(s)
vsc.process()
return n, nil
}
// WriteRune writes a single rune to the internal buffer, updating screen state
// as described on VirtualScreen. Always returns nil error.
func (vsc *VirtualScreen) WriteRune(r rune) (n int, err error) {
n, _ = vsc.buf.WriteRune(r)
vsc.process()
return n, nil
}
// WriteByte writes a single byte to the internal buffer, updating screen state
// as described on VirtualScreen. Always returns nil error.
func (vsc *VirtualScreen) WriteByte(b byte) error {
_ = vsc.buf.WriteByte(b)
vsc.process()
return nil
}
// WriteESC writes one or more ANSI escapes to the internal buffer, updating
// screen state as described on VirtualScreen.
func (vsc *VirtualScreen) WriteESC(seqs ...ansi.Escape) int {
n := vsc.buf.WriteESC(seqs...)
vsc.process()
return n
}
// WriteSeq writes one or more ANSI escape sequences to the internal buffer,
// updating screen state as described on VirtualScreen.
func (vsc *VirtualScreen) WriteSeq(seqs ...ansi.Seq) int {
n := vsc.buf.WriteSeq(seqs...)
vsc.process()
return n
}
// WriteSGR writes one or more ANSI SGR sequences to the internal buffer,
// updating screen state as described on VirtualScreen.
func (vsc *VirtualScreen) WriteSGR(attrs ...ansi.SGRAttr) (n int) {
for i := range attrs {
if attr := vsc.Cursor.MergeSGR(attrs[i]); attr != 0 {
n += vsc.buf.WriteSGR(attr)
}
}
if n > 0 {
vsc.buf.Skip(n)
}
return n
}
func (vsc *VirtualScreen) process() {
vsc.buf.Process(vsc)
vsc.buf.Discard()
}
// Enter calls SizeToTerm.
func (tsc *TermScreen) Enter(term *Term) error { return tsc.SizeToTerm(term) }
// Exit Reset()s all virtual state, and restores real terminal graphics and
// cursor state.
func (tsc *TermScreen) Exit(term *Term) error {
// discard all virtual state...
tsc.Reset()
// ...and restore real cursor state
n := tsc.buf.WriteSGR(tsc.Real.Cursor.MergeSGR(0))
n += tsc.buf.WriteSeq(tsc.Real.Cursor.Show())
if n > 0 {
return term.Flush(&tsc.buf)
}
return nil
}
// SizeToTerm invalidates and resizes the screen to match the passed terminal's
// current size.
func (tsc *TermScreen) SizeToTerm(term *Term) error {
sz, err := term.Size()
if err == nil {
tsc.Invalidate()
tsc.Resize(sz)
}
return nil
}
var (
_ ansiWriter = &ScreenDiffer{}
_ Context = &TermScreen{}
)