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 }