forked from slimtoolkit/uilive
-
Notifications
You must be signed in to change notification settings - Fork 0
/
writer.go
176 lines (145 loc) · 3.81 KB
/
writer.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
package uilive
import (
"bytes"
"errors"
"io"
"os"
"sync"
"time"
)
// ESC is the ASCII code for escape character
const ESC = 27
// RefreshInterval is the default refresh interval to update the ui
var RefreshInterval = time.Millisecond
var overFlowHandled bool
var termWidth int
// Out is the default output writer for the Writer
var Out = os.Stdout
// ErrClosedPipe is the error returned when trying to writer is not listening
var ErrClosedPipe = errors.New("uilive: read/write on closed pipe")
// FdWriter is a writer with a file descriptor.
type FdWriter interface {
io.Writer
Fd() uintptr
}
// Writer is a buffered the writer that updates the terminal. The contents of writer will be flushed on a timed interval or when Flush is called.
type Writer struct {
// Out is the writer to write to
Out io.Writer
// RefreshInterval is the time the UI sould refresh
RefreshInterval time.Duration
ticker *time.Ticker
tdone chan bool
buf bytes.Buffer
mtx *sync.Mutex
lineCount int
}
type bypass struct {
writer *Writer
}
type newline struct {
writer *Writer
}
// New returns a new Writer with defaults
func New() *Writer {
var err error
termWidth, _, err = getTermSize()
if err != nil {
return nil
}
if termWidth != 0 {
overFlowHandled = true
}
return &Writer{
Out: Out,
RefreshInterval: RefreshInterval,
mtx: &sync.Mutex{},
}
}
// Flush writes to the out and resets the buffer. It should be called after the last call to Write to ensure that any data buffered in the Writer is written to output.
// Any incomplete escape sequence at the end is considered complete for formatting purposes.
// An error is returned if the contents of the buffer cannot be written to the underlying output stream
func (w *Writer) Flush() error {
w.mtx.Lock()
defer w.mtx.Unlock()
// do nothing if buffer is empty
if len(w.buf.Bytes()) == 0 {
return nil
}
w.clearLines()
lines := 0
var currentLine bytes.Buffer
for _, b := range w.buf.Bytes() {
if b == '\n' {
lines++
currentLine.Reset()
} else {
currentLine.Write([]byte{b})
if overFlowHandled && currentLine.Len() > termWidth {
lines++
currentLine.Reset()
}
}
}
w.lineCount = lines
_, err := w.Out.Write(w.buf.Bytes())
w.buf.Reset()
return err
}
// Start starts the listener in a non-blocking manner
func (w *Writer) Start() {
if w.ticker == nil {
w.ticker = time.NewTicker(w.RefreshInterval)
w.tdone = make(chan bool, 1)
}
go w.Listen()
}
// Stop stops the listener that updates the terminal
func (w *Writer) Stop() {
w.Flush()
close(w.tdone)
}
// Listen listens for updates to the writer's buffer and flushes to the out provided. It blocks the runtime.
func (w *Writer) Listen() {
for {
select {
case <-w.ticker.C:
if w.ticker != nil {
w.Flush()
}
case <-w.tdone:
w.mtx.Lock()
w.ticker.Stop()
w.ticker = nil
w.mtx.Unlock()
return
}
}
}
// Write save the contents of buf to the writer b. The only errors returned are ones encountered while writing to the underlying buffer.
func (w *Writer) Write(buf []byte) (n int, err error) {
w.mtx.Lock()
defer w.mtx.Unlock()
return w.buf.Write(buf)
}
// Bypass creates an io.Writer which allows non-buffered output to be written to the underlying output
func (w *Writer) Bypass() io.Writer {
return &bypass{writer: w}
}
func (b *bypass) Write(p []byte) (int, error) {
b.writer.mtx.Lock()
defer b.writer.mtx.Unlock()
b.writer.clearLines()
b.writer.lineCount = 0
return b.writer.Out.Write(p)
}
// Newline creates an io.Writer which allows buffered output to be written to the underlying output. This enable writing
// to multiple lines at once.
func (w *Writer) Newline() io.Writer {
return &newline{writer: w}
}
func (n *newline) Write(p []byte) (int, error) {
n.writer.mtx.Lock()
defer n.writer.mtx.Unlock()
return n.writer.buf.Write(p)
}