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