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