Index: configure.ac ================================================================== --- configure.ac +++ configure.ac @@ -1560,10 +1560,11 @@ AC_SUBST(OF_PROCESS_M, "OFProcess.m") AC_DEFINE(OF_HAVE_PROCESSES, 1, [Whether we have processes]) ]) AC_CHECK_HEADERS_ONCE([complex.h sys/ioctl.h sys/ttycom.h]) +AC_CHECK_FUNCS(isatty) AC_CHECK_FUNC(pledge, [ AC_DEFINE(OF_HAVE_PLEDGE, 1, [Whether we have pledge()]) ]) Index: src/OFStdIOStream.h ================================================================== --- src/OFStdIOStream.h +++ src/OFStdIOStream.h @@ -24,10 +24,12 @@ OF_ASSUME_NONNULL_BEGIN /*! @file */ +@class OFColor; + /*! * @class OFStdIOStream OFStdIOStream.h ObjFW/OFStdIOStream.h * * @brief A class for providing standard input, output and error as OFStream. * @@ -62,10 +64,31 @@ * number of rows could not be queried. */ @property (readonly, nonatomic) int rows; - (instancetype)init OF_UNAVAILABLE; + +/*! + * @brief Sets the foreground color on the underlying terminal. Does nothing if + * there is no underlying terminal or colors are unsupported. + * + * @param color The foreground color to set + */ +- (void)setForegroundColor: (OFColor *)color; + +/*! + * @brief Sets the background color on the underlying terminal. Does nothing if + * there is no underlying terminal or colors are unsupported. + * + * @param color The background color to set + */ +- (void)setBackgroundColor: (OFColor *)color; + +/*! + * @brief Resets forward and background color. + */ +- (void)resetColor; @end #ifdef __cplusplus extern "C" { #endif Index: src/OFStdIOStream.m ================================================================== --- src/OFStdIOStream.m +++ src/OFStdIOStream.m @@ -28,10 +28,11 @@ # include #endif #import "OFStdIOStream.h" #import "OFStdIOStream+Private.h" +#import "OFColor.h" #import "OFDate.h" #import "OFApplication.h" #ifdef OF_WINDOWS # include "OFWin32ConsoleStdIOStream.h" #endif @@ -93,10 +94,51 @@ [of_stderr writeFormat: @"[%@.%03d %@(%d)] %@\n", dateString, date.microsecond / 1000, me, getpid(), msg]; objc_autoreleasePoolPop(pool); } + +#ifdef HAVE_ISATTY +static int +colorToANSI(OFColor *color) +{ + if ([color isEqual: [OFColor black]]) + return 30; + if ([color isEqual: [OFColor maroon]]) + return 31; + if ([color isEqual: [OFColor green]]) + return 32; + if ([color isEqual: [OFColor olive]]) + return 33; + if ([color isEqual: [OFColor navy]]) + return 34; + if ([color isEqual: [OFColor purple]]) + return 35; + if ([color isEqual: [OFColor teal]]) + return 36; + if ([color isEqual: [OFColor silver]]) + return 37; + if ([color isEqual: [OFColor grey]]) + return 90; + if ([color isEqual: [OFColor red]]) + return 91; + if ([color isEqual: [OFColor lime]]) + return 92; + if ([color isEqual: [OFColor yellow]]) + return 93; + if ([color isEqual: [OFColor blue]]) + return 94; + if ([color isEqual: [OFColor fuchsia]]) + return 95; + if ([color isEqual: [OFColor aqua]]) + return 96; + if ([color isEqual: [OFColor white]]) + return 97; + + return -1; +} +#endif @implementation OFStdIOStream #ifndef OF_WINDOWS + (void)load { @@ -368,6 +410,46 @@ return ws.ws_row; #else return -1; #endif } + +- (void)setForegroundColor: (OFColor *)color +{ +#ifdef HAVE_ISATTY + int code; + + if (!isatty(_fd)) + return; + + if ((code = colorToANSI(color)) == -1) + return; + + [self writeFormat: @"\033[%um", code]; +#endif +} + +- (void)setBackgroundColor: (OFColor *)color +{ +#ifdef HAVE_ISATTY + int code; + + if (!isatty(_fd)) + return; + + if ((code = colorToANSI(color)) == -1) + return; + + [self writeFormat: @"\033[%um", code + 10]; +#endif +} + +- (void)resetColor +{ +#ifdef HAVE_ISATTY + if (!isatty(_fd)) + return; + + [self writeString: @"\033[0m"]; +#endif +} @end