1 /** 2 * Console colors library. Define the main symbols cwrite[f][ln], and color functions. 3 * 4 * Copyright: Guillaume Piolat 2014-2022. 5 * Copyright: Adam D. Ruppe 2013-2022. 6 * Copyright: Robert Pasiński 2012-2013. 7 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 8 */ 9 module consolecolors; 10 11 import core.stdc.stdio: printf, FILE, fwrite, fflush, fputc; 12 import std.stdio : File, stdout; 13 import std.string: format; 14 version(Windows) import core.sys.windows.windows; 15 version(Posix) import core.sys.posix.unistd; 16 17 public: 18 19 /// Return all available console string colors for this library. 20 string[] availableConsoleColors() pure nothrow @safe 21 { 22 return [ 23 "black", "red", "green", "orange", "blue", "magenta", "cyan", "lgrey", 24 "grey", "lred", "lgreen", "yellow", "lblue", "lmagenta", "lcyan", "white" 25 ]; 26 } 27 28 pure nothrow @safe 29 { 30 /// Terminal colors functions. Change foreground color of the text. 31 /// This wraps the text around, for consumption into `cwrite` or equivalent. 32 string black(const(char)[] text) { return "<black>" ~ text ~ "</black>"; } 33 ///ditto 34 string red(const(char)[] text) { return "<red>" ~ text ~ "</red>"; } 35 ///ditto 36 string green(const(char)[] text) { return "<green>" ~ text ~ "</green>"; } 37 ///ditto 38 string orange(const(char)[] text) { return "<orange>" ~ text ~ "</orange>"; } 39 ///ditto 40 string blue(const(char)[] text) { return "<blue>" ~ text ~ "</blue>"; } 41 ///ditto 42 string magenta(const(char)[] text) { return "<magenta>" ~ text ~ "</magenta>"; } 43 ///ditto 44 string cyan(const(char)[] text) { return "<cyan>" ~ text ~ "</cyan>"; } 45 ///ditto 46 string lgrey(const(char)[] text) { return "<lgrey>" ~ text ~ "</lgrey>"; } 47 ///ditto 48 string grey(const(char)[] text) { return "<grey>" ~ text ~ "</grey>"; } 49 ///ditto 50 string lred(const(char)[] text) { return "<lred>" ~ text ~ "</lred>"; } 51 ///ditto 52 string lgreen(const(char)[] text) { return "<lgreen>" ~ text ~ "</lgreen>"; } 53 ///ditto 54 string yellow(const(char)[] text) { return "<yellow>" ~ text ~ "</yellow>"; } 55 ///ditto 56 string lblue(const(char)[] text) { return "<lblue>" ~ text ~ "</lblue>"; } 57 ///ditto 58 string lmagenta(const(char)[] text) { return "<lmagenta>" ~ text ~ "</lmagenta>"; } 59 ///ditto 60 string lcyan(const(char)[] text) { return "<lcyan>" ~ text ~ "</lcyan>"; } 61 ///ditto 62 string white(const(char)[] text) { return "<white>" ~ text ~ "</white>"; } 63 64 /// Change background color of the text. 65 /// This wraps the text around, for consumption into `cwrite` or equivalent. 66 string on_black(const(char)[] text) { return "<on_black>" ~ text ~ "</on_black>"; } 67 ///ditto 68 string on_red(const(char)[] text) { return "<on_red>" ~ text ~ "</on_red>"; } 69 ///ditto 70 string on_green(const(char)[] text) { return "<on_green>" ~ text ~ "</on_green>"; } 71 ///ditto 72 string on_orange(const(char)[] text) { return "<on_orange>" ~ text ~ "</on_orange>"; } 73 ///ditto 74 string on_blue(const(char)[] text) { return "<on_blue>" ~ text ~ "</on_blue>"; } 75 ///ditto 76 string on_magenta(const(char)[] text) { return "<on_magenta>" ~ text ~ "</on_magenta>"; } 77 ///ditto 78 string on_cyan(const(char)[] text) { return "<on_cyan>" ~ text ~ "</on_cyan>"; } 79 ///ditto 80 string on_lgrey(const(char)[] text) { return "<on_lgrey>" ~ text ~ "</on_lgrey>"; } 81 ///ditto 82 string on_grey(const(char)[] text) { return "<on_grey>" ~ text ~ "</on_grey>"; } 83 ///ditto 84 string on_lred(const(char)[] text) { return "<on_lred>" ~ text ~ "</on_lred>"; } 85 ///ditto 86 string on_lgreen(const(char)[] text) { return "<on_lgreen>" ~ text ~ "</on_lgreen>"; } 87 ///ditto 88 string on_yellow(const(char)[] text) { return "<on_yellow>" ~ text ~ "</on_yellow>"; } 89 ///ditto 90 string on_lblue(const(char)[] text) { return "<on_lblue>" ~ text ~ "</on_lblue>"; } 91 ///ditto 92 string on_lmagenta(const(char)[] text) { return "<on_lmagenta>" ~ text ~ "</on_lmagenta>"; } 93 ///ditto 94 string on_lcyan(const(char)[] text) { return "<on_lcyan>" ~ text ~ "</on_lcyan>"; } 95 ///ditto 96 string on_white(const(char)[] text) { return "<on_white>" ~ text ~ "</on_white>"; } 97 } 98 99 /// Wraps text into a particular foreground color. 100 /// Wrong colourname gets ignored. 101 string color(const(char)[] text, const(char)[] color) pure @safe 102 { 103 return format("<%s>%s</%s>", color, text, color); 104 } 105 106 /// Coloured `write`/`writef`/`writeln`/`writefln`. 107 /// 108 /// The language that these function take as input can contain HTML tags. 109 /// Unknown tags have no effect and are removed. 110 /// Tags can't have attributes. 111 /// 112 /// Accepted tags: 113 /// - <COLORNAME> such as: 114 /// <black>, <red>, <green>, <orange>, <blue>, <magenta>, <cyan>, <lgrey>, 115 /// <grey>, <lred>, <lgreen>, <yellow>, <lblue>, <lmagenta>, <lcyan>, <white> 116 /// 117 /// Escaping: 118 /// - To pass '<' as text and not a tag, use < 119 /// - To pass '>' as text and not a tag, use > 120 /// - To pass '&' as text not an entity, use & 121 void cwrite(T...)(T args) 122 { 123 import std.conv : to; 124 125 // PERF: meh 126 string s = ""; 127 foreach(arg; args) 128 s ~= to!string(arg); 129 130 int res = emitToTerminal(s); 131 132 // Throw error if parsing error. 133 switch(res) 134 { 135 case CC_ERR_OK: break; 136 case CC_UNTERMINATED_TAG: throw new Exception("Unterminated <tag> in coloured text"); 137 case CC_UNKNOWN_TAG: throw new Exception("Unknown <tag> in coloured text"); 138 case CC_MISMATCHED_TAG: throw new Exception("Mismatched <tag> in coloured text"); 139 case CC_TERMINAL_ERROR: throw new Exception("Unspecified terminal error"); 140 default: 141 assert(false); // if you fail here, console-colors is buggy 142 } 143 } 144 145 ///ditto 146 void cwriteln(T...)(T args) 147 { 148 // Most general instance 149 cwrite(args, '\n'); 150 } 151 152 ///ditto 153 void cwritef(Char, T...)(in Char[] fmt, T args) 154 { 155 import std.string : format; 156 auto s = format(fmt, args); 157 cwrite(s); 158 } 159 160 ///ditto 161 void cwritefln(Char, T...)(in Char[] fmt, T args) 162 { 163 cwritef(fmt ~ "\n", args); 164 } 165 166 /// Disable output console colors. 167 void disableConsoleColors() 168 { 169 g_termInterpreter.disableColors(); 170 } 171 172 173 // PRIVATE PARTS API START HERE 174 private: 175 176 177 enum int CC_ERR_OK = 0, // "<blue>text</blue>" 178 CC_UNTERMINATED_TAG = 1, // "<blue" 179 CC_UNKNOWN_TAG = 2, // "<pink>text</pink>" 180 CC_MISMATCHED_TAG = 3, // "<blue>text</red>" 181 CC_TERMINAL_ERROR = 4; // terminal.d error. 182 183 // Implementation of `emitToTerminal`. This is a combined lexer/parser/emitter. 184 // It can throw Exception in case of misformat of the input text. 185 int emitToTerminal( const(char)[] s) @trusted 186 { 187 TermInterpreter* term = &g_termInterpreter; 188 return term.interpret(s); 189 } 190 191 private: 192 193 /// A global, shared state machine that does the terminal emulation and book-keeping. 194 TermInterpreter g_termInterpreter = TermInterpreter.init; 195 196 shared static this() 197 { 198 g_termInterpreter.initialize(); 199 } 200 201 shared static ~this() 202 { 203 destroy(g_termInterpreter); 204 } 205 206 struct TermInterpreter 207 { 208 void initialize() 209 { 210 if (stdoutIsTerminal) 211 { 212 if (_terminal.initialize()) 213 { 214 _enableTerm = true; 215 cstdout = stdout.getFP(); 216 } 217 } 218 } 219 220 ~this() 221 { 222 } 223 224 void disableColors() 225 { 226 _enableTerm = false; 227 } 228 229 /// Moves the interpreter forward, eventually do actions. 230 /// Return: error code. 231 int interpret(const(char)[] s) 232 { 233 // Init tag stack. 234 // State is reset between all calls to interpret, so that errors can be eaten out. 235 236 input = s; 237 inputPos = 0; 238 239 stack(0) = Tag(TermColor.unknown, TermColor.unknown, "html"); 240 _tagStackIndex = 0; 241 242 setForeground(TermColor.initial); 243 setBackground(TermColor.initial); 244 245 bool finished = false; 246 bool termTextWasOutput = false; 247 while(!finished) 248 { 249 final switch (_parserState) 250 { 251 case ParserState.initial: 252 253 Token token = getNextToken(); 254 final switch(token.type) 255 { 256 case TokenType.tagOpen: 257 { 258 enterTag(token.text); 259 break; 260 } 261 262 case TokenType.tagClose: 263 { 264 exitTag(token.text); 265 break; 266 } 267 268 case TokenType.tagOpenClose: 269 { 270 enterTag(token.text); 271 exitTag(token.text); 272 break; 273 } 274 275 case TokenType.text: 276 { 277 printf("%.*s", cast(int)token.text.length, token.text.ptr); 278 break; 279 } 280 281 case TokenType.endOfInput: 282 finished = true; 283 break; 284 285 } 286 break; 287 } 288 } 289 return 0; 290 } 291 292 private: 293 bool _enableTerm = false; 294 Terminal _terminal; 295 296 FILE* cstdout; 297 298 // Style/Tag stack 299 static struct Tag 300 { 301 TermColor fg = TermColor.unknown; // last applied foreground color 302 TermColor bg = TermColor.unknown; // last applied background color 303 const(char)[] name; // last applied tag 304 } 305 enum int MAX_NESTED_TAGS = 32; 306 307 Tag[MAX_NESTED_TAGS] _stack; 308 int _tagStackIndex; 309 310 ref Tag stack(int index) nothrow @nogc return 311 { 312 return _stack[index]; 313 } 314 315 ref Tag stackTop() nothrow @nogc return 316 { 317 return _stack[_tagStackIndex]; 318 } 319 320 void enterTag(const(char)[] tagName) 321 { 322 if (_tagStackIndex >= MAX_NESTED_TAGS) 323 throw new Exception("Tag stack is full, internal error of console-colors"); 324 325 // dup top of stack, set foreground color 326 _stack[_tagStackIndex + 1] = _stack[_tagStackIndex]; 327 _stack[_tagStackIndex + 1].name = tagName; // Note: this name doesn't outlive the line of text we write 328 329 _tagStackIndex += 1; 330 331 bool bg = false; 332 if ((tagName.length >= 3) && (tagName[0..3] == "on_")) 333 { 334 tagName = tagName[3..$]; 335 bg = true; 336 } 337 338 switch(tagName) 339 { 340 case "black": setColor(TermColor.black, bg); break; 341 case "red": setColor(TermColor.red, bg); break; 342 case "green": setColor(TermColor.green, bg); break; 343 case "orange": setColor(TermColor.orange, bg); break; 344 case "blue": setColor(TermColor.blue, bg); break; 345 case "magenta": setColor(TermColor.magenta, bg); break; 346 case "cyan": setColor(TermColor.cyan, bg); break; 347 case "lgrey": setColor(TermColor.lgrey, bg); break; 348 case "grey": setColor(TermColor.grey, bg); break; 349 case "lred": setColor(TermColor.lred, bg); break; 350 case "lgreen": setColor(TermColor.lgreen, bg); break; 351 case "yellow": setColor(TermColor.yellow, bg); break; 352 case "lblue": setColor(TermColor.lblue, bg); break; 353 case "lmagenta": setColor(TermColor.lmagenta, bg); break; 354 case "lcyan": setColor(TermColor.lcyan, bg); break; 355 case "white": setColor(TermColor.white, bg); break; 356 default: 357 break; // unknown tag 358 } 359 } 360 361 void setColor(TermColor c, bool bg) nothrow @nogc 362 { 363 if (bg) setBackground(c); 364 else setForeground(c); 365 } 366 367 void setForeground(TermColor fg) nothrow @nogc 368 { 369 stackTop().fg = fg; 370 if (_enableTerm) 371 _terminal.setForegroundColor(stackTop().fg, &flushStdoutIfWindows); 372 } 373 374 void setBackground(TermColor bg) nothrow @nogc 375 { 376 stackTop().bg = bg; 377 if (_enableTerm) 378 _terminal.setBackgroundColor(stackTop().bg, &flushStdoutIfWindows); 379 } 380 381 void applyStyleOnTop() 382 { 383 if (_enableTerm) 384 { 385 // PERF: do this at once. 386 _terminal.setForegroundColor(stackTop().fg, &flushStdoutIfWindows); 387 _terminal.setBackgroundColor(stackTop().bg, &flushStdoutIfWindows); 388 } 389 } 390 391 void exitTag(const(char)[] tagName) 392 { 393 if (_tagStackIndex <= 0) 394 throw new Exception("Unexpected closing tag"); 395 396 if (stackTop().name != tagName) 397 throw new Exception("Closing tag mismatch"); 398 399 // drop one state of stack, apply old style 400 _tagStackIndex -= 1; 401 applyStyleOnTop(); 402 } 403 404 // <parser> 405 406 ParserState _parserState = ParserState.initial; 407 enum ParserState 408 { 409 initial 410 } 411 412 // </parser> 413 414 // <lexer> 415 416 const(char)[] input; 417 int inputPos; 418 419 LexerState _lexerState = LexerState.initial; 420 enum LexerState 421 { 422 initial, 423 insideEntity, 424 insideTag, 425 } 426 427 enum TokenType 428 { 429 tagOpen, // <red> 430 tagClose, // </red> 431 tagOpenClose, // <red/> 432 text, 433 endOfInput 434 } 435 436 static struct Token 437 { 438 TokenType type; 439 440 // name of tag, or text 441 const(char)[] text = null; 442 443 // position in input text 444 int inputPos = 0; 445 } 446 447 bool hasNextChar() 448 { 449 return inputPos < input.length; 450 } 451 452 char peek() 453 { 454 return input[inputPos]; 455 } 456 457 const(char)[] lastNChars(int n) 458 { 459 return input[inputPos - n .. inputPos]; 460 } 461 462 const(char)[] charsSincePos(int pos) 463 { 464 return input[pos .. inputPos]; 465 } 466 467 void next() 468 { 469 inputPos += 1; 470 } 471 472 void flushStdoutIfWindows() nothrow @nogc 473 { 474 version(Windows) 475 { 476 // On windows, because the C stdlib is buffered, we need to flush() 477 // before changing color else text is going to be the next color. 478 if (cstdout != null) 479 fflush(cstdout); 480 } 481 } 482 483 Token getNextToken() 484 { 485 Token r; 486 r.inputPos = inputPos; 487 488 if (!hasNextChar()) 489 { 490 r.type = TokenType.endOfInput; 491 return r; 492 } 493 else if (peek() == '<') 494 { 495 // it is a tag 496 bool closeTag = false; 497 next; 498 if (!hasNextChar()) 499 throw new Exception("Excepted tag name after <"); 500 501 if (peek() == '/') 502 { 503 closeTag = true; 504 next; 505 if (!hasNextChar()) 506 throw new Exception("Excepted tag name after </"); 507 } 508 509 const(char)[] tagName; 510 int startOfTagName = inputPos; 511 512 while(hasNextChar()) 513 { 514 char ch = peek(); 515 if (ch == '/') 516 { 517 tagName = charsSincePos(startOfTagName); 518 if (closeTag) 519 throw new Exception("Can't have tags like this </tagname/>"); 520 521 next; 522 if (!hasNextChar()) 523 throw new Exception("Excepted '>' in closing tag "); 524 525 if (peek() == '>') 526 { 527 next; 528 529 r.type = TokenType.tagOpenClose; 530 r.text = tagName; 531 return r; 532 } 533 } 534 else if (ch == '>') 535 { 536 tagName = charsSincePos(startOfTagName); 537 next; 538 r.type = closeTag ? TokenType.tagClose : TokenType.tagOpen; 539 r.text = tagName; 540 return r; 541 } 542 else 543 { 544 next; 545 } 546 // TODO: check chars are valid in HTML tags 547 } 548 throw new Exception("Unterminated tag"); 549 } 550 else if (peek() == '&') 551 { 552 // it is an HTML entity 553 next; 554 if (!hasNextChar()) 555 throw new Exception("Excepted entity name after &"); 556 557 int startOfEntity = inputPos; 558 while(hasNextChar()) 559 { 560 char ch = peek(); 561 if (ch == ';') 562 { 563 const(char)[] entityName = charsSincePos(startOfEntity); 564 switch (entityName) 565 { 566 case "lt": r.text = "<"; break; 567 case "gt": r.text = ">"; break; 568 case "amp": r.text = "&"; break; 569 default: 570 throw new Exception("Unknown entity name"); 571 } 572 next; 573 r.type = TokenType.text; 574 return r; 575 } 576 else if ((ch >= 'a' && ch <= 'z') || (ch >= 'a' && ch <= 'z')) 577 { 578 next; 579 } 580 else 581 throw new Exception("Illegal character in entity name, you probably mean < or > or &"); 582 } 583 throw new Exception("Unfinished entity name, you probably mean < or > or &"); 584 } 585 else 586 { 587 int startOfText = inputPos; 588 while(hasNextChar()) 589 { 590 char ch = peek(); 591 if (ch == '>') 592 throw new Exception("Illegal character >, use > instead if intended"); 593 if (ch == '<') 594 break; 595 if (ch == '&') 596 break; 597 next; 598 } 599 assert(inputPos != startOfText); 600 r.type = TokenType.text; 601 r.text = charsSincePos(startOfText); 602 return r; 603 } 604 } 605 } 606 607 nothrow @nogc @safe: 608 609 /// Those are the colors supported by `Terminal` (not the colors of the outside API). 610 /// Their value when positive is the value of Windows foreground colors. 611 enum TermColor : int 612 { 613 unknown = -2, // unknown color, for example when detection failed 614 initial = -1, // the color detected at creation of Terminal 615 black = 0, 616 red, 617 green, 618 orange, 619 blue, 620 magenta, 621 cyan, 622 lgrey, 623 grey, 624 lred, 625 lgreen, 626 yellow, 627 lblue, 628 lmagenta, 629 lcyan, 630 white, 631 } 632 633 634 // Term provide the following API: 635 // - initialize(): capture existing colors, restore them in destructor 636 // - setForegroundColor(TermColor color) 637 // - setBackgroundColor(TermColor color) 638 struct Terminal 639 { 640 nothrow @nogc @safe: 641 642 // Initialize the terminal. 643 // Return: success. If false, don't use this instance. 644 bool initialize() @trusted 645 { 646 version(Posix) 647 { 648 _initialForegroundColor = TermColor.initial; 649 _initialBackgroundColor = TermColor.initial; 650 _currentForegroundColor = _initialForegroundColor; 651 _currentBackgroundColor = _initialBackgroundColor; 652 return true; 653 } 654 else version(Windows) 655 { 656 // saves console attributes 657 _console = GetStdHandle(STD_OUTPUT_HANDLE); 658 if (_console == null) 659 return false; 660 CONSOLE_SCREEN_BUFFER_INFO consoleInfo; 661 if (_console && GetConsoleScreenBufferInfo(_console, &consoleInfo) != 0) 662 { 663 _currentAttr = consoleInfo.wAttributes; 664 665 _initialForegroundColor = convertWinattrToTermColor(_currentAttr, false); 666 _initialBackgroundColor = convertWinattrToTermColor(_currentAttr, true); 667 668 _currentForegroundColor = _initialForegroundColor; 669 _currentBackgroundColor = _initialBackgroundColor; 670 return true; 671 } 672 else 673 return false; 674 } 675 else 676 static assert(false); 677 } 678 679 ~this() @trusted 680 { 681 // Note that this is also destructed if constructor failed (.init) 682 // so have to handle it anyway like most D objects. 683 if (!_initialized) 684 return; 685 686 version(Posix) 687 { 688 printf("\x1B[0m"); 689 } 690 else version(Windows) 691 { 692 setForegroundColor(_initialForegroundColor, null); 693 setBackgroundColor(_initialBackgroundColor, null); 694 } 695 else 696 static assert(false); 697 } 698 699 void setForegroundColor(TermColor color, 700 scope void delegate() nothrow @nogc callThisBeforeChangingColor ) @trusted 701 { 702 assert(color != TermColor.unknown); 703 704 if (color == TermColor.initial) 705 color = _initialForegroundColor; 706 707 if (_currentForegroundColor == color) 708 return; 709 _currentForegroundColor = color; 710 711 if (callThisBeforeChangingColor) 712 callThisBeforeChangingColor(); 713 version(Posix) 714 { 715 int code = convertTermColorToVT100Attr(color, false); 716 printf("\x1B[%dm", code); 717 } 718 else version(Windows) 719 { 720 WORD attr = cast(WORD)( (_currentAttr & ~FOREGROUND_MASK) | convertTermColorToWinAttr(color, false) ); 721 SetConsoleTextAttribute(_console, attr); 722 _currentAttr = attr; 723 } 724 else 725 static assert(false); 726 } 727 728 void setBackgroundColor(TermColor color, scope void delegate() nothrow @nogc callThisBeforeChangingColor) @trusted 729 { 730 assert(color != TermColor.unknown); 731 732 if (color == TermColor.initial) 733 color = _initialBackgroundColor; 734 735 if (_currentBackgroundColor == color) 736 return; 737 _currentBackgroundColor = color; 738 739 if (callThisBeforeChangingColor) 740 callThisBeforeChangingColor(); 741 version(Posix) 742 { 743 int code = convertTermColorToVT100Attr(color, true); 744 printf("\x1B[%dm", code); 745 } 746 else version(Windows) 747 { 748 WORD attr = cast(WORD)( (_currentAttr & ~BACKGROUND_MASK) | (convertTermColorToWinAttr(color, true)) ); 749 SetConsoleTextAttribute(_console, attr); 750 _currentAttr = attr; 751 } 752 else 753 static assert(false); 754 } 755 756 private: 757 758 // Successfully initialized. 759 bool _initialized = false; 760 761 // At initialization, find those. 762 TermColor _initialForegroundColor = TermColor.unknown; 763 TermColor _initialBackgroundColor = TermColor.unknown; 764 765 // Act as cache to avoid useless syscalls. 766 TermColor _currentForegroundColor = TermColor.unknown; 767 TermColor _currentBackgroundColor = TermColor.unknown; 768 769 version(Windows) 770 { 771 HANDLE _console; // console handle. 772 WORD _currentAttr; // Last known cached console attribute. 773 CONSOLE_SCREEN_BUFFER_INFO consoleInfo; 774 } 775 776 version(Windows) 777 { 778 enum int BACKGROUND_MASK = (BACKGROUND_BLUE | BACKGROUND_GREEN | BACKGROUND_RED | BACKGROUND_INTENSITY); 779 enum int FOREGROUND_MASK = (FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED | FOREGROUND_INTENSITY); 780 781 // Note: rotation, LUT works in both direction. 782 static immutable ubyte[16] TRANSLATE_WINATTR = [ 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 ]; 783 } 784 785 TermColor convertWinattrToTermColor(int attrUnmasked, bool bg) 786 { 787 if (bg) attrUnmasked = attrUnmasked >>> 4; 788 return cast(TermColor) TRANSLATE_WINATTR[attrUnmasked & 15]; 789 } 790 791 /// Return a mask representing windows attribute for color c. 792 int convertTermColorToWinAttr(TermColor c, bool bg) 793 { 794 assert (c != TermColor.unknown); 795 if (c == TermColor.initial) 796 return bg ? _initialBackgroundColor : _initialForegroundColor; 797 else 798 { 799 int res = TRANSLATE_WINATTR[cast(ubyte)c]; 800 if (bg) res = res << 4; 801 return res; 802 } 803 } 804 805 int convertTermColorToVT100Attr(TermColor c, bool bg) 806 { 807 assert (c != TermColor.unknown); 808 809 int res; 810 811 if (c == TermColor.initial) 812 { 813 res = 39; 814 } 815 else 816 { 817 int lowbits = c & 7; 818 bool intensity = (c & 8) != 0; 819 res = 30 + lowbits; 820 if (intensity) res += 60; 821 } 822 823 if (bg) res += 10; 824 825 return res; 826 } 827 } 828 829 // Terminal is only valid to use on an actual console device or terminal 830 // handle. You should not attempt to construct a Terminal instance if this 831 // returns false. 832 bool stdoutIsTerminal() @trusted 833 { 834 version(Posix) 835 { 836 return cast(bool) isatty(1); 837 } 838 else version(Windows) 839 { 840 HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); 841 if (hConsole == INVALID_HANDLE_VALUE) 842 return false; 843 844 return GetFileType(hConsole) == FILE_TYPE_CHAR; 845 } 846 else 847 static assert(false); 848 }