1 /**
2  * Provides simple API for coloring and formatting text in terminal.
3  * On Windows OS it uses WinAPI functions, on POSIX systems it uses mainly ANSI codes.
4  * 
5  * $(B Important notes):
6  * $(UL
7  *  $(LI Font styles have no effect on windows platform.)
8  *  $(LI Light background colors are not supported. Non-light equivalents are used on Posix platforms.)
9  * )
10  * 
11  * License: 
12  *  <a href="http://www.boost.org/LICENSE_1_0.txt">Boost License</a>
13  * Authors:
14  *  <a href="http://github.com/robik">Robert 'Robik' Pasiński</a>
15  */
16 module consoled;
17 
18 import std.typecons, std.algorithm;
19 import std.array : replicate;
20 
21 
22 /// Console output stream
23 enum ConsoleOutputStream
24 {
25     /// Standard output
26     stdout,
27     
28     /// Standard error output
29     stderr
30 }
31 
32 
33 /**
34  * Console font output style
35  * 
36  * Does nothing on windows.
37  */
38 enum FontStyle
39 {
40     none          = 0, /// Default
41     underline     = 1, /// Underline
42     strikethrough = 2  /// Characters legible, but marked for deletion. Not widely supported.
43 }
44 
45 alias void delegate(CloseEvent) @system CloseHandler;
46 
47 /**
48  * Represents close event.
49  */
50 struct CloseEvent
51 {
52     /// Close type
53     CloseType type;
54     
55     /// Is close event blockable?
56     bool      isBlockable;
57 }
58 
59 /**
60  * Close type.
61  */
62 enum CloseType
63 {
64     Interrupt, // User pressed Ctrl+C key combination.
65     Stop,      // User pressed Ctrl+Break key combination. On posix it is Ctrl+Z.
66     Quit,      // Posix only. User pressed Ctrl+\ key combination.
67     Other      // Other close reasons. Probably unblockable.
68 }
69 
70 /**
71  * Console input mode
72  */
73 struct ConsoleInputMode
74 {
75     /// Echo printed characters?
76     bool echo = true;
77     
78     /// Enable line buffering?
79     bool line = true;
80     
81     /**
82      * Creates new ConsoleInputMode instance
83      * 
84      * Params:
85      *  echo = Echo printed characters?
86      *  line = Use Line buffering?
87      */
88     this(bool echo, bool line)
89     {
90         this.echo = echo;
91         this.line = line;
92     }
93     
94     /**
95      * Console input mode with no feature enabled
96      */
97     static ConsoleInputMode None = ConsoleInputMode(false, false);
98 }
99 
100 /**
101  * Represents point in console.
102  */
103 alias Tuple!(int, "x", int, "y") ConsolePoint;
104 
105 /// Special keys
106 enum SpecialKey
107 {
108     home = 512, /// Home key
109     pageUp,     /// Page Up key
110     pageDown,   /// Page Down key
111     end,        /// End key
112     delete_,    /// Delete key
113     insert,     /// Insert key
114     up,         /// Arrow up key
115     down,       /// Arrow down key
116     left,       /// Arrow left key
117     right,      /// Arrow right key
118     
119     escape = 27,/// Escape key
120     tab = 9,    /// Tab key
121 }
122 
123 ////////////////////////////////////////////////////////////////////////
124 version(Windows)
125 { 
126     private enum BG_MASK = 0xf0;
127     private enum FG_MASK = 0x0f;
128     
129     import core.sys.windows.windows, std.stdio, std.string;
130 
131     ///
132     enum Color : ushort
133     {        
134         black        = 0, /// The black color.
135         blue         = 1, /// The blue color.
136         green        = 2, /// The green color.
137         cyan         = 3, /// The cyan color. (blue-green)
138         red          = 4, /// The red color.
139         magenta      = 5, /// The magenta color. (dark pink like)
140         yellow       = 6, /// The yellow color.
141         lightGray    = 7, /// The light gray color. (silver)
142         
143         gray         = 8,  /// The gray color.
144         lightBlue    = 9,  /// The light blue color.
145         lightGreen   = 10, /// The light green color.
146         lightCyan    = 11, /// The light cyan color. (light blue-green)
147         lightRed     = 12, /// The light red color.
148         lightMagenta = 13, /// The light magenta color. (pink)
149         lightYellow  = 14, /// The light yellow color.
150         white        = 15, /// The white color.
151         
152         bright       = 8,  /// Bright flag. Use with dark colors to make them light equivalents.
153         initial      = 256 /// Default color.
154     }
155     
156     
157     private __gshared
158     {
159         CONSOLE_SCREEN_BUFFER_INFO info;
160         HANDLE hOutput = null, hInput = null;
161         
162         Color fg, bg, defFg, defBg;
163         CloseHandler[] closeHandlers;
164     }
165     
166     
167     shared static this()
168     {
169         loadDefaultColors(ConsoleOutputStream.stdout);
170         SetConsoleCtrlHandler(cast(PHANDLER_ROUTINE)&defaultCloseHandler, true);
171     }
172     
173     private void loadDefaultColors(ConsoleOutputStream cos)
174     {
175         uint handle;
176         
177         if(cos == ConsoleOutputStream.stdout) {
178             handle = STD_OUTPUT_HANDLE;
179         } else if(cos == ConsoleOutputStream.stderr) {
180             handle = STD_ERROR_HANDLE;
181         } else {
182             assert(0, "Invalid console output stream specified");
183         }
184         
185         
186         hOutput  = GetStdHandle(handle);
187         hInput   = GetStdHandle(STD_INPUT_HANDLE);
188         
189         // Get current colors
190         GetConsoleScreenBufferInfo( hOutput, &info );
191         
192         // Background are first 4 bits
193         defBg = cast(Color)((info.wAttributes & (BG_MASK)) >> 4);
194                 
195         // Rest are foreground
196         defFg = cast(Color) (info.wAttributes & (FG_MASK));
197         
198         fg = Color.initial;
199         bg = Color.initial;
200     }
201     
202     private ushort buildColor(Color fg, Color bg)
203     {
204         if(fg == Color.initial) {
205             fg = defFg;
206         }
207         
208         if(bg == Color.initial) {
209             bg = defBg;
210         }
211             
212         return cast(ushort)(fg | bg << 4);
213     }
214     
215     private void updateColor()
216     {
217         stdout.flush();
218         SetConsoleTextAttribute(hOutput, buildColor(fg, bg));
219     }
220     
221     
222     /**
223      * Current console font color
224      * 
225      * Returns:
226      *  Current foreground color set
227      */
228     Color foreground() @property 
229     {
230         return fg;
231     }
232     
233     /**
234      * Current console background color
235      * 
236      * Returns:
237      *  Current background color set
238      */
239     Color background() @property
240     {
241         return bg;
242     }
243     
244     /**
245      * Sets console foreground color
246      *
247      * Flushes stdout.
248      *
249      * Params:
250      *  color = Foreground color to set
251      */
252     void foreground(Color color) @property 
253     {
254         fg = color;
255         updateColor();
256     }
257     
258     
259     /**
260      * Sets console background color
261      *
262      * Flushes stdout.
263      *
264      * Params:
265      *  color = Background color to set
266      */
267     void background(Color color) @property 
268     {
269         bg = color;
270         updateColor();
271     }
272     
273     /**
274      * Sets new console output stream
275      * 
276      * This function sets default colors 
277      * that are used when function is called.
278      * 
279      * Params:
280      *  cos = New console output stream
281      */
282     void outputStream(ConsoleOutputStream cos) @property
283     {
284         loadDefaultColors(cos);
285     }
286     
287     /**
288      * Sets console font style
289      * 
290      * Does nothing on windows.
291      * 
292      * Params:
293      *  fs = Font style to set
294      */
295     void fontStyle(FontStyle fs) @property {}
296     
297     /**
298      * Returns console font style
299      * 
300      * Returns:
301      *  Font style, always none on windows.
302      */
303     FontStyle fontStyle() @property
304     {
305         return FontStyle.none;
306     }
307     
308     
309     /**
310      * Console size
311      * 
312      * Returns:
313      *  Tuple containing console rows and cols.
314      */
315     ConsolePoint size() @property 
316     {
317         GetConsoleScreenBufferInfo( hOutput, &info );
318         
319         int cols, rows;
320         
321         cols = (info.srWindow.Right  - info.srWindow.Left + 1);
322         rows = (info.srWindow.Bottom - info.srWindow.Top  + 1);
323 
324         return ConsolePoint(cols, rows);
325     }
326     
327     /**
328      * Sets console position
329      * 
330      * Params:
331      *  x = X coordinate of cursor postion
332      *  y = Y coordinate of cursor position
333      */
334     void setCursorPos(int x, int y)
335     {
336         COORD coord = {
337             cast(short)min(width, max(0, x)), 
338             cast(short)max(0, y)
339         };
340         stdout.flush();
341         SetConsoleCursorPosition(hOutput, coord);
342     }
343     
344     /**
345      * Gets cursor position
346      * 
347      * Returns:
348      *  Cursor position
349      */
350     ConsolePoint cursorPos() @property
351     {
352         GetConsoleScreenBufferInfo( hOutput, &info );
353         return ConsolePoint(
354             info.dwCursorPosition.X, 
355             min(info.dwCursorPosition.Y, height) // To keep same behaviour with posix
356         );
357     }
358     
359     
360     
361     /**
362      * Sets console title
363      * 
364      * Params:
365      *  title = Title to set
366      */
367     void title(string title) @property
368     {
369         SetConsoleTitleA(toStringz(title));
370     }
371     
372     
373     /**
374      * Adds handler for console close event.
375      * 
376      * Params:
377      *  closeHandler = New close handler
378      */
379     void addCloseHandler(CloseHandler closeHandler)
380     {
381         closeHandlers ~= closeHandler;
382     }
383     
384     /**
385      * Moves cursor by specified offset
386      * 
387      * Params:
388      *  x = X offset
389      *  y = Y offset
390      */
391     private void moveCursor(int x, int y)
392     {
393         stdout.flush();
394         auto pos = cursorPos();
395         setCursorPos(max(pos.x + x, 0), max(0, pos.y + y));
396     }
397 
398     /**
399      * Moves cursor up by n rows
400      * 
401      * Params:
402      *  n = Number of rows to move
403      */
404     void moveCursorUp(int n = 1)
405     {
406         moveCursor(0, -n);
407     }
408 
409     /**
410      * Moves cursor down by n rows
411      * 
412      * Params:
413      *  n = Number of rows to move
414      */
415     void moveCursorDown(int n = 1)
416     {
417         moveCursor(0, n);
418     }
419 
420     /**
421      * Moves cursor left by n columns
422      * 
423      * Params:
424      *  n = Number of columns to move
425      */
426     void moveCursorLeft(int n = 1)
427     {
428         moveCursor(-n, 0);
429     }
430 
431     /**
432      * Moves cursor right by n columns
433      * 
434      * Params:
435      *  n = Number of columns to move
436      */
437     void moveCursorRight(int n = 1)
438     {
439         moveCursor(n, 0);
440     }
441     
442     /**
443      * Gets console mode
444      * 
445      * Returns:
446      *  Current console mode
447      */
448     ConsoleInputMode mode() @property
449     {
450         ConsoleInputMode cim;
451         DWORD m;
452         GetConsoleMode(hInput, &m);
453         
454         cim.echo  = !!(m & ENABLE_ECHO_INPUT);
455         cim.line  = !!(m & ENABLE_LINE_INPUT);
456         
457         return cim;
458     }
459     
460     /**
461      * Sets console mode
462      * 
463      * Params:
464      *  New console mode
465      */
466     void mode(ConsoleInputMode cim) @property
467     {
468         DWORD m;
469         
470         (cim.echo) ? (m |= ENABLE_ECHO_INPUT) : (m &= ~ENABLE_ECHO_INPUT);
471         (cim.line) ? (m |= ENABLE_LINE_INPUT) : (m &= ~ENABLE_LINE_INPUT);
472         
473         SetConsoleMode(hInput, m);
474     }
475     
476     /**
477      * Reads character without line buffering
478      * 
479      * Params:
480      *  echo = Print typed characters
481      */
482     int getch(bool echo = false)
483     {
484         INPUT_RECORD ir;
485         DWORD count;
486         auto m = mode;
487         
488         mode = ConsoleInputMode.None;
489         
490         do {
491             ReadConsoleInputA(hInput, &ir, 1, &count);
492         } while((ir.EventType != KEY_EVENT || !ir.KeyEvent.bKeyDown) && kbhit());
493 	// the extra kbhit is to filter out events AFTER the keydown
494 	// to ensure next time we call this, we're back on a fresh keydown
495 	// event. Without that, the key up event will trigger kbhit, then
496 	// you call getch(), and it blocks because it read keyup then looped
497 	// and is waiting for another keydown.
498         
499         mode = m;
500         
501         return ir.KeyEvent.wVirtualKeyCode;
502     }
503     
504     /**
505      * Checks if any key is pressed.
506      * 
507      * Shift, Ctrl and Alt keys are not detected.
508      * 
509      * Returns:
510      *  True if any key is pressed, false otherwise.
511      */
512     bool kbhit()
513     {
514         return WaitForSingleObject(hInput, 0) == WAIT_OBJECT_0;
515     }
516     
517     /**
518      * Sets cursor visibility
519      * 
520      * Params:
521      *  visible = Cursor visibility
522      */
523     void cursorVisible(bool visible) @property
524     {
525         CONSOLE_CURSOR_INFO cci;
526         GetConsoleCursorInfo(hOutput, &cci);
527         cci.bVisible = visible;
528         SetConsoleCursorInfo(hOutput, &cci);
529     }
530     
531     private CloseEvent idToCloseEvent(ulong i)
532     {
533         CloseEvent ce;
534         
535         switch(i)
536         {
537             case 0:
538                 ce.type = CloseType.Interrupt;
539             break;
540                 
541             case 1:
542                 ce.type = CloseType.Stop;
543             break;
544                 
545             default:
546                 ce.type = CloseType.Other;
547         }
548         
549         ce.isBlockable = (ce.type != CloseType.Other);
550         
551         return ce;
552     }
553     
554     private bool defaultCloseHandler(ulong reason)
555     {
556         foreach(closeHandler; closeHandlers)
557         {
558             closeHandler(idToCloseEvent(reason));
559         }
560         
561         return true;
562     }
563 }
564 ////////////////////////////////////////////////////////////////////////
565 else version(Posix)
566 {
567     import std.stdio, 
568             std.conv,
569             std.string,
570             core.sys.posix.unistd,
571             core.sys.posix.sys.ioctl,
572             core.sys.posix.termios,
573             core.sys.posix.fcntl,
574             core.sys.posix.sys.time;
575     
576     enum SIGINT  = 2;
577     enum SIGTSTP = 20;
578     enum SIGQUIT = 3;
579     extern(C) void signal(int, void function(int) @system);
580     
581     enum
582     {
583         UNDERLINE_ENABLE  = 4,
584         UNDERLINE_DISABLE = 24,
585             
586         STRIKE_ENABLE     = 9,
587         STRIKE_DISABLE    = 29
588     }
589     
590     ///
591     enum Color : ushort
592     {        
593         black        = 30, /// The black color.
594         red          = 31, /// The red color.
595         green        = 32, /// The green color.
596         yellow       = 33, /// The yellow color.
597         blue         = 34, /// The blue color.
598         magenta      = 35, /// The magenta color. (dark pink like)
599         cyan         = 36, /// The cyan color. (blue-green)
600         lightGray    = 37, /// The light gray color. (silver)
601         
602         gray         = 94,  /// The gray color.
603         lightRed     = 95,  /// The light red color.
604         lightGreen   = 96,  /// The light green color.
605         lightYellow  = 97,  /// The light yellow color.
606         lightBlue    = 98,  /// The light red color.
607         lightMagenta = 99,  /// The light magenta color. (pink)
608         lightCyan    = 100, /// The light cyan color.(light blue-green)
609         white        = 101, /// The white color.
610         
611         bright       = 64,  /// Bright flag. Use with dark colors to make them light equivalents.
612         initial      = 256  /// Default color
613     }
614     
615     
616     private __gshared
617     {   
618         Color fg = Color.initial;
619         Color bg = Color.initial;
620         File stream;
621         int stdinFd;
622         FontStyle currentFontStyle;
623         
624         CloseHandler[] closeHandlers;
625         SpecialKey[string] specialKeys;
626     }
627     
628     shared static this()
629     {
630         stream = stdout;
631         signal(SIGINT,  &defaultCloseHandler);
632         signal(SIGTSTP, &defaultCloseHandler);
633         signal(SIGQUIT, &defaultCloseHandler);
634         stdinFd = fileno(stdin.getFP);
635         
636         specialKeys = [
637             "[A" : SpecialKey.up,
638             "[B" : SpecialKey.down,
639             "[C" : SpecialKey.right,
640             "[D" : SpecialKey.left,
641             
642             "OH" : SpecialKey.home,
643             "[5~": SpecialKey.pageUp,
644             "[6~": SpecialKey.pageDown,
645             "OF" : SpecialKey.end,
646             "[3~": SpecialKey.delete_,
647             "[2~": SpecialKey.insert,
648             
649             "\033":SpecialKey.escape
650         ];
651     }
652     
653     
654     private bool isRedirected()
655     {
656         return isatty( fileno(stream.getFP) ) != 1;
657     }
658     
659     private void printAnsi()
660     {
661         stream.writef("\033[%d;%d;%d;%d;%dm",
662             fg &  Color.bright ? 1 : 0,            
663             fg & ~Color.bright,
664             (bg & ~Color.bright) + 10, // Background colors are normal + 10
665             
666             currentFontStyle & FontStyle.underline     ? UNDERLINE_ENABLE : UNDERLINE_DISABLE,
667             currentFontStyle & FontStyle.strikethrough ? STRIKE_ENABLE    : STRIKE_DISABLE
668         );        
669     }
670     
671     /**
672      * Sets console foreground color
673      *
674      * Params:
675      *  color = Foreground color to set
676      */
677     void foreground(Color color) @property
678     {
679         if(isRedirected()) {
680             return;
681         }
682         
683         fg = color;        
684         printAnsi();
685     }
686     
687     /**
688      * Sets console background color
689      *
690      * Params:
691      *  color = Background color to set
692      */
693     void background(Color color) @property
694     {
695         if(isRedirected()) {
696             return;
697         }
698         
699         bg = color;
700         printAnsi();
701     }   
702     
703     /**
704      * Current console background color
705      * 
706      * Returns:
707      *  Current foreground color set
708      */
709     Color foreground() @property
710     {
711         return fg;
712     }
713     
714     /**
715      * Current console font color
716      * 
717      * Returns:
718      *  Current background color set
719      */
720     Color background() @property
721     {
722         return bg;
723     }
724     
725     /**
726      * Sets new console output stream
727      * 
728      * Params:
729      *  cos = New console output stream
730      */
731     void outputStream(ConsoleOutputStream cos) @property
732     {
733         if(cos == ConsoleOutputStream.stdout) {
734             stream = stdout;
735         } else if(cos == ConsoleOutputStream.stderr) {
736             stream = stderr;
737         } else {
738             assert(0, "Invalid consone output stream specified");
739         }
740     }
741     
742     
743     /**
744      * Sets console font style
745      * 
746      * Params:
747      *  fs = Font style to set
748      */
749     void fontStyle(FontStyle fs) @property
750     {
751         currentFontStyle = fs;
752         printAnsi();
753     }
754     
755     /**
756      * Console size
757      * 
758      * Returns:
759      *  Tuple containing console rows and cols.
760      */
761     ConsolePoint size() @property
762     {
763         winsize w;
764         ioctl(STDOUT_FILENO, TIOCGWINSZ, &w);
765 
766         return ConsolePoint(cast(int)w.ws_col, cast(int)w.ws_row);
767     }
768     
769     /**
770      * Sets console position
771      * 
772      * Params:
773      *  x = X coordinate of cursor postion
774      *  y = Y coordinate of cursor position
775      */
776     void setCursorPos(int x, int y)
777     {
778         stdout.flush();
779         writef("\033[%d;%df", y + 1, x + 1);
780     }
781     
782     /**
783      * Gets cursor position
784      * 
785      * Returns:
786      *  Cursor position
787      */
788     ConsolePoint cursorPos() @property
789     {
790         termios told, tnew;
791         char[] buf;
792         
793         tcgetattr(0, &told);
794         tnew = told;
795         tnew.c_lflag &= ~ECHO & ~ICANON;
796         tcsetattr(0, TCSANOW, &tnew);
797         
798         write("\033[6n");
799         stdout.flush();
800         foreach(i; 0..8)
801         {
802             char c;
803             c = cast(char)getch();
804             buf ~= c;
805             if(c == 'R')
806                 break;
807         }
808         tcsetattr(0, TCSANOW, &told);
809         
810         buf = buf[2..$-1];
811         auto tmp = buf.split(";");
812         
813         return ConsolePoint(to!int(tmp[1]) - 1, to!int(tmp[0]) - 1);
814     }
815     
816     /**
817      * Sets console title
818      * 
819      * Params:
820      *  title = Title to set
821      */
822     void title(string title) @property
823     {
824         stdout.flush();
825         writef("\033]0;%s\007", title); // TODO: Check if supported
826     }
827     
828     /**
829      * Adds handler for console close event.
830      * 
831      * Params:
832      *  closeHandler = New close handler
833      */
834     void addCloseHandler(CloseHandler closeHandler)
835     {
836         closeHandlers ~= closeHandler;
837     }
838     
839     /**
840      * Moves cursor up by n rows
841      * 
842      * Params:
843      *  n = Number of rows to move
844      */
845     void moveCursorUp(int n = 1)
846     {
847         writef("\033[%dA", n);
848     }
849 
850     /**
851      * Moves cursor down by n rows
852      * 
853      * Params:
854      *  n = Number of rows to move
855      */
856     void moveCursorDown(int n = 1)
857     {
858         writef("\033[%dB", n);
859     }
860 
861     /**
862      * Moves cursor left by n columns
863      * 
864      * Params:
865      *  n = Number of columns to move
866      */
867     void moveCursorLeft(int n = 1)
868     {
869         writef("\033[%dD", n);
870     }
871 
872     /**
873      * Moves cursor right by n columns
874      * 
875      * Params:
876      *  n = Number of columns to move
877      */
878     void moveCursorRight(int n = 1)
879     {
880         writef("\033[%dC", n);
881     }
882     
883     /**
884      * Gets console mode
885      * 
886      * Returns:
887      *  Current console mode
888      */
889     ConsoleInputMode mode() @property
890     {
891         ConsoleInputMode cim;
892         termios tio;
893 	ubyte[100] hack;
894         
895         tcgetattr(stdinFd, &tio);
896         cim.echo = !!(tio.c_lflag & ECHO);
897         cim.line = !!(tio.c_lflag & ICANON);
898         
899         return cim;
900     }
901     
902     /**
903      * Sets console mode
904      * 
905      * Params:
906      *  New console mode
907      */
908     void mode(ConsoleInputMode cim) @property
909     {
910         termios tio;
911 	ubyte[100] hack;
912         
913         tcgetattr(stdinFd, &tio);
914         
915         (cim.echo) ? (tio.c_lflag |= ECHO) : (tio.c_lflag &= ~ECHO);
916         (cim.line) ? (tio.c_lflag |= ICANON) : (tio.c_lflag &= ~ICANON);
917         tcsetattr(stdinFd, TCSANOW, &tio);
918     }
919     
920     /**
921      * Reads character without line buffering
922      * 
923      * Params:
924      *  echo = Print typed characters
925      */
926     int getch(bool echo = false)
927     {
928         int c;
929         string buf;
930         ConsoleInputMode m;
931         
932         m = mode;
933         mode = ConsoleInputMode(echo, false);
934         c = getchar();
935         
936         if(c == SpecialKey.escape)
937         {
938             while(kbhit())
939             {
940                 buf ~= getchar();
941             }
942             writeln(buf);
943             if(buf in specialKeys) {
944                 c = specialKeys[buf];
945             } else {
946                 c = -1;
947             }
948         }
949         
950         mode = m;
951         
952         return c;
953     }
954     
955     /**
956      * Checks if anykey is pressed.
957      * 
958      * Shift, Ctrl and Alt keys are not detected.
959      * 
960      * Returns:
961      *  True if anykey is pressed, false otherwise.
962      */
963     bool kbhit()
964     {
965         ConsoleInputMode m;
966         int c;
967         int old;
968         
969         m = mode;
970         mode = ConsoleInputMode.None;
971         
972         old = fcntl(STDIN_FILENO, F_GETFL, 0);
973         fcntl(STDIN_FILENO, F_SETFL, old | O_NONBLOCK);
974 
975         c = getchar();
976 
977         fcntl(STDIN_FILENO, F_SETFL, old);
978         mode = m;
979 
980         if(c != EOF)
981         {
982             ungetc(c, stdin.getFP);
983             return true;
984         }
985 
986         return false;
987     }
988     
989     /**
990      * Sets cursor visibility
991      * 
992      * Params:
993      *  visible = Cursor visibility
994      */
995     void cursorVisible(bool visible) @property
996     {
997         char c;
998         if(visible)
999             c = 'h';
1000         else
1001             c = 'l';
1002            
1003         writef("\033[?25%c", c);
1004     }
1005     
1006     private CloseEvent idToCloseEvent(ulong i)
1007     {
1008         CloseEvent ce;
1009         
1010         switch(i)
1011         {
1012             case SIGINT:
1013                 ce.type = CloseType.Interrupt;
1014             break;
1015             
1016             case SIGQUIT:
1017                 ce.type = CloseType.Quit;
1018             break;
1019             
1020             case SIGTSTP:
1021                 ce.type = CloseType.Stop;
1022             break;
1023             
1024             default:
1025                 ce.type = CloseType.Other;
1026         }
1027         
1028         ce.isBlockable = (ce.type != CloseType.Other);
1029         
1030         return ce;
1031     }
1032     
1033     private extern(C) void defaultCloseHandler(int reason) @system
1034     {
1035         foreach(closeHandler; closeHandlers)
1036         {
1037             closeHandler(idToCloseEvent(reason));
1038         }
1039     }
1040 }
1041 
1042 /**
1043  * Console width
1044  * 
1045  * Returns:
1046  *  Console width as number of columns
1047  */
1048 @property int width()
1049 {
1050     return size.x;
1051 }
1052 
1053 /**
1054  * Console height
1055  * 
1056  * Returns:
1057  *  Console height as number of rows
1058  */
1059 @property int height()
1060 {
1061     return size.y;
1062 }
1063 
1064 
1065 /**
1066  * Reads password from user
1067  * 
1068  * Params:
1069  *  mask = Typed character mask
1070  * 
1071  * Returns:
1072  *  Password
1073  */
1074 string readPassword(char mask = '*')
1075 {
1076     string pass;
1077     int c;
1078     
1079     version(Windows)
1080     {
1081         int backspace = 8;
1082         int enter = 13;
1083     }
1084     version(Posix)
1085     {
1086         int backspace = 127;
1087         int enter = 10;
1088     }
1089     
1090     while((c = getch()) != enter)
1091     {
1092         if(c == backspace) {
1093             if(pass.length > 0) {
1094                 pass = pass[0..$-1];
1095                 write("\b \b");
1096                 stdout.flush();
1097             }
1098         } else {
1099             pass ~= cast(char)c;
1100             write(mask);
1101         }
1102     }
1103     
1104     return pass;
1105 }
1106 
1107 
1108 /**
1109  * Fills area with specified character
1110  * 
1111  * Params:
1112  *  p1 = Top-Left corner coordinates of area
1113  *  p2 = Bottom-Right corner coordinates of area
1114  *  fill = Character to fill area
1115  */
1116 void fillArea(ConsolePoint p1, ConsolePoint p2, char fill)
1117 {
1118     foreach(i; p1.y .. p2.y + 1)
1119     {       
1120         setCursorPos(p1.x, i);
1121         write( replicate((&fill)[0..1], p2.x - p1.x));
1122                                 // ^ Converting char to char[]
1123         stdout.flush();
1124     }
1125 }
1126 
1127 /**
1128  * Draws box with specified border character
1129  * 
1130  * Params:
1131  *  p1 = Top-Left corner coordinates of box
1132  *  p2 = Bottom-Right corner coordinates of box
1133  *  fill = Border character
1134  */
1135 void drawBox(ConsolePoint p1, ConsolePoint p2, char border)
1136 {
1137     drawHorizontalLine(p1, p2.x - p1.x, border);
1138     foreach(i; p1.y + 1 .. p2.y)
1139     {       
1140         setCursorPos(p1.x, i);
1141         write(border);
1142         setCursorPos(p2.x - 1, i);
1143         write(border);
1144     }
1145     drawHorizontalLine(ConsolePoint(p1.x, p2.y), p2.x - p1.x, border);
1146 }
1147 
1148 /**
1149  * Draws horizontal line with specified fill character
1150  * 
1151  * Params:
1152  *  pos = Start coordinates
1153  *  length = Line width
1154  *  border = Border character
1155  */
1156 void drawHorizontalLine(ConsolePoint pos, int length, char border)
1157 {
1158     setCursorPos(pos.x, pos.y);
1159     write(replicate((&border)[0..1], length));
1160 }
1161 
1162 /**
1163  * Draws horizontal line with specified fill character
1164  * 
1165  * Params:
1166  *  pos = Start coordinates
1167  *  length = Line height
1168  *  border = Border character
1169  */
1170 void drawVerticalLine(ConsolePoint pos, int length, char border)
1171 {
1172     foreach(i; pos.y .. length)
1173     {
1174         setCursorPos(pos.x, i);
1175         write(border);
1176     }
1177 }
1178 
1179 /**
1180  * Writes at specified position
1181  * 
1182  * Params:
1183  *  point = Where to write
1184  *  data = Data to write
1185  */
1186 void writeAt(T)(ConsolePoint point, T data)
1187 {
1188     setCursorPos(point.x, point.y);
1189     write(data);
1190     stdout.flush();
1191 }
1192 
1193 /**
1194  * Clears console screen
1195  */
1196 void clearScreen()
1197 {
1198     auto size = size;
1199     short length = cast(short)(size.x * size.y); // Number of all characters to write
1200     setCursorPos(0, 0);
1201     
1202     write( std.array.replicate(" ", length));
1203     stdout.flush();
1204 }
1205 
1206 /**
1207  * Brings default colors back
1208  */
1209 void resetColors()
1210 {
1211     foreground = Color.initial;
1212     background = Color.initial;
1213 }
1214 
1215 
1216 /**
1217  * Brings font formatting to default
1218  */
1219 void resetFontStyle()
1220 {
1221     fontStyle = FontStyle.none;
1222 }
1223 
1224 
1225 struct EnumTypedef(T, string _name) if(is(T == enum))
1226 {
1227     public T val = T.init;
1228     
1229     this(T v) { val = v; }
1230     
1231     static EnumTypedef!(T, _name) opDispatch(string n)()
1232     {
1233         return EnumTypedef!(T, _name)(__traits(getMember, val, n));
1234     }
1235 }
1236 
1237 /// Alias for color enum
1238 alias EnumTypedef!(Color, "fg") Fg;
1239 
1240 /// ditto
1241 alias EnumTypedef!(Color, "bg") Bg;
1242 
1243 
1244 /**
1245  * Represents color theme.
1246  * 
1247  * Examples:
1248  * ----
1249  * alias ThError = ColorTheme(Color.red, Color.black);
1250  * writeln(ThError("string to write using Error theme(red foreground on black background)"));
1251  * ----
1252  */
1253 struct ColorTheme(Color fg, Color bg)
1254 {
1255     string s;
1256     this(string s)
1257     {
1258         this.s = s;
1259     }
1260 
1261     void toString(scope void delegate(const(char)[]) sink) const
1262     {
1263         auto _fg = foreground;
1264         auto _bg = background;
1265         foreground = fg;
1266         background = bg;
1267         sink(s.dup);
1268         foreground = _fg;
1269         background = _bg;
1270     }
1271 }
1272 
1273 
1274 /**
1275  * Writes text to console and colorizes text
1276  * 
1277  * Params:
1278  *  params = Text to write
1279  */
1280 void writec(T...)(T params)
1281 {
1282     foreach(param; params)
1283     {
1284         static if(is(typeof(param) == Fg)) {
1285             foreground = param.val;
1286         } else static if(is(typeof(param) == Bg)) {
1287             background = param.val;
1288         } else {
1289             write(param);
1290         }
1291     }
1292 }
1293 
1294 /**
1295  * Writes line to console and goes to newline
1296  * 
1297  * Params:
1298  *  params = Text to write
1299  */
1300 void writecln(T...)(T params)
1301 {
1302     writec(params);
1303     writeln();
1304 }