-
Notifications
You must be signed in to change notification settings - Fork 0
/
termloader.go
202 lines (179 loc) · 4.53 KB
/
termloader.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
package termloader
import (
"fmt"
"io"
"os"
"os/exec"
"regexp"
"runtime"
"strings"
"sync"
"time"
"golang.org/x/crypto/ssh/terminal"
)
// constants for ANSI escape sequences.
const (
escape = "\x1b"
reset = 0
)
// map for storing the clear function.
var clear map[string]func()
// valid colors.
var validColors = map[int]bool{
Red: true,
Green: true,
Yellow: true,
Blue: true,
Magenta: true,
Cyan: true,
Gray: true,
}
// Loader config.
type Loader struct {
Image *Image // Loading image
Color int // Color of the loader
Delay time.Duration // Animation speed of the loader
Text string // Text to be displayed above the loader
Writer io.Writer // Stdout
active bool // current state of the loader
charset []string // character set for the loader
mutex sync.Mutex // mutex
stop chan bool // channel for stopping the loader
hasImage bool // loading image provided
}
// this ugly hack needs to be removed. Need to find a better way to clear the stdout.
func init() {
clear = make(map[string]func())
clear["linux"] = func() {
cmd := exec.Command("clear")
cmd.Stdout = os.Stdout
_ = cmd.Run()
}
clear["darwin"] = clear["linux"]
}
// New returns a pointer to the Loader interface with provided options. Default loader color will be white.
func New(charsetConfig CharsetConfig) *Loader {
charset := charsetConfig.Charset
delay := charsetConfig.Delay
return &Loader{
Image: &Image{
Filters: &Filters{},
Writer: os.Stdout,
},
Delay: delay,
Color: None,
Writer: os.Stdout,
active: false,
charset: charset,
mutex: sync.Mutex{},
stop: make(chan bool, 1),
}
}
// clrScr will clear the stdout.
func clrScr() {
if value, ok := clear[runtime.GOOS]; ok {
value()
} else {
panic("your platform is unsupported! couldn't clear terminal screen :(")
}
}
// hCenter is a helper function which will help in rendering the loader horizontally centered on the terminal screen.
func hCenter(s string, width int) string {
var spaces string
regex := regexp.MustCompile("\x1b\\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]")
strLen := strings.Count(regex.ReplaceAllString(s, ""), "") - 1
halfStrLen := strLen / 2
center := (width / 2) - halfStrLen
for i := 0; i < center; i++ {
spaces += " "
}
return spaces
}
// vCenter is a helper function which will help in rendering the loader vertically centered on the terminal screen.
func vCenter(lineCount int, height int) string {
var lines string
center := (height / 2) - (lineCount / 2)
for i := 0; i < center; i++ {
lines += "\n"
}
return lines
}
// validColor will return true if the provided color is valid else returns false.
func validColor(color int) bool {
valid := false
if validColors[color] {
valid = true
}
return valid
}
// ColorString will wrap the provided string with ANSI escape sequences for color and return the colored string.
func ColorString(str string, color int) string {
if color == None || !validColor(color) {
return str
}
prefix := fmt.Sprintf("%s[%dm", escape, color)
suffix := fmt.Sprintf("%s[%dm", escape, reset)
return prefix + str + suffix
}
// Starts the loader.
func (l *Loader) Start() {
if l.active {
return
}
l.active = true
fd := int(os.Stdin.Fd())
if ok := terminal.IsTerminal(fd); !ok {
return
}
rendered := false
renderedImage := false
go func() {
lineCount := 1
width, height, _ := terminal.GetSize(fd)
if l.Text != "" {
lineCount += 1
}
if l.Image.Path != "" {
l.hasImage = true
}
if !l.hasImage {
_, _ = fmt.Fprint(l.Writer, vCenter(lineCount, height))
}
for {
for i := 0; i < len(l.charset); i++ {
select {
case <-l.stop:
return
default:
l.mutex.Lock()
if l.hasImage && !renderedImage {
_, _ = fmt.Fprintf(l.Writer, "%s[?25l", escape) // disable cursor
l.Image.Render(width, height, lineCount)
renderedImage = true
}
if l.Text != "" && !rendered {
textCenter := hCenter(l.Text, width)
_, _ = fmt.Fprintf(l.Writer, "%s[?25l", escape) // disable cursor
_, _ = fmt.Fprintln(l.Writer, textCenter+l.Text)
rendered = true
}
loaderCenter := hCenter(l.charset[i], width)
_, _ = fmt.Fprintf(l.Writer, "%s\r", loaderCenter+ColorString(l.charset[i], l.Color))
l.mutex.Unlock()
time.Sleep(l.Delay)
}
}
}
}()
}
// Stops the loader.
func (l *Loader) Stop() {
l.mutex.Lock()
defer l.mutex.Unlock()
if l.active {
l.active = false
fmt.Printf("%s[?25h", escape) // enable cursor
l.stop <- true
clrScr()
}
}