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 }