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