1 /**
2  * Control the various text mode attributes, such as color, when writing text
3  * to the console.
4  *
5  * Copyright:   Copyright (C) 1999-2020 by The D Language Foundation, All Rights Reserved
6  * Authors:     $(LINK2 http://www.digitalmars.com, Walter Bright)
7  * License:     $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
8  * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/console.d, _console.d)
9  * Documentation:  https://dlang.org/phobos/dmd_console.html
10  * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/console.d
11  */
12 
13 module dmd.console;
14 
15 import core.stdc.stdio;
16 extern (C) int isatty(int) nothrow;
17 
18 
19 enum Color : int
20 {
21     black         = 0,
22     red           = 1,
23     green         = 2,
24     blue          = 4,
25     yellow        = red | green,
26     magenta       = red | blue,
27     cyan          = green | blue,
28     lightGray     = red | green | blue,
29     bright        = 8,
30     darkGray      = bright | black,
31     brightRed     = bright | red,
32     brightGreen   = bright | green,
33     brightBlue    = bright | blue,
34     brightYellow  = bright | yellow,
35     brightMagenta = bright | magenta,
36     brightCyan    = bright | cyan,
37     white         = bright | lightGray,
38 }
39 
40 struct Console
41 {
42   nothrow:
43 
44     version (Windows)
45     {
46         import core.sys.windows.winbase;
47         import core.sys.windows.wincon;
48         import core.sys.windows.windef;
49 
50       private:
51         CONSOLE_SCREEN_BUFFER_INFO sbi;
52         HANDLE handle;
53         FILE* _fp;
54 
55       public:
56 
57         @property FILE* fp() { return _fp; }
58 
59         /**
60          Tries to detect whether DMD has been invoked from a terminal.
61          Returns: `true` if a terminal has been detected, `false` otherwise
62          */
63         static bool detectTerminal()
64         {
65             auto h = GetStdHandle(STD_OUTPUT_HANDLE);
66             CONSOLE_SCREEN_BUFFER_INFO sbi;
67             if (GetConsoleScreenBufferInfo(h, &sbi) == 0) // get initial state of console
68                 return false; // no terminal detected
69 
70             version (CRuntime_DigitalMars)
71             {
72                 return isatty(stdout._file) != 0;
73             }
74             else version (CRuntime_Microsoft)
75             {
76                 return isatty(fileno(stdout)) != 0;
77             }
78             else
79             {
80                 static assert(0, "Unsupported Windows runtime.");
81             }
82         }
83 
84         /*********************************
85          * Create an instance of Console connected to stream fp.
86          * Params:
87          *      fp = io stream
88          * Returns:
89          *      pointer to created Console
90          *      null if failed
91          */
92         static Console* create(FILE* fp)
93         {
94             /* Determine if stream fp is a console
95              */
96             version (CRuntime_DigitalMars)
97             {
98                 if (!isatty(fp._file))
99                     return null;
100             }
101             else version (CRuntime_Microsoft)
102             {
103                 if (!isatty(fileno(fp)))
104                     return null;
105             }
106             else
107             {
108                 return null;
109             }
110 
111             DWORD nStdHandle;
112             if (fp == stdout)
113                 nStdHandle = STD_OUTPUT_HANDLE;
114             else if (fp == stderr)
115                 nStdHandle = STD_ERROR_HANDLE;
116             else
117                 return null;
118 
119             auto h = GetStdHandle(nStdHandle);
120             CONSOLE_SCREEN_BUFFER_INFO sbi;
121             if (GetConsoleScreenBufferInfo(h, &sbi) == 0) // get initial state of console
122                 return null;
123 
124             auto c = new Console();
125             c._fp = fp;
126             c.handle = h;
127             c.sbi = sbi;
128             return c;
129         }
130 
131         /*******************
132          * Turn on/off intensity.
133          * Params:
134          *      bright = turn it on
135          */
136         void setColorBright(bool bright)
137         {
138             SetConsoleTextAttribute(handle, sbi.wAttributes | (bright ? FOREGROUND_INTENSITY : 0));
139         }
140 
141         /***************************
142          * Set color and intensity.
143          * Params:
144          *      color = the color
145          */
146         void setColor(Color color)
147         {
148             enum FOREGROUND_WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
149             WORD attr = sbi.wAttributes;
150             attr = (attr & ~(FOREGROUND_WHITE | FOREGROUND_INTENSITY)) |
151                    ((color & Color.red)    ? FOREGROUND_RED   : 0) |
152                    ((color & Color.green)  ? FOREGROUND_GREEN : 0) |
153                    ((color & Color.blue)   ? FOREGROUND_BLUE  : 0) |
154                    ((color & Color.bright) ? FOREGROUND_INTENSITY : 0);
155             SetConsoleTextAttribute(handle, attr);
156         }
157 
158         /******************
159          * Reset console attributes to what they were
160          * when create() was called.
161          */
162         void resetColor()
163         {
164             SetConsoleTextAttribute(handle, sbi.wAttributes);
165         }
166     }
167     else version (Posix)
168     {
169         /* The ANSI escape codes are used.
170          * https://en.wikipedia.org/wiki/ANSI_escape_code
171          * Foreground colors: 30..37
172          * Background colors: 40..47
173          * Attributes:
174          *  0: reset all attributes
175          *  1: high intensity
176          *  2: low intensity
177          *  3: italic
178          *  4: single line underscore
179          *  5: slow blink
180          *  6: fast blink
181          *  7: reverse video
182          *  8: hidden
183          */
184 
185         import core.sys.posix.unistd;
186 
187       private:
188         FILE* _fp;
189 
190       public:
191 
192         @property FILE* fp() { return _fp; }
193         /**
194          Tries to detect whether DMD has been invoked from a terminal.
195          Returns: `true` if a terminal has been detect, `false` otherwise
196          */
197         static bool detectTerminal()
198         {
199             import core.stdc.stdlib : getenv;
200             const(char)* term = getenv("TERM");
201             import core.stdc.string : strcmp;
202             return isatty(STDERR_FILENO) && term && term[0] && strcmp(term, "dumb") != 0;
203         }
204 
205         static Console* create(FILE* fp)
206         {
207             auto c = new Console();
208             c._fp = fp;
209             return c;
210         }
211 
212         void setColorBright(bool bright)
213         {
214             fprintf(_fp, "\033[%dm", bright);
215         }
216 
217         void setColor(Color color)
218         {
219             fprintf(_fp, "\033[%d;%dm", color & Color.bright ? 1 : 0, 30 + (color & ~Color.bright));
220         }
221 
222         void resetColor()
223         {
224             fputs("\033[m", _fp);
225         }
226     }
227     else
228     {
229         @property FILE* fp() { assert(0); }
230 
231         static Console* create(FILE* fp)
232         {
233             return null;
234         }
235 
236         void setColorBright(bool bright)
237         {
238             assert(0);
239         }
240 
241         void setColor(Color color)
242         {
243             assert(0);
244         }
245 
246         void resetColor()
247         {
248             assert(0);
249         }
250     }
251 
252 }