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 }