-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmatrix.go
135 lines (111 loc) · 2.92 KB
/
matrix.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
package termite
import (
"context"
"fmt"
"io"
"sync"
"time"
)
// Matrix is a multiline structure that reflects its state on screen
type Matrix interface {
RefreshInterval() time.Duration
NewLineStringWriter() io.StringWriter
NewLineWriter() io.Writer
Start() context.CancelFunc
}
// MatrixLine an accessor to a line in a Matrix structure
type MatrixLine interface {
WriteString(s string)
}
type terminalMatrix struct {
lines []string
refreshInterval time.Duration
writer io.StringWriter
mx *sync.RWMutex
}
type matrixLineWriter struct {
index int
matrix *terminalMatrix
}
// NewMatrix creates a new matrix that writes to the specified writer and refreshes every refreshInterval.
func NewMatrix(writer io.StringWriter, refreshInterval time.Duration) Matrix {
return &terminalMatrix{
lines: []string{},
refreshInterval: refreshInterval,
writer: writer,
mx: &sync.RWMutex{},
}
}
func (m *terminalMatrix) RefreshInterval() time.Duration {
return m.refreshInterval
}
// Start starts the matrix update process.
// Returns a cancel handle to stop the matrix updates.
func (m *terminalMatrix) Start() context.CancelFunc {
context, cancel := context.WithCancel(context.Background())
waitStart := &sync.WaitGroup{}
waitStart.Add(1)
var drainWaitGroup *sync.WaitGroup
go func() {
timer := time.NewTicker(m.refreshInterval)
drainWaitGroup = &sync.WaitGroup{}
drainWaitGroup.Add(1)
// now that we loaded the drain wait group, we can release the caller
waitStart.Done()
for {
select {
case <-context.Done():
timer.Stop()
m.updateTerminal(false)
drainWaitGroup.Done()
return
case <-timer.C:
m.updateTerminal(true)
}
}
}()
waitStart.Wait()
return func() {
cancel()
// Wait for the final update to complete
drainWaitGroup.Wait()
}
}
func (m *terminalMatrix) updateTerminal(resetCursorPosition bool) {
c := NewCursor(m.writer)
m.mx.Lock()
defer m.mx.Unlock()
if len(m.lines) == 0 {
return
}
for _, line := range m.lines {
m.writer.WriteString(fmt.Sprintf("%s%s\r\n", TermControlEraseLine, line))
}
if resetCursorPosition {
c.Up(len(m.lines))
}
}
// NewLineStringWriter returns a new string writter to interact with a single matrix line
func (m *terminalMatrix) NewLineStringWriter() io.StringWriter {
m.mx.Lock()
defer m.mx.Unlock()
index := len(m.lines)
m.lines = append(m.lines, "")
return &matrixLineWriter{
index: index,
matrix: m,
}
}
// NewLineWriter returns a new writer interface to interact with a single matrix line.
func (m *terminalMatrix) NewLineWriter() io.Writer {
return m.NewLineStringWriter().(*matrixLineWriter)
}
func (l *matrixLineWriter) WriteString(s string) (n int, err error) {
return l.Write([]byte(s))
}
func (l *matrixLineWriter) Write(b []byte) (n int, err error) {
l.matrix.mx.Lock()
defer l.matrix.mx.Unlock()
l.matrix.lines[l.index] = string(b)
return len(b), nil
}