Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cherry-pick upstream support for enforcing ANSI colors for redirected stderr on Windows #3744

Merged
merged 1 commit into from
May 31, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
351 changes: 172 additions & 179 deletions dmd/console.d
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ module dmd.console;
import core.stdc.stdio;
extern (C) int isatty(int) nothrow;

version (Windows)
{
import core.sys.windows.winbase;
import core.sys.windows.wincon;
import core.sys.windows.windef;
}
else version (Posix)
{
import core.sys.posix.unistd;
}
else
{
static assert(0);
}

enum Color : int
{
Expand All @@ -37,216 +51,195 @@ enum Color : int
white = bright | lightGray,
}

struct Console
interface Console
{
nothrow:
@property FILE* fp();

/**
* Turn on/off intensity.
* Params:
* bright = turn it on
*/
void setColorBright(bool bright);

/**
* Set color and intensity.
* Params:
* color = the color
*/
void setColor(Color color);

/**
* Reset console attributes to what they were
* when create() was called.
*/
void resetColor();
}

version (Windows)
{
import core.sys.windows.winbase;
import core.sys.windows.wincon;
import core.sys.windows.windef;

private:
CONSOLE_SCREEN_BUFFER_INFO sbi;
HANDLE handle;
FILE* _fp;

public:
version (Windows)
private final class WindowsConsole : Console
{
nothrow:

@property FILE* fp() { return _fp; }
private:
CONSOLE_SCREEN_BUFFER_INFO sbi;
HANDLE handle;
FILE* _fp;

/**
Tries to detect whether DMD has been invoked from a terminal.
Returns: `true` if a terminal has been detected, `false` otherwise
static HANDLE getStdHandle(FILE *fp)
{
/* Determine if stream fp is a console
*/
static bool detectTerminal()
version (CRuntime_DigitalMars)
{
auto h = GetStdHandle(STD_OUTPUT_HANDLE);
CONSOLE_SCREEN_BUFFER_INFO sbi;
if (GetConsoleScreenBufferInfo(h, &sbi) == 0) // get initial state of console
return false; // no terminal detected

version (CRuntime_DigitalMars)
{
return isatty(stdout._file) != 0;
}
else version (CRuntime_Microsoft)
{
return isatty(fileno(stdout)) != 0;
}
else
{
static assert(0, "Unsupported Windows runtime.");
}
if (!isatty(fp._file))
return null;
}

/*********************************
* Create an instance of Console connected to stream fp.
* Params:
* fp = io stream
* Returns:
* pointer to created Console
* null if failed
*/
static Console* create(FILE* fp)
else version (CRuntime_Microsoft)
{
/* Determine if stream fp is a console
*/
version (CRuntime_DigitalMars)
{
if (!isatty(fp._file))
return null;
}
else version (CRuntime_Microsoft)
{
if (!isatty(fileno(fp)))
return null;
}
else
{
if (!isatty(fileno(fp)))
return null;
}

DWORD nStdHandle;
if (fp == stdout)
nStdHandle = STD_OUTPUT_HANDLE;
else if (fp == stderr)
nStdHandle = STD_ERROR_HANDLE;
else
return null;

auto h = GetStdHandle(nStdHandle);
CONSOLE_SCREEN_BUFFER_INFO sbi;
if (GetConsoleScreenBufferInfo(h, &sbi) == 0) // get initial state of console
return null;

auto c = new Console();
c._fp = fp;
c.handle = h;
c.sbi = sbi;
return c;
}

/*******************
* Turn on/off intensity.
* Params:
* bright = turn it on
*/
void setColorBright(bool bright)
else
{
SetConsoleTextAttribute(handle, sbi.wAttributes | (bright ? FOREGROUND_INTENSITY : 0));
static assert(0, "Unsupported Windows runtime.");
}

/***************************
* Set color and intensity.
* Params:
* color = the color
*/
void setColor(Color color)
{
enum FOREGROUND_WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
WORD attr = sbi.wAttributes;
attr = (attr & ~(FOREGROUND_WHITE | FOREGROUND_INTENSITY)) |
((color & Color.red) ? FOREGROUND_RED : 0) |
((color & Color.green) ? FOREGROUND_GREEN : 0) |
((color & Color.blue) ? FOREGROUND_BLUE : 0) |
((color & Color.bright) ? FOREGROUND_INTENSITY : 0);
SetConsoleTextAttribute(handle, attr);
}
if (fp == stdout)
return GetStdHandle(STD_OUTPUT_HANDLE);
else if (fp == stderr)
return GetStdHandle(STD_ERROR_HANDLE);
else
return null;
}

/******************
* Reset console attributes to what they were
* when create() was called.
*/
void resetColor()
{
SetConsoleTextAttribute(handle, sbi.wAttributes);
}
public:

@property FILE* fp() { return _fp; }

static WindowsConsole create(FILE* fp)
{
auto h = getStdHandle(fp);
if (h is null)
return null;

CONSOLE_SCREEN_BUFFER_INFO sbi;
if (GetConsoleScreenBufferInfo(h, &sbi) == 0) // get initial state of console
return null;

auto c = new WindowsConsole();
c._fp = fp;
c.handle = h;
c.sbi = sbi;
return c;
}
else version (Posix)

void setColorBright(bool bright)
{
/* The ANSI escape codes are used.
* https://en.wikipedia.org/wiki/ANSI_escape_code
* Foreground colors: 30..37
* Background colors: 40..47
* Attributes:
* 0: reset all attributes
* 1: high intensity
* 2: low intensity
* 3: italic
* 4: single line underscore
* 5: slow blink
* 6: fast blink
* 7: reverse video
* 8: hidden
*/
SetConsoleTextAttribute(handle, sbi.wAttributes | (bright ? FOREGROUND_INTENSITY : 0));
}

import core.sys.posix.unistd;
void setColor(Color color)
{
enum FOREGROUND_WHITE = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
WORD attr = sbi.wAttributes;
attr = (attr & ~(FOREGROUND_WHITE | FOREGROUND_INTENSITY)) |
((color & Color.red) ? FOREGROUND_RED : 0) |
((color & Color.green) ? FOREGROUND_GREEN : 0) |
((color & Color.blue) ? FOREGROUND_BLUE : 0) |
((color & Color.bright) ? FOREGROUND_INTENSITY : 0);
SetConsoleTextAttribute(handle, attr);
}

private:
FILE* _fp;
void resetColor()
{
SetConsoleTextAttribute(handle, sbi.wAttributes);
}
}

public:
/* Uses the ANSI escape codes.
* https://en.wikipedia.org/wiki/ANSI_escape_code
* Foreground colors: 30..37
* Background colors: 40..47
* Attributes:
* 0: reset all attributes
* 1: high intensity
* 2: low intensity
* 3: italic
* 4: single line underscore
* 5: slow blink
* 6: fast blink
* 7: reverse video
* 8: hidden
*/
private final class ANSIConsole : Console
{
nothrow:

@property FILE* fp() { return _fp; }
/**
Tries to detect whether DMD has been invoked from a terminal.
Returns: `true` if a terminal has been detect, `false` otherwise
*/
static bool detectTerminal()
{
import core.stdc.stdlib : getenv;
const(char)* term = getenv("TERM");
import core.stdc.string : strcmp;
return isatty(STDERR_FILENO) && term && term[0] && strcmp(term, "dumb") != 0;
}
private:
FILE* _fp;

static Console* create(FILE* fp)
{
auto c = new Console();
c._fp = fp;
return c;
}
public:

void setColorBright(bool bright)
{
fprintf(_fp, "\033[%dm", bright);
}
this(FILE* fp) { _fp = fp; }

void setColor(Color color)
{
fprintf(_fp, "\033[%d;%dm", color & Color.bright ? 1 : 0, 30 + (color & ~Color.bright));
}
@property FILE* fp() { return _fp; }

void resetColor()
{
fputs("\033[m", _fp);
}
void setColorBright(bool bright)
{
fprintf(_fp, "\033[%dm", bright);
}
else

void setColor(Color color)
{
@property FILE* fp() { assert(0); }
fprintf(_fp, "\033[%d;%dm", color & Color.bright ? 1 : 0, 30 + (color & ~Color.bright));
}

static Console* create(FILE* fp)
{
return null;
}
void resetColor()
{
fputs("\033[m", _fp);
}
}

void setColorBright(bool bright)
{
assert(0);
}
/**
Tries to detect whether DMD has been invoked from a terminal.
Returns: `true` if a terminal has been detected, `false` otherwise
*/
bool detectTerminal() nothrow
{
version (Posix)
{
import core.stdc.stdlib : getenv;
const(char)* term = getenv("TERM");
import core.stdc.string : strcmp;
return isatty(STDERR_FILENO) && term && term[0] && strcmp(term, "dumb") != 0;
}
else version (Windows)
{
auto h = WindowsConsole.getStdHandle(stderr);
if (h is null)
return false;

void setColor(Color color)
{
assert(0);
}
CONSOLE_SCREEN_BUFFER_INFO sbi;
return GetConsoleScreenBufferInfo(h, &sbi) != 0;
}
}

void resetColor()
{
assert(0);
}
/**
* Creates an instance of Console connected to stream fp.
* Params:
* fp = io stream
* Returns:
* reference to created Console
*/
Console createConsole(FILE* fp) nothrow
{
version (Windows)
{
if (auto c = WindowsConsole.create(fp))
return c;
}

return new ANSIConsole(fp);
}
Loading