Add support for linking against a curses library when available and

using it to detect whether or not a terminal supports colors. This
replaces a particularly egregious hack that merely compared the TERM
environment variable to "dumb". That doesn't really translate to
a reasonable experience for users that have actually ensured their
terminal's capabilities are accurately reflected.

This makes testing a terminal for color support somewhat more expensive,
but it is called very rarely anyways. The important fast path when the
output is being piped somewhere is already in place.

The global lock may seem excessive, but the spec for calling into curses
is *terrible*. The whole library is terrible, and I spent quite a bit of
time looking for a better way of doing this before convincing myself
that this was the fundamentally correct way to behave. The damage of the
curses library is very narrowly confined, and we continue to use raw
escape codes for actually manipulating the colors which is a much sane
system than directly using curses here (IMO).

If this causes trouble for folks, please let me know. I've tested it on
Linux and will watch the bots carefully. I've also worked to account for
the variances of curses interfaces that I could finde documentation for,
but that may not have been sufficient.

git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@187874 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Chandler Carruth 2013-08-07 08:47:36 +00:00
parent f76e118cf8
commit f7364d5833
9 changed files with 432 additions and 11 deletions

View File

@ -123,6 +123,8 @@ set(FFI_INCLUDE_DIR "" CACHE PATH "Additional directory, where CMake should sear
set(LLVM_TARGET_ARCH "host"
CACHE STRING "Set target to use for LLVM JIT or use \"host\" for automatic detection.")
option(LLVM_ENABLE_CURSES "Use curses to detect terminal info if available." ON)
option(LLVM_ENABLE_THREADS "Use threads if available." ON)
option(LLVM_ENABLE_ZLIB "Use zlib for compression/decompression if available." ON)

View File

@ -1072,6 +1072,17 @@ AC_ARG_WITH(bug-report-url,
AC_DEFINE_UNQUOTED(BUG_REPORT_URL,"$withval",
[Bug report URL.])
dnl --enable-curses: check whether the user wants to control use of curses:
AC_ARG_ENABLE(curses,AS_HELP_STRING(
[--enable-curses],
[Use curses for querying terminal infomation if available (default is YES)]),
[case "$enableval" in
yes) llvm_cv_enable_curses="yes" ;;
no) llvm_cv_enable_curses="no" ;;
*) AC_MSG_ERROR([Invalid setting for --enable-curses. Use "yes" or "no"]) ;;
esac],
llvm_cv_enable_curses="yes")
dnl --enable-libffi : check whether the user wants to turn off libffi:
AC_ARG_ENABLE(libffi,AS_HELP_STRING(
--enable-libffi,[Check for the presence of libffi (default is NO)]),
@ -1378,6 +1389,14 @@ dnl macros to detect whether clock_gettime is available, this just finds the
dnl right libraries to link with.
AC_SEARCH_LIBS(clock_gettime,rt)
dnl The curses library is optional; used for querying terminal info
if test "$llvm_cv_enable_curses" = "yes" ; then
dnl We need the has_color functionality in curses for it to be useful.
AC_SEARCH_LIBS(has_colors,curses ncurses ncursesw,
AC_DEFINE([HAVE_CURSES],[1],
[Define if curses provides the has_color() function on this platform.]))
fi
dnl libffi is optional; used to call external functions from the interpreter
if test "$llvm_cv_enable_libffi" = "yes" ; then
AC_SEARCH_LIBS(ffi_call,ffi,AC_DEFINE([HAVE_FFI_CALL],[1],
@ -1554,6 +1573,11 @@ else
AC_SUBST(HAVE_LIBZ, 0)
fi
dnl Try to find a suitable curses header.
if test "$llvm_cv_enable_curses" = "yes" ; then
AC_CHECK_HEADERS([curses.h ncurses.h ncursesw.h ncurses/curses.h ncursesw/curses.h])
fi
dnl Try to find ffi.h.
if test "$llvm_cv_enable_libffi" = "yes" ; then
AC_CHECK_HEADERS([ffi.h ffi/ffi.h])

View File

@ -74,6 +74,12 @@ check_symbol_exists(FE_INEXACT "fenv.h" HAVE_DECL_FE_INEXACT)
check_include_file(mach/mach.h HAVE_MACH_MACH_H)
check_include_file(mach-o/dyld.h HAVE_MACH_O_DYLD_H)
check_include_file(curses.h HAVE_CURSES_H)
check_include_file(ncurses.h HAVE_NCURSES_H)
check_include_file(ncursesw.h HAVE_NCURSESW_H)
check_include_file(ncurses/curses.h HAVE_NCURSES_CURSES_H)
check_include_file(ncursesw/curses.h HAVE_NCURSESW_CURSES_H)
# library checks
if( NOT PURE_WINDOWS )
check_library_exists(pthread pthread_create "" HAVE_LIBPTHREAD)
@ -97,6 +103,19 @@ if( NOT PURE_WINDOWS )
else()
set(HAVE_LIBZ 0)
endif()
if(LLVM_ENABLE_CURSES)
check_library_exists(curses has_colors "" HAVE_CURSES)
if(NOT HAVE_CURSES)
check_library_exists(ncurses has_colors "" HAVE_NCURSES)
set(HAVE_CURSES ${HAVE_NCURSES})
if(NOT HAVE_CURSES)
check_library_exists(ncursesw has_colors "" HAVE_NCURSESW)
set(HAVE_CURSES ${HAVE_NCURSESW})
endif()
endif()
else()
set(HAVE_CURSES 0)
endif()
endif()
# function checks

View File

@ -10,6 +10,15 @@ function(get_system_libs return_var)
if( HAVE_LIBDL )
set(system_libs ${system_libs} ${CMAKE_DL_LIBS})
endif()
if(LLVM_ENABLE_CURSES)
if(HAVE_NCURSESW)
set(system_libs ${system_libs} ncursesw)
elseif(HAVE_NCURSES)
set(system_libs ${system_libs} ncurses)
elseif(HAVE_CURSES)
set(system_libs ${system_libs} curses)
endif()
endif()
if( LLVM_ENABLE_THREADS AND HAVE_LIBPTHREAD )
set(system_libs ${system_libs} pthread)
endif()

View File

@ -20,6 +20,8 @@ set(TARGET_TRIPLE "@TARGET_TRIPLE@")
set(LLVM_TOOLS_BINARY_DIR @LLVM_TOOLS_BINARY_DIR@)
set(LLVM_ENABLE_CURSES @LLVM_ENABLE_CURSES@)
set(LLVM_ENABLE_THREADS @LLVM_ENABLE_THREADS@)
set(LLVM_ENABLE_ZLIB @LLVM_ENABLE_ZLIB@)

300
configure vendored
View File

@ -1453,6 +1453,8 @@ Optional Features:
target1,target2,... (default=disable)
--enable-bindings Build specific language bindings:
all,auto,none,{binding-name} (default=auto)
--enable-curses Use curses for querying terminal infomation if
available (default is YES)
--enable-libffi Check for the presence of libffi (default is NO)
--enable-ltdl-install install libltdl
@ -6004,6 +6006,20 @@ cat >>confdefs.h <<_ACEOF
_ACEOF
# Check whether --enable-curses was given.
if test "${enable_curses+set}" = set; then
enableval=$enable_curses; case "$enableval" in
yes) llvm_cv_enable_curses="yes" ;;
no) llvm_cv_enable_curses="no" ;;
*) { { echo "$as_me:$LINENO: error: Invalid setting for --enable-curses. Use \"yes\" or \"no\"" >&5
echo "$as_me: error: Invalid setting for --enable-curses. Use \"yes\" or \"no\"" >&2;}
{ (exit 1); exit 1; }; } ;;
esac
else
llvm_cv_enable_curses="yes"
fi
# Check whether --enable-libffi was given.
if test "${enable_libffi+set}" = set; then
enableval=$enable_libffi; case "$enableval" in
@ -10545,7 +10561,7 @@ else
lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2
lt_status=$lt_dlunknown
cat > conftest.$ac_ext <<EOF
#line 10548 "configure"
#line 10564 "configure"
#include "confdefs.h"
#if HAVE_DLFCN_H
@ -12252,6 +12268,112 @@ if test "$ac_res" != no; then
fi
if test "$llvm_cv_enable_curses" = "yes" ; then
{ echo "$as_me:$LINENO: checking for library containing has_colors" >&5
echo $ECHO_N "checking for library containing has_colors... $ECHO_C" >&6; }
if test "${ac_cv_search_has_colors+set}" = set; then
echo $ECHO_N "(cached) $ECHO_C" >&6
else
ac_func_search_save_LIBS=$LIBS
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
/* Override any GCC internal prototype to avoid an error.
Use char because int might match the return type of a GCC
builtin and then its argument prototype would still apply. */
#ifdef __cplusplus
extern "C"
#endif
char has_colors ();
int
main ()
{
return has_colors ();
;
return 0;
}
_ACEOF
for ac_lib in '' curses ncurses ncursesw; do
if test -z "$ac_lib"; then
ac_res="none required"
else
ac_res=-l$ac_lib
LIBS="-l$ac_lib $ac_func_search_save_LIBS"
fi
rm -f conftest.$ac_objext conftest$ac_exeext
if { (ac_try="$ac_link"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_link") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } &&
{ ac_try='test -z "$ac_c_werror_flag" || test ! -s conftest.err'
{ (case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_try") 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); }; } &&
{ ac_try='test -s conftest$ac_exeext'
{ (case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_try") 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); }; }; then
ac_cv_search_has_colors=$ac_res
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
fi
rm -f core conftest.err conftest.$ac_objext \
conftest$ac_exeext
if test "${ac_cv_search_has_colors+set}" = set; then
break
fi
done
if test "${ac_cv_search_has_colors+set}" = set; then
:
else
ac_cv_search_has_colors=no
fi
rm conftest.$ac_ext
LIBS=$ac_func_search_save_LIBS
fi
{ echo "$as_me:$LINENO: result: $ac_cv_search_has_colors" >&5
echo "${ECHO_T}$ac_cv_search_has_colors" >&6; }
ac_res=$ac_cv_search_has_colors
if test "$ac_res" != no; then
test "$ac_res" = "none required" || LIBS="$ac_res $LIBS"
cat >>confdefs.h <<\_ACEOF
#define HAVE_CURSES 1
_ACEOF
fi
fi
if test "$llvm_cv_enable_libffi" = "yes" ; then
{ echo "$as_me:$LINENO: checking for library containing ffi_call" >&5
echo $ECHO_N "checking for library containing ffi_call... $ECHO_C" >&6; }
@ -16421,6 +16543,182 @@ else
fi
if test "$llvm_cv_enable_curses" = "yes" ; then
for ac_header in curses.h ncurses.h ncursesw.h ncurses/curses.h ncursesw/curses.h
do
as_ac_Header=`echo "ac_cv_header_$ac_header" | $as_tr_sh`
if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then
{ echo "$as_me:$LINENO: checking for $ac_header" >&5
echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; }
if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then
echo $ECHO_N "(cached) $ECHO_C" >&6
fi
ac_res=`eval echo '${'$as_ac_Header'}'`
{ echo "$as_me:$LINENO: result: $ac_res" >&5
echo "${ECHO_T}$ac_res" >&6; }
else
# Is the header compilable?
{ echo "$as_me:$LINENO: checking $ac_header usability" >&5
echo $ECHO_N "checking $ac_header usability... $ECHO_C" >&6; }
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
$ac_includes_default
#include <$ac_header>
_ACEOF
rm -f conftest.$ac_objext
if { (ac_try="$ac_compile"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_compile") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } &&
{ ac_try='test -z "$ac_c_werror_flag" || test ! -s conftest.err'
{ (case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_try") 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); }; } &&
{ ac_try='test -s conftest.$ac_objext'
{ (case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_try") 2>&5
ac_status=$?
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); }; }; then
ac_header_compiler=yes
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_header_compiler=no
fi
rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext
{ echo "$as_me:$LINENO: result: $ac_header_compiler" >&5
echo "${ECHO_T}$ac_header_compiler" >&6; }
# Is the header present?
{ echo "$as_me:$LINENO: checking $ac_header presence" >&5
echo $ECHO_N "checking $ac_header presence... $ECHO_C" >&6; }
cat >conftest.$ac_ext <<_ACEOF
/* confdefs.h. */
_ACEOF
cat confdefs.h >>conftest.$ac_ext
cat >>conftest.$ac_ext <<_ACEOF
/* end confdefs.h. */
#include <$ac_header>
_ACEOF
if { (ac_try="$ac_cpp conftest.$ac_ext"
case "(($ac_try" in
*\"* | *\`* | *\\*) ac_try_echo=\$ac_try;;
*) ac_try_echo=$ac_try;;
esac
eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5
(eval "$ac_cpp conftest.$ac_ext") 2>conftest.er1
ac_status=$?
grep -v '^ *+' conftest.er1 >conftest.err
rm -f conftest.er1
cat conftest.err >&5
echo "$as_me:$LINENO: \$? = $ac_status" >&5
(exit $ac_status); } >/dev/null; then
if test -s conftest.err; then
ac_cpp_err=$ac_c_preproc_warn_flag
ac_cpp_err=$ac_cpp_err$ac_c_werror_flag
else
ac_cpp_err=
fi
else
ac_cpp_err=yes
fi
if test -z "$ac_cpp_err"; then
ac_header_preproc=yes
else
echo "$as_me: failed program was:" >&5
sed 's/^/| /' conftest.$ac_ext >&5
ac_header_preproc=no
fi
rm -f conftest.err conftest.$ac_ext
{ echo "$as_me:$LINENO: result: $ac_header_preproc" >&5
echo "${ECHO_T}$ac_header_preproc" >&6; }
# So? What about this header?
case $ac_header_compiler:$ac_header_preproc:$ac_c_preproc_warn_flag in
yes:no: )
{ echo "$as_me:$LINENO: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&5
echo "$as_me: WARNING: $ac_header: accepted by the compiler, rejected by the preprocessor!" >&2;}
{ echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the compiler's result" >&5
echo "$as_me: WARNING: $ac_header: proceeding with the compiler's result" >&2;}
ac_header_preproc=yes
;;
no:yes:* )
{ echo "$as_me:$LINENO: WARNING: $ac_header: present but cannot be compiled" >&5
echo "$as_me: WARNING: $ac_header: present but cannot be compiled" >&2;}
{ echo "$as_me:$LINENO: WARNING: $ac_header: check for missing prerequisite headers?" >&5
echo "$as_me: WARNING: $ac_header: check for missing prerequisite headers?" >&2;}
{ echo "$as_me:$LINENO: WARNING: $ac_header: see the Autoconf documentation" >&5
echo "$as_me: WARNING: $ac_header: see the Autoconf documentation" >&2;}
{ echo "$as_me:$LINENO: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&5
echo "$as_me: WARNING: $ac_header: section \"Present But Cannot Be Compiled\"" >&2;}
{ echo "$as_me:$LINENO: WARNING: $ac_header: proceeding with the preprocessor's result" >&5
echo "$as_me: WARNING: $ac_header: proceeding with the preprocessor's result" >&2;}
{ echo "$as_me:$LINENO: WARNING: $ac_header: in the future, the compiler will take precedence" >&5
echo "$as_me: WARNING: $ac_header: in the future, the compiler will take precedence" >&2;}
( cat <<\_ASBOX
## ------------------------------------ ##
## Report this to http://llvm.org/bugs/ ##
## ------------------------------------ ##
_ASBOX
) | sed "s/^/$as_me: WARNING: /" >&2
;;
esac
{ echo "$as_me:$LINENO: checking for $ac_header" >&5
echo $ECHO_N "checking for $ac_header... $ECHO_C" >&6; }
if { as_var=$as_ac_Header; eval "test \"\${$as_var+set}\" = set"; }; then
echo $ECHO_N "(cached) $ECHO_C" >&6
else
eval "$as_ac_Header=\$ac_header_preproc"
fi
ac_res=`eval echo '${'$as_ac_Header'}'`
{ echo "$as_me:$LINENO: result: $ac_res" >&5
echo "${ECHO_T}$ac_res" >&6; }
fi
if test `eval echo '${'$as_ac_Header'}'` = yes; then
cat >>confdefs.h <<_ACEOF
#define `echo "HAVE_$ac_header" | $as_tr_cpp` 1
_ACEOF
fi
done
fi
if test "$llvm_cv_enable_libffi" = "yes" ; then

View File

@ -48,6 +48,12 @@
/* Define to 1 if you have the `closedir' function. */
#cmakedefine HAVE_CLOSEDIR ${HAVE_CLOSEDIR}
/* Define if curses provides the has_color() function on this platform. */
#cmakedefine HAVE_CURSES
/* Define to 1 if you have the <curses.h> header file. */
#cmakedefine HAVE_CURSES_H
/* Define to 1 if you have the <cxxabi.h> header file. */
#cmakedefine HAVE_CXXABI_H ${HAVE_CXXABI_H}
@ -256,6 +262,18 @@
/* Define if mmap() can map files into memory */
#undef HAVE_MMAP_FILE
/* Define to 1 if you have the <ncursesw/curses.h> header file. */
#cmakedefine HAVE_NCURSESW_CURSES_H
/* Define to 1 if you have the <ncursesw.h> header file. */
#cmakedefine HAVE_NCURSESW_H
/* Define to 1 if you have the <ncurses/curses.h> header file. */
#cmakedefine HAVE_NCURSES_CURSES_H
/* Define to 1 if you have the <ncurses.h> header file. */
#cmakedefine HAVE_NCURSES_H
/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
#cmakedefine HAVE_NDIR_H ${HAVE_NDIR_H}

View File

@ -69,6 +69,12 @@
/* can use __crashreporter_info__ */
#undef HAVE_CRASHREPORTER_INFO
/* Define if curses provides the has_color() function on this platform. */
#undef HAVE_CURSES
/* Define to 1 if you have the <curses.h> header file. */
#undef HAVE_CURSES_H
/* Define to 1 if you have the <cxxabi.h> header file. */
#undef HAVE_CXXABI_H
@ -282,6 +288,18 @@
/* Define if mmap() can map files into memory */
#undef HAVE_MMAP_FILE
/* Define to 1 if you have the <ncursesw/curses.h> header file. */
#undef HAVE_NCURSESW_CURSES_H
/* Define to 1 if you have the <ncursesw.h> header file. */
#undef HAVE_NCURSESW_H
/* Define to 1 if you have the <ncurses/curses.h> header file. */
#undef HAVE_NCURSES_CURSES_H
/* Define to 1 if you have the <ncurses.h> header file. */
#undef HAVE_NCURSES_H
/* Define to 1 if you have the <ndir.h> header file, and it defines `DIR'. */
#undef HAVE_NDIR_H

View File

@ -13,6 +13,8 @@
#include "Unix.h"
#include "llvm/ADT/Hashing.h"
#include "llvm/Support/Mutex.h"
#include "llvm/Support/MutexGuard.h"
#include "llvm/Support/TimeValue.h"
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
@ -36,6 +38,25 @@
# include <termios.h>
#endif
// See if we can use curses to detect information about a terminal when
// connected to one.
#ifdef HAVE_CURSES
# if defined(HAVE_CURSES_H)
# include <curses.h>
# elif defined(HAVE_NCURSES_H)
# include <ncurses.h>
# elif defined(HAVE_NCURSESW_H)
# include <ncursesw.h>
# elif defined(HAVE_NCURSES_CURSES_H)
# include <ncurses/curses.h>
# elif defined(HAVE_NCURSESW_CURSES_H)
# include <ncursesw/curses.h>
# else
# error Have a curses library but unable to find a curses header!
# endif
# include <term.h>
#endif
//===----------------------------------------------------------------------===//
//=== WARNING: Implementation here must contain only generic UNIX code that
//=== is guaranteed to work on *all* UNIX variants.
@ -245,22 +266,32 @@ unsigned Process::StandardErrColumns() {
return getColumns(2);
}
static bool terminalHasColors() {
if (const char *term = std::getenv("TERM")) {
// Most modern terminals support ANSI escape sequences for colors.
// We could check terminfo, or have a list of known terms that support
// colors, but that would be overkill.
// The user can always ask for no colors by setting TERM to dumb, or
// using a commandline flag.
return strcmp(term, "dumb") != 0;
}
static bool terminalHasColors(int fd) {
#ifdef HAVE_CURSES
// First, acquire a global lock because the curses C routines are thread
// hostile.
static sys::Mutex M;
MutexGuard G(M);
int errret = 0;
if (setupterm((char *)0, fd, &errret) != OK)
// Regardless of why, if we can't get terminfo, we shouldn't try to print
// colors.
return false;
// Test whether the terminal as set up supports color output.
if (has_colors() == TRUE)
return true;
#endif
// Otherwise, be conservative.
return false;
}
bool Process::FileDescriptorHasColors(int fd) {
// A file descriptor has colors if it is displayed and the terminal has
// colors.
return FileDescriptorIsDisplayed(fd) && terminalHasColors();
return FileDescriptorIsDisplayed(fd) && terminalHasColors(fd);
}
bool Process::StandardOutHasColors() {