From e4b3d03da53ea8bc177f046ec311285697155f0b Mon Sep 17 00:00:00 2001 From: Matt Pharr Date: Thu, 19 Apr 2012 11:36:28 -0700 Subject: [PATCH] When available, use ANSI escapes to colorize diagnostic output. Issue #245. --- run_tests.py | 4 ++ util.cpp | 118 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 108 insertions(+), 14 deletions(-) diff --git a/run_tests.py b/run_tests.py index 724e1037..ce5e98f1 100755 --- a/run_tests.py +++ b/run_tests.py @@ -17,6 +17,10 @@ import shlex import platform import tempfile +# disable fancy error/warning printing with ANSI colors, so grepping for error +# messages doesn't get confused +os.environ["TERM"] = "dumb" + # This script is affected by http://bugs.python.org/issue5261 on OSX 10.5 Leopard # git history has a workaround for that issue. diff --git a/util.cpp b/util.cpp index 7057755b..92c1bac1 100644 --- a/util.cpp +++ b/util.cpp @@ -90,6 +90,49 @@ lTerminalWidth() { } +static bool +lHaveANSIColors() { + static bool r = (getenv("TERM") != NULL && + strcmp(getenv("TERM"), "dumb") != 0); + return r; +} + + +static const char * +lStartBold() { + if (lHaveANSIColors()) + return "\e[1m"; + else + return ""; +} + + +static const char * +lStartRed() { + if (lHaveANSIColors()) + return "\e[31m"; + else + return ""; +} + + +static const char * +lStartBlue() { + if (lHaveANSIColors()) + return "\e[34m"; + else + return ""; +} + + +static const char * +lResetColor() { + if (lHaveANSIColors()) + return "\e[0m"; + else + return ""; +} + /** Given a pointer into a string, find the end of the current word and return a pointer to its last character. */ @@ -140,17 +183,43 @@ lPrintFileLineContext(SourcePos p) { fclose(f); } + +/** Counts the number of characters into the buf at which the numColons + colon character is found. Skips over ANSI escape sequences and doesn't + include their characters in the final count. + */ +static int +lFindIndent(int numColons, const char *buf) { + int indent = 0; + while (*buf != '\0') { + if (*buf == '\e') { + while (*buf != '\0' && *buf != 'm') + ++buf; + if (*buf == 'm') + ++buf; + } + else { + if (*buf == ':') { + if (--numColons == 0) + break; + } + ++indent; + ++buf; + } + } + return indent + 2; +} + + /** Print the given string to the given FILE, assuming the given output column width. Break words as needed to avoid words spilling past the last column. */ static void -lPrintWithWordBreaks(const char *buf, int columnWidth, FILE *out) { +lPrintWithWordBreaks(const char *buf, int indent, int columnWidth, FILE *out) { #ifdef ISPC_IS_WINDOWS fputs(buf, out); #else int column = 0; - Assert(strchr(buf, ':') != NULL); - int indent = strchr(buf, ':') - buf + 2; int width = std::max(40, columnWidth - 2); // Collect everything into a string and print it all at once at the end @@ -160,6 +229,15 @@ lPrintWithWordBreaks(const char *buf, int columnWidth, FILE *out) { const char *msgPos = buf; while (true) { + if (*msgPos == '\e') { + // handle ANSI color escape: copy it to the output buffer + // without charging for the characters it uses + do { + outStr.push_back(*msgPos++); + } while (*msgPos != '\0' && *msgPos != 'm'); + continue; + } + while (*msgPos != '\0' && isspace(*msgPos)) ++msgPos; if (*msgPos == '\0') @@ -171,8 +249,8 @@ lPrintWithWordBreaks(const char *buf, int columnWidth, FILE *out) { column = indent; outStr.push_back('\n'); // Indent to the same column as the ":" at the start of the - // message, unless doing so would be too far in. - for (int i = 0; i < std::min(16, indent); ++i) + // message. + for (int i = 0; i < indent; ++i) outStr.push_back(' '); } @@ -225,26 +303,37 @@ asprintf(char **sptr, const char *fmt, ...) @param args Arguments with values for format string % entries */ static void -lPrint(const char *type, SourcePos p, const char *fmt, va_list args) { +lPrint(const char *type, bool isError, SourcePos p, const char *fmt, + va_list args) { char *errorBuf, *formattedBuf; if (vasprintf(&errorBuf, fmt, args) == -1) { fprintf(stderr, "vasprintf() unable to allocate memory!\n"); abort(); } + + int indent = 0; if (p.first_line == 0) { // We don't have a valid SourcePos, so create a message without it - if (asprintf(&formattedBuf, "%s: %s\n", type, errorBuf) == -1) { + if (asprintf(&formattedBuf, "%s%s%s%s%s: %s%s\n", lStartBold(), + isError ? lStartRed() : lStartBlue(), type, + lResetColor(), lStartBold(), errorBuf, + lResetColor()) == -1) { fprintf(stderr, "asprintf() unable to allocate memory!\n"); exit(1); } + indent = lFindIndent(1, formattedBuf); } else { // Create an error message that includes the file and line number - if (asprintf(&formattedBuf, "%s:%d:%d: %s: %s\n", p.name, - p.first_line, p.first_column, type, errorBuf) == -1) { + if (asprintf(&formattedBuf, "%s%s:%d:%d: %s%s%s%s: %s%s\n", + lStartBold(), p.name, p.first_line, p.first_column, + isError ? lStartRed() : lStartBlue(), type, + lResetColor(), lStartBold(), errorBuf, + lResetColor()) == -1) { fprintf(stderr, "asprintf() unable to allocate memory!\n"); exit(1); } + indent = lFindIndent(3, formattedBuf); } // Now that we've done all that work, see if we've already printed the @@ -255,7 +344,7 @@ lPrint(const char *type, SourcePos p, const char *fmt, va_list args) { return; printed.insert(formattedBuf); - lPrintWithWordBreaks(formattedBuf, lTerminalWidth(), stderr); + lPrintWithWordBreaks(formattedBuf, indent, lTerminalWidth(), stderr); lPrintFileLineContext(p); free(errorBuf); @@ -271,7 +360,7 @@ Error(SourcePos p, const char *fmt, ...) { va_list args; va_start(args, fmt); - lPrint("Error", p, fmt, args); + lPrint("Error", true, p, fmt, args); va_end(args); } @@ -283,7 +372,7 @@ Debug(SourcePos p, const char *fmt, ...) { va_list args; va_start(args, fmt); - lPrint("Debug", p, fmt, args); + lPrint("Debug", false, p, fmt, args); va_end(args); } @@ -298,7 +387,8 @@ Warning(SourcePos p, const char *fmt, ...) { va_list args; va_start(args, fmt); - lPrint(g->warningsAsErrors ? "Error" : "Warning", p, fmt, args); + lPrint(g->warningsAsErrors ? "Error" : "Warning", g->warningsAsErrors, + p, fmt, args); va_end(args); } @@ -311,7 +401,7 @@ PerformanceWarning(SourcePos p, const char *fmt, ...) { va_list args; va_start(args, fmt); - lPrint("Performance Warning", p, fmt, args); + lPrint("Performance Warning", false, p, fmt, args); va_end(args); }