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 &lt;
119 /// - To pass '>' as text and not a tag, use &gt;
120 /// - To pass '&' as text not an entity, use &amp;
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 &lt; or &gt; or &amp;");                
582             }
583             throw new Exception("Unfinished entity name, you probably mean &lt; or &gt; or &amp;");
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 &gt; 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 }