1 /// Formatting and util to work with BASH
2 module sily.bashfmt;
3 
4 import std.conv : to;
5 version (Have_speedy_stdio) import speedy.stdio: write, writef;
6 else import std.stdio : write, writef;
7 
8 // static this() {
9 //     version(windows) {
10 //         import core.stdc.stdlib: exit;
11 //         exit(2);
12 //     }
13 // }
14 
15 // LINK: https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797
16 
17 /// Short alias to formatting enum
18 alias FG = Foreground;
19 /// Ditto
20 alias BG = Background;
21 /// Ditto
22 alias FM = Formatting;
23 /// Ditto
24 alias FR = FormattingReset;
25 
26 /// Contains escape sequences for foreground colors
27 enum Foreground : string {
28     reset = "\033[39m",
29     black = "\033[30m",
30     red = "\033[31m",
31     green = "\033[32m",
32     yellow = "\033[33m",
33     blue = "\033[34m",
34     magenta = "\033[35m",
35     cyan = "\033[36m",
36     ltgray = "\033[37m",
37     dkgray = "\033[90m",
38     ltred = "\033[91m",
39     ltgreen = "\033[92m",
40     ltyellow = "\033[93m",
41     ltblue = "\033[94m",
42     ltmagenta = "\033[95m",
43     ltcyan = "\033[96m",
44     white = "\033[97m",
45 }
46 
47 /// Contains escape sequences for background colors
48 enum Background : string {
49     reset = "\033[49m",
50     black = "\033[40m",
51     red = "\033[41m",
52     green = "\033[42m",
53     yellow = "\033[43m",
54     blue = "\033[44m",
55     magenta = "\033[45m",
56     cyan = "\033[46m",
57     ltgray = "\033[47m",
58     dkgray = "\033[100m",
59     ltred = "\033[101m",
60     ltgreen = "\033[102m",
61     ltyellow = "\033[103m",
62     ltblue = "\033[104m",
63     ltmagenta = "\033[105m",
64     ltcyan = "\033[106m",
65     white = "\033[107m"
66 }
67 
68 /// Contains escape sequences for string formatting (bold, italics)
69 enum Formatting : string {
70     bold = "\033[1m",
71     dim = "\033[2m",
72     italics = "\033[3m",
73     uline = "\033[4m",
74     blink = "\033[5m",
75     inverse = "\033[7m",
76     hidden = "\033[8m",
77     striked = "\033[9m",
78     dline = "\033[21m",
79     cline = "\033[4:3m"
80 }
81 
82 /// Contains escape sequences to reset string formatting
83 enum FormattingReset : string {
84     reset = "\033[0m",
85     fullreset = "\033[m",
86 
87     bold = "\033[21m",
88     dim = "\033[22m",
89     italics = "\033[22m",
90     uline = "\033[24m",
91     blink = "\033[25m",
92     inverse = "\033[27m",
93     hidden = "\033[28m",
94     striked = "\033[29m",
95     dline = "\033[24m",
96     cline = "\033[4:0m"
97 }
98 
99 /* --------------------------------- OUTPUT --------------------------------- */
100 
101 /** 
102 Casts args to string and writes to stdout
103 Intended to be used to print formatting
104 ---
105 fwrite("White text", FG.red, "Red text", FG.reset, BG.red, "Red background", FR.fullreset);
106 ---
107 Params:
108   args = Text or one of formatting strings
109 */
110 void fwrite(A...)(A args) {
111     foreach (arg; args) {
112         write(cast(string) arg);
113     }
114 }
115 
116 /** 
117 Casts args to string and writes to stdout with `\n` at the end
118 Intended to be used to print formatting
119 ---
120 fwriteln("White text", FG.red, "Red text", FG.reset, BG.red, "Red background", FR.fullreset);
121 ---
122 Params:
123   args = Text or one of formatting strings
124 */
125 void fwriteln(A...)(A args) {
126     foreach (arg; args) {
127         write(cast(string) arg);
128     }
129     write("\n");
130 }
131 
132 /* ------------------------------- LINE ERASE ------------------------------- */
133 
134 /** 
135 Erases `num` lines in terminal starting with current.
136 Params:
137   num = Number of lines to erase
138 */
139 void eraseLines(int num) {
140     if (num < 1) return;
141     eraseCurrentLine();
142     --num;
143 
144     while (num) {
145         cursorMoveUpScroll();
146         eraseCurrentLine();
147         --num;
148     }
149 }
150 
151 /// Fully erases current line 
152 void eraseCurrentLine() {
153     write("\033[2K");
154 }
155 
156 /// Erases text from start of current line to cursor 
157 void eraseLineLeft() {
158     write("\033[1K");
159 }
160 
161 /// Erases text from cursor to end of current line
162 void eraseLineRight() {
163     write("\033[K");
164 }
165 
166 /* --------------------------------- CURSOR --------------------------------- */
167 
168 import sily.vector: uvec2;
169 
170 version(Posix) {
171     import sily.terminal: terminalModeSetRaw, terminalModeReset, getch, isTerminalRaw;
172 
173     /// Returns cursor position
174     uvec2 cursorGetPosition() {
175         uvec2 v;
176         char[] buf = new char[](30);
177         int i, pow;
178         char ch;
179         bool wasTerminalRaw = isTerminalRaw();
180         // FIXME: make checks for if terminal is raw already
181         if (!wasTerminalRaw) terminalModeSetRaw();
182         writef("\033[6n");
183 
184         for (i = 0, ch = 0; ch != 'R'; i++) {
185             int r = getch(); ch = cast(char) r;
186             // in case of getting stuck
187             if (r == 17) {
188                 if (!wasTerminalRaw) terminalModeReset(); 
189                 return v;
190             }
191             if (!r) {
192                 // error("Error reading response"); moveCursorTo(0);
193                 if (!wasTerminalRaw) terminalModeReset(); 
194                 return v;
195             }
196             buf[i] = ch;
197             // if (i != 0) { 
198             //     import std.format: format;
199             //     trace("buf[%d]: %c %d".format(i, ch, ch)); moveCursorTo(0);
200             // }
201         }
202         if (i < 2) {
203             if (!wasTerminalRaw) terminalModeReset();
204             // error("Incorrect response size"); moveCursorTo(0);
205             return v;
206         }
207 
208         for (i -= 2, pow = 1; buf[i] != ';'; --i, pow *= 10) {
209             v.x = v.x + (buf[i] - '0') * pow;
210         }
211         for (--i, pow = 1; buf[i] != '['; --i, pow *= 10) {
212             v.y = v.y + (buf[i] - '0') * pow;
213         }
214 
215         if (!wasTerminalRaw) terminalModeReset();
216         return v;
217     }
218 }
219 
220 version(Windows) {
221     import core.sys.windows.windows;
222 
223     uvec2 cursorGetPosition() {
224         HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
225         CONSOLE_SCREEN_BUFFER_INFO cbsi;
226         if (GetConsoleScreenBufferInfo(hConsoleOutput, &cbsi)) {
227             COORD c =  cbsi.dwCursorPosition;
228             return uvec2(c.X, c.Y);
229         } else {
230             // The function failed. Call GetLastError() for details.
231             return uvec2(0);
232         }
233     }
234 }
235 
236 /** 
237 Moves cursor in terminal to `{x, y}` or to `x`. **COORDINATES START FROM 1**
238 Params:
239   x = Column to move to
240   y = Row to move to
241 */
242 void cursorMoveTo(int x, int y) {
243     writef("\033[%d;%df", y, x);
244 }
245 /// Ditto
246 void cursorMoveTo(uvec2 pos) { 
247     writef("\033[%d;%df", pos.y, pos.x);
248 }
249 /// Ditto
250 void cursorMoveTo(int x) {
251     writef("\033[%dG", x);
252 }
253 
254 /// Moves cursor in terminal to `{1, 1}`
255 void cursorMoveHome() {
256     writef("\033[H");
257 }
258 
259 // TODO: add cursorMoveRel()
260 
261 /** 
262 Moves cursor in terminal up by `lineAmount`
263 Params: 
264   lineAmount = int
265 */
266 void cursorMoveUp(int lineAmount = 1) {
267     writef("\033[%dA", lineAmount);
268 }
269 
270 /// Moves cursor in terminal up by 1 and scrolls if needed
271 void cursorMoveUpScroll() {
272     writef("\033M");
273 }
274 
275 /** 
276 Moves cursor in terminal up by`lineAmount` and sets cursor X to 1
277 Params: 
278   lineAmount = int
279 */
280 void cursorMoveUpStart(int lineAmount = 1) {
281     writef("\033[%dF", lineAmount);
282 }
283 
284 /** 
285 Moves cursor in terminal down by `lineAmount`
286 Params: 
287   lineAmount = int
288  */
289 void cursorMoveDown(int lineAmount = 1) {
290     writef("\033[%dB", lineAmount);
291 }
292 
293 /** 
294 Moves cursor in terminal down by`lineAmount` and sets cursor X to 1
295 Params: 
296   lineAmount = int
297 */
298 void cursorMoveDownStart(int lineAmount = 1) {
299     writef("\033[%dE", lineAmount);
300 }
301 
302 /** 
303 Moves cursor in terminal right by `columnAmount`
304 Params: 
305   columnAmount = int
306 */
307 void cursorMoveRight(int columnAmount = 1) {
308     writef("\033[%dC", columnAmount);
309 }
310 
311 /** 
312 Moves cursor in terminal left by `columnAmount`
313 Params: 
314   columnAmount = int
315 */
316 void cursorMoveLeft(int columnAmount = 1) {
317     writef("\033[%dD", columnAmount);
318 }
319 
320 /// Saves/Restores cursor position to be restored later (DEC)
321 void cursorSavePosition() {
322     write("\0337");
323 }
324 
325 /// Ditto
326 void cursorRestorePosition() {
327     write("\0338");
328 }
329 
330 /// Saves/Restores cursor position to be restored later (SCO). **PREFER DEC (`saveCursorPosition`) VERSION INSTEAD.**
331 void cursorSavePositionSCO() {
332     write("\033[s");
333 }
334 
335 /// Ditto
336 void cursorRestorePositionSCO() {
337     write("\033[u");
338 }
339 
340 /// Hides cursor. Does not reset position
341 void cursorHide() {
342     write("\033[?25l");
343 }
344 
345 /// Shows cursor. Does not reset position
346 void cursorShow() {
347     write("\033[?25h");
348 }
349 
350 /* --------------------------------- SCREEN --------------------------------- */
351 
352 /// Clears terminal screen and resets cursor position
353 void screenClear() {
354     write("\033[2J");
355     cursorMoveHome();
356 }
357 
358 /// Clears terminal screen
359 void screenClearOnly() {
360     write("\033[2J");
361 }
362 
363 /// Enabled/Disables Alt Buffer. **PREFER `screenEnableAltBuffer` OR `screenDisableAltBuffer` INSTEAD.**
364 void screenSave() {
365     write("\033[?47h");
366 }
367 /// Ditto
368 void screenRestore() {
369     write("\033[?47l");
370 }
371 
372 /// Enabled/Disables alternative screen buffer. 
373 void screenEnableAltBuffer() {
374     write("\033[?1049h");
375 }
376 /// Ditto
377 void screenDisableAltBuffer() {
378     write("\033[?1049l");
379 }
380 
381 /// Hard resets terminal. Not recommended to use
382 void screenHardReset() {
383     write("\033c");
384 }
385 
386 /// Sets terminal title that's going to last until program termination
387 void setTitle(string title) {
388     write("\033]0;" ~ title ~ "\007");
389 }
390 
391 /// Rings audio bell
392 void bell() {
393     write("\a");
394 }
395 
396 /** 
397 Intended to be used in SIGINT callback
398 Resets all formatting and shows cursor
399 */
400 void cleanTerminalState() nothrow @nogc @system {
401     import core.stdc.stdio: printf;
402     printf("\033[?1049l\033[?25h\033[m\033[?1000;1006;1015l");
403 }