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
|
module term
import os
import strings.textscanner
const (
default_columns_size = 80
default_rows_size = 25
)
// Coord - used by term.get_cursor_position and term.set_cursor_position
pub struct Coord {
pub mut:
x int
y int
}
// can_show_color_on_stdout returns true if colors are allowed in stdout;
// returns false otherwise.
pub fn can_show_color_on_stdout() bool {
return supports_escape_sequences(1)
}
// can_show_color_on_stderr returns true if colors are allowed in stderr;
// returns false otherwise.
pub fn can_show_color_on_stderr() bool {
return supports_escape_sequences(2)
}
// failed returns a bold white on red version of the string `s`
// If colors are not allowed, returns the string `s`
pub fn failed(s string) string {
if can_show_color_on_stdout() {
return bg_red(bold(white(s)))
}
return s
}
// ok_message returns a colored string with green color.
// If colors are not allowed, returns a given string.
pub fn ok_message(s string) string {
if can_show_color_on_stdout() {
return green(' $s ')
}
return s
}
// fail_message returns a colored string with red color.
// If colors are not allowed, returns a given string.
pub fn fail_message(s string) string {
return failed(' $s ')
}
// warn_message returns a colored string with yellow color.
// If colors are not allowed, returns a given string.
pub fn warn_message(s string) string {
if can_show_color_on_stdout() {
return bright_yellow(' $s ')
}
return s
}
// colorize returns a colored string by running the specified `cfn` over
// the message `s`, only if colored output is supported by the terminal.
// Example: term.colorize(term.yellow, 'the message')
pub fn colorize(cfn fn (string) string, s string) string {
if can_show_color_on_stdout() {
return cfn(s)
}
return s
}
// strip_ansi removes any ANSI sequences in the `text`
pub fn strip_ansi(text string) string {
// This is a port of https://github.com/kilobyte/colorized-logs/blob/master/ansi2txt.c
// \e, [, 1, m, a, b, c, \e, [, 2, 2, m => abc
mut input := textscanner.new(text)
mut output := []byte{cap: text.len}
mut ch := 0
for ch != -1 {
ch = input.next()
if ch == 27 {
ch = input.next()
if ch == `[` {
for {
ch = input.next()
if ch in [`;`, `?`] || (ch >= `0` && ch <= `9`) {
continue
}
break
}
} else if ch == `]` {
ch = input.next()
if ch >= `0` && ch <= `9` {
for {
ch = input.next()
if ch == -1 || ch == 7 {
break
}
if ch == 27 {
ch = input.next()
break
}
}
}
} else if ch == `%` {
ch = input.next()
}
} else if ch != -1 {
output << byte(ch)
}
}
return output.bytestr()
}
// h_divider returns a horizontal divider line with a dynamic width,
// that depends on the current terminal settings.
// If an empty string is passed in, print enough spaces to make a new line
pub fn h_divider(divider string) string {
cols, _ := get_terminal_size()
mut result := ''
if divider.len > 0 {
result = divider.repeat(1 + (cols / divider.len))
} else {
result = ' '.repeat(1 + cols)
}
return result[0..cols]
}
// header_left returns a horizontal divider line with a title text on the left.
// e.g: term.header_left('TITLE', '=')
// ==== TITLE =========================
pub fn header_left(text string, divider string) string {
plain_text := strip_ansi(text)
xcols, _ := get_terminal_size()
cols := imax(1, xcols)
relement := if divider.len > 0 { divider } else { ' ' }
hstart := relement.repeat(4)[0..4]
remaining_cols := (cols - (hstart.len + 1 + plain_text.len + 1))
hend := relement.repeat((remaining_cols + 1) / relement.len)[0..remaining_cols]
return '$hstart $text $hend'
}
// header returns a horizontal divider line with a centered text in the middle.
// e.g: term.header('TEXT', '=')
// =============== TEXT ===============
pub fn header(text string, divider string) string {
if text.len == 0 {
return h_divider(divider)
}
xcols, _ := get_terminal_size()
cols := imax(1, xcols)
tlimit := imax(1, if cols > text.len + 2 + 2 * divider.len {
text.len
} else {
cols - 3 - 2 * divider.len
})
tlimit_alligned := if (tlimit % 2) != (cols % 2) { tlimit + 1 } else { tlimit }
tstart := imax(0, (cols - tlimit_alligned) / 2)
mut ln := ''
if divider.len > 0 {
ln = divider.repeat(1 + cols / divider.len)[0..cols]
} else {
ln = ' '.repeat(1 + cols)
}
if ln.len == 1 {
return ln + ' ' + text[0..tlimit] + ' ' + ln
}
return ln[0..tstart] + ' ' + text[0..tlimit] + ' ' + ln[tstart + tlimit + 2..cols]
}
fn imax(x int, y int) int {
return if x > y { x } else { y }
}
fn supports_escape_sequences(fd int) bool {
vcolors_override := os.getenv('VCOLORS')
if vcolors_override == 'always' {
return true
}
if vcolors_override == 'never' {
return false
}
env_term := os.getenv('TERM')
if env_term == 'dumb' {
return false
}
$if windows {
env_conemu := os.getenv('ConEmuANSI')
if env_conemu == 'ON' {
return true
}
// 4 is enable_virtual_terminal_processing
return (os.is_atty(fd) & 0x0004) > 0
} $else {
return os.is_atty(fd) > 0
}
}
|