From b3093ecee63152032750ede294f52275c5d34928 Mon Sep 17 00:00:00 2001 From: Zach Anderson Date: Fri, 17 Mar 2017 12:35:36 -0700 Subject: [PATCH] [dart:io] Move Platform.ansiSupported to {Stdin,Stdout}.supportsAnsiEscapes On Windows, some Windows 10 builds support only ANSI output, but not input, so these need to be separated. I'm also improving the detection on Mac and Linux to avoid hardcoding the result. Instead, supportsAnsiEscapes will be true if isatty() and the TERM environment variable contains the string 'xterm'. related #28614 R=lrn@google.com Review-Url: https://codereview.chromium.org/2753233002 . --- CHANGELOG.md | 2 +- runtime/bin/io_natives.cc | 3 +- runtime/bin/platform.cc | 5 -- runtime/bin/platform.h | 2 - runtime/bin/platform_android.cc | 5 -- runtime/bin/platform_fuchsia.cc | 5 -- runtime/bin/platform_linux.cc | 5 -- runtime/bin/platform_macos.cc | 5 -- runtime/bin/platform_patch.dart | 2 - runtime/bin/platform_unsupported.cc | 6 --- runtime/bin/platform_win.cc | 20 ++------ runtime/bin/stdio.cc | 30 +++++++++++ runtime/bin/stdio.h | 3 ++ runtime/bin/stdio_android.cc | 21 ++++++++ runtime/bin/stdio_fuchsia.cc | 12 +++++ runtime/bin/stdio_linux.cc | 21 ++++++++ runtime/bin/stdio_macos.cc | 21 ++++++++ runtime/bin/stdio_patch.dart | 19 +++++++ runtime/bin/stdio_unsupported.cc | 12 +++++ runtime/bin/stdio_win.cc | 49 ++++++++++++++++++ .../_internal/js_runtime/lib/io_patch.dart | 15 ++++-- sdk/lib/io/platform.dart | 9 ---- sdk/lib/io/platform_impl.dart | 2 - sdk/lib/io/stdio.dart | 51 ++++++++++++++++++- tests/standalone/io/ansi_supported_test.dart | 32 ++++++++---- 25 files changed, 276 insertions(+), 81 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f63522d3eb1..997ae349008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,7 +14,7 @@ * `dart:io`: Added functions `File.lastAccessed`, `File.lastAccessedSync`, `File.setLastModified`, `File.setLastModifiedSync`, `File.setLastAccessed`, and `File.setLastAccessedSync`. -* `dart:io`: Added `Platform.ansiSupported`. +* `dart:io`: Added `{Stdin,Stdout}.supportsAnsiEscapes`. ### Dart VM * Calls to `print()` and `Stdout.write*()` now correctly print unicode diff --git a/runtime/bin/io_natives.cc b/runtime/bin/io_natives.cc index b39e44e9fb3..8f3c79a9a31 100644 --- a/runtime/bin/io_natives.cc +++ b/runtime/bin/io_natives.cc @@ -92,7 +92,6 @@ namespace bin { V(Platform_Environment, 0) \ V(Platform_ExecutableArguments, 0) \ V(Platform_GetVersion, 0) \ - V(Platform_AnsiSupported, 0) \ V(Process_Start, 11) \ V(Process_Wait, 5) \ V(Process_KillPid, 2) \ @@ -148,7 +147,9 @@ namespace bin { V(Stdin_SetEchoMode, 1) \ V(Stdin_GetLineMode, 0) \ V(Stdin_SetLineMode, 1) \ + V(Stdin_AnsiSupported, 0) \ V(Stdout_GetTerminalSize, 1) \ + V(Stdout_AnsiSupported, 1) \ V(StringToSystemEncoding, 1) \ V(SystemEncodingToString, 1) \ V(X509_Subject, 1) \ diff --git a/runtime/bin/platform.cc b/runtime/bin/platform.cc index dc1ebc459d3..e8b7849147f 100644 --- a/runtime/bin/platform.cc +++ b/runtime/bin/platform.cc @@ -109,11 +109,6 @@ void FUNCTION_NAME(Platform_GetVersion)(Dart_NativeArguments args) { Dart_SetReturnValue(args, Dart_NewStringFromCString(Dart_VersionString())); } - -void FUNCTION_NAME(Platform_AnsiSupported)(Dart_NativeArguments args) { - Dart_SetBooleanReturnValue(args, Platform::AnsiSupported()); -} - } // namespace bin } // namespace dart diff --git a/runtime/bin/platform.h b/runtime/bin/platform.h index f5906d2ca00..9dc7e7a21a6 100644 --- a/runtime/bin/platform.h +++ b/runtime/bin/platform.h @@ -83,8 +83,6 @@ class Platform { static int GetScriptIndex() { return script_index_; } static char** GetArgv() { return argv_; } - static bool AnsiSupported(); - static DART_NORETURN void Exit(int exit_code); private: diff --git a/runtime/bin/platform_android.cc b/runtime/bin/platform_android.cc index 10371c60f88..b81ce71e195 100644 --- a/runtime/bin/platform_android.cc +++ b/runtime/bin/platform_android.cc @@ -85,11 +85,6 @@ const char* Platform::ResolveExecutablePath() { } -bool Platform::AnsiSupported() { - return true; -} - - void Platform::Exit(int exit_code) { exit(exit_code); } diff --git a/runtime/bin/platform_fuchsia.cc b/runtime/bin/platform_fuchsia.cc index ea3115a11ac..3b1a1c190ce 100644 --- a/runtime/bin/platform_fuchsia.cc +++ b/runtime/bin/platform_fuchsia.cc @@ -109,11 +109,6 @@ const char* Platform::ResolveExecutablePath() { } -bool Platform::AnsiSupported() { - return true; -} - - void Platform::Exit(int exit_code) { exit(exit_code); } diff --git a/runtime/bin/platform_linux.cc b/runtime/bin/platform_linux.cc index 810dec4c457..b68f4921c7e 100644 --- a/runtime/bin/platform_linux.cc +++ b/runtime/bin/platform_linux.cc @@ -110,11 +110,6 @@ const char* Platform::ResolveExecutablePath() { } -bool Platform::AnsiSupported() { - return true; -} - - void Platform::Exit(int exit_code) { exit(exit_code); } diff --git a/runtime/bin/platform_macos.cc b/runtime/bin/platform_macos.cc index 787069a4978..0088956c210 100644 --- a/runtime/bin/platform_macos.cc +++ b/runtime/bin/platform_macos.cc @@ -154,11 +154,6 @@ const char* Platform::ResolveExecutablePath() { } -bool Platform::AnsiSupported() { - return true; -} - - void Platform::Exit(int exit_code) { exit(exit_code); } diff --git a/runtime/bin/platform_patch.dart b/runtime/bin/platform_patch.dart index 5896fbdac24..c4b348114e2 100644 --- a/runtime/bin/platform_patch.dart +++ b/runtime/bin/platform_patch.dart @@ -21,8 +21,6 @@ native "Platform_ExecutableArguments"; @patch static String _version() native "Platform_GetVersion"; - @patch static bool _ansiSupported() - native "Platform_AnsiSupported"; @patch static String _packageRoot() => VMLibraryHooks.packageRootString; diff --git a/runtime/bin/platform_unsupported.cc b/runtime/bin/platform_unsupported.cc index 520d9f2ed7b..64f62ef7e9b 100644 --- a/runtime/bin/platform_unsupported.cc +++ b/runtime/bin/platform_unsupported.cc @@ -64,12 +64,6 @@ void FUNCTION_NAME(Platform_GetVersion)(Dart_NativeArguments args) { "Platform is not supported on this platform")); } - -void FUNCTION_NAME(Platform_AnsiSupported)(Dart_NativeArguments args) { - Dart_ThrowException(DartUtils::NewInternalError( - "Platform is not supported on this platform")); -} - } // namespace bin } // namespace dart diff --git a/runtime/bin/platform_win.cc b/runtime/bin/platform_win.cc index 14f1a1f2458..53293024268 100644 --- a/runtime/bin/platform_win.cc +++ b/runtime/bin/platform_win.cc @@ -83,23 +83,18 @@ class PlatformWin { SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8); - ansi_supported_ = true; + // Try to set the bits for ANSI support, but swallow any failures. HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE); DWORD out_mode; if ((out != INVALID_HANDLE_VALUE) && GetConsoleMode(out, &out_mode)) { const DWORD request = out_mode | ENABLE_VIRTUAL_TERMINAL_PROCESSING; - ansi_supported_ = ansi_supported_ && SetConsoleMode(out, request); - } else { - ansi_supported_ = false; + SetConsoleMode(out, request); } - HANDLE in = GetStdHandle(STD_INPUT_HANDLE); DWORD in_mode; if ((in != INVALID_HANDLE_VALUE) && GetConsoleMode(in, &in_mode)) { const DWORD request = in_mode | ENABLE_VIRTUAL_TERMINAL_INPUT; - ansi_supported_ = ansi_supported_ && SetConsoleMode(in, request); - } else { - ansi_supported_ = false; + SetConsoleMode(in, request); } } @@ -108,13 +103,10 @@ class PlatformWin { RestoreConsoleLocked(); } - static bool ansi_supported() { return ansi_supported_; } - private: static Mutex* platform_win_mutex_; static int saved_output_cp_; static int saved_input_cp_; - static bool ansi_supported_; static void RestoreConsoleLocked() { // STD_OUTPUT_HANDLE and STD_INPUT_HANDLE may have been closed or @@ -179,7 +171,6 @@ class PlatformWin { int PlatformWin::saved_output_cp_ = -1; int PlatformWin::saved_input_cp_ = -1; Mutex* PlatformWin::platform_win_mutex_ = NULL; -bool PlatformWin::ansi_supported_ = false; bool Platform::Initialize() { PlatformWin::InitOnce(); @@ -276,11 +267,6 @@ const char* Platform::ResolveExecutablePath() { } -bool Platform::AnsiSupported() { - return PlatformWin::ansi_supported(); -} - - void Platform::Exit(int exit_code) { // TODO(zra): Remove once VM shuts down cleanly. ::dart::private_flag_windows_run_tls_destructors = false; diff --git a/runtime/bin/stdio.cc b/runtime/bin/stdio.cc index aa1c7c2288c..fec14b1c170 100644 --- a/runtime/bin/stdio.cc +++ b/runtime/bin/stdio.cc @@ -69,6 +69,16 @@ void FUNCTION_NAME(Stdin_SetLineMode)(Dart_NativeArguments args) { } +void FUNCTION_NAME(Stdin_AnsiSupported)(Dart_NativeArguments args) { + bool supported = false; + if (Stdin::AnsiSupported(&supported)) { + Dart_SetBooleanReturnValue(args, supported); + } else { + Dart_SetReturnValue(args, DartUtils::NewDartOSError()); + } +} + + void FUNCTION_NAME(Stdout_GetTerminalSize)(Dart_NativeArguments args) { if (!Dart_IsInteger(Dart_GetNativeArgument(args, 0))) { OSError os_error(-1, "Invalid argument", OSError::kUnknown); @@ -92,6 +102,26 @@ void FUNCTION_NAME(Stdout_GetTerminalSize)(Dart_NativeArguments args) { } } + +void FUNCTION_NAME(Stdout_AnsiSupported)(Dart_NativeArguments args) { + if (!Dart_IsInteger(Dart_GetNativeArgument(args, 0))) { + OSError os_error(-1, "Invalid argument", OSError::kUnknown); + Dart_SetReturnValue(args, DartUtils::NewDartOSError(&os_error)); + return; + } + intptr_t fd = DartUtils::GetIntptrValue(Dart_GetNativeArgument(args, 0)); + if ((fd != 1) && (fd != 2)) { + Dart_SetReturnValue(args, Dart_NewApiError("Terminal fd must be 1 or 2")); + return; + } + bool supported = false; + if (Stdout::AnsiSupported(fd, &supported)) { + Dart_SetBooleanReturnValue(args, supported); + } else { + Dart_SetReturnValue(args, DartUtils::NewDartOSError()); + } +} + } // namespace bin } // namespace dart diff --git a/runtime/bin/stdio.h b/runtime/bin/stdio.h index 56b591499b6..e55c317aa1d 100644 --- a/runtime/bin/stdio.h +++ b/runtime/bin/stdio.h @@ -27,6 +27,8 @@ class Stdin { static bool GetLineMode(bool* enabled); static bool SetLineMode(bool enabled); + static bool AnsiSupported(bool* supported); + private: DISALLOW_ALLOCATION(); DISALLOW_IMPLICIT_CONSTRUCTORS(Stdin); @@ -36,6 +38,7 @@ class Stdin { class Stdout { public: static bool GetTerminalSize(intptr_t fd, int size[2]); + static bool AnsiSupported(intptr_t fd, bool* supported); private: DISALLOW_ALLOCATION(); diff --git a/runtime/bin/stdio_android.cc b/runtime/bin/stdio_android.cc index 8b264f81496..e6b62501dab 100644 --- a/runtime/bin/stdio_android.cc +++ b/runtime/bin/stdio_android.cc @@ -83,6 +83,21 @@ bool Stdin::SetLineMode(bool enabled) { } +static bool TermHasXTerm() { + const char* term = getenv("TERM"); + if (term == NULL) { + return false; + } + return strstr(term, "xterm") != NULL; +} + + +bool Stdin::AnsiSupported(bool* supported) { + *supported = isatty(STDIN_FILENO) && TermHasXTerm(); + return true; +} + + bool Stdout::GetTerminalSize(intptr_t fd, int size[2]) { struct winsize w; int status = NO_RETRY_EXPECTED(ioctl(fd, TIOCGWINSZ, &w)); @@ -94,6 +109,12 @@ bool Stdout::GetTerminalSize(intptr_t fd, int size[2]) { return false; } + +bool Stdout::AnsiSupported(intptr_t fd, bool* supported) { + *supported = isatty(fd) && TermHasXTerm(); + return true; +} + } // namespace bin } // namespace dart diff --git a/runtime/bin/stdio_fuchsia.cc b/runtime/bin/stdio_fuchsia.cc index 07febe1df26..c36e06f6927 100644 --- a/runtime/bin/stdio_fuchsia.cc +++ b/runtime/bin/stdio_fuchsia.cc @@ -42,11 +42,23 @@ bool Stdin::SetLineMode(bool enabled) { } +bool Stdin::AnsiSupported(bool* supported) { + UNIMPLEMENTED(); + return false; +} + + bool Stdout::GetTerminalSize(intptr_t fd, int size[2]) { UNIMPLEMENTED(); return false; } + +bool Stdout::AnsiSupported(intptr_t fd, bool* supported) { + UNIMPLEMENTED(); + return false; +} + } // namespace bin } // namespace dart diff --git a/runtime/bin/stdio_linux.cc b/runtime/bin/stdio_linux.cc index 8199f201d71..d3aab28ae31 100644 --- a/runtime/bin/stdio_linux.cc +++ b/runtime/bin/stdio_linux.cc @@ -83,6 +83,21 @@ bool Stdin::SetLineMode(bool enabled) { } +static bool TermHasXTerm() { + const char* term = getenv("TERM"); + if (term == NULL) { + return false; + } + return strstr(term, "xterm") != NULL; +} + + +bool Stdin::AnsiSupported(bool* supported) { + *supported = isatty(STDIN_FILENO) && TermHasXTerm(); + return true; +} + + bool Stdout::GetTerminalSize(intptr_t fd, int size[2]) { struct winsize w; int status = NO_RETRY_EXPECTED(ioctl(fd, TIOCGWINSZ, &w)); @@ -94,6 +109,12 @@ bool Stdout::GetTerminalSize(intptr_t fd, int size[2]) { return false; } + +bool Stdout::AnsiSupported(intptr_t fd, bool* supported) { + *supported = isatty(fd) && TermHasXTerm(); + return true; +} + } // namespace bin } // namespace dart diff --git a/runtime/bin/stdio_macos.cc b/runtime/bin/stdio_macos.cc index 5ed3d7e4e11..37ac894583d 100644 --- a/runtime/bin/stdio_macos.cc +++ b/runtime/bin/stdio_macos.cc @@ -83,6 +83,21 @@ bool Stdin::SetLineMode(bool enabled) { } +static bool TermHasXTerm() { + const char* term = getenv("TERM"); + if (term == NULL) { + return false; + } + return strstr(term, "xterm") != NULL; +} + + +bool Stdin::AnsiSupported(bool* supported) { + *supported = isatty(STDIN_FILENO) && TermHasXTerm(); + return true; +} + + bool Stdout::GetTerminalSize(intptr_t fd, int size[2]) { struct winsize w; int status = NO_RETRY_EXPECTED(ioctl(fd, TIOCGWINSZ, &w)); @@ -94,6 +109,12 @@ bool Stdout::GetTerminalSize(intptr_t fd, int size[2]) { return false; } + +bool Stdout::AnsiSupported(intptr_t fd, bool* supported) { + *supported = isatty(fd) && TermHasXTerm(); + return true; +} + } // namespace bin } // namespace dart diff --git a/runtime/bin/stdio_patch.dart b/runtime/bin/stdio_patch.dart index e537dc87212..d33fbc2123d 100644 --- a/runtime/bin/stdio_patch.dart +++ b/runtime/bin/stdio_patch.dart @@ -83,11 +83,20 @@ } } + @patch bool get supportsAnsiEscapes { + var result = _supportsAnsiEscapes(); + if (result is OSError) { + throw new StdinException("Error determining ANSI support", result); + } + return result; + } + static _echoMode() native "Stdin_GetEchoMode"; static _setEchoMode(bool enabled) native "Stdin_SetEchoMode"; static _lineMode() native "Stdin_GetLineMode"; static _setLineMode(bool enabled) native "Stdin_SetLineMode"; static _readByte() native "Stdin_ReadByte"; + static _supportsAnsiEscapes() native "Stdin_AnsiSupported"; } @patch class Stdout { @@ -112,6 +121,16 @@ } static _getTerminalSize(int fd) native "Stdout_GetTerminalSize"; + + @patch static bool _supportsAnsiEscapes(int fd) { + var result = _getAnsiSupported(fd); + if (result is! bool) { + throw new StdoutException("Error determining ANSI support", result); + } + return result; + } + + static _getAnsiSupported(int fd) native "Stdout_AnsiSupported"; } diff --git a/runtime/bin/stdio_unsupported.cc b/runtime/bin/stdio_unsupported.cc index 1482f919bbb..d4c9d0693ca 100644 --- a/runtime/bin/stdio_unsupported.cc +++ b/runtime/bin/stdio_unsupported.cc @@ -41,11 +41,23 @@ void FUNCTION_NAME(Stdin_SetLineMode)(Dart_NativeArguments args) { } +void FUNCTION_NAME(Stdin_AnsiSupported)(Dart_NativeArguments args) { + Dart_ThrowException( + DartUtils::NewDartArgumentError("Stdin unsupported on this platform")); +} + + void FUNCTION_NAME(Stdout_GetTerminalSize)(Dart_NativeArguments args) { Dart_ThrowException( DartUtils::NewDartArgumentError("Stdout unsupported on this platform")); } + +void FUNCTION_NAME(Stdout_AnsiSupported)(Dart_NativeArguments args) { + Dart_ThrowException( + DartUtils::NewDartArgumentError("Stdout unsupported on this platform")); +} + } // namespace bin } // namespace dart diff --git a/runtime/bin/stdio_win.cc b/runtime/bin/stdio_win.cc index 8f2a72cf23d..40df3a707be 100644 --- a/runtime/bin/stdio_win.cc +++ b/runtime/bin/stdio_win.cc @@ -9,6 +9,16 @@ #include "bin/stdio.h" +// These are not always defined in the header files. See: +// https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx +#ifndef ENABLE_VIRTUAL_TERMINAL_INPUT +#define ENABLE_VIRTUAL_TERMINAL_INPUT 0x0200 +#endif + +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#endif + namespace dart { namespace bin { @@ -77,6 +87,23 @@ bool Stdin::SetLineMode(bool enabled) { } +bool Stdin::AnsiSupported(bool* supported) { + ASSERT(supported != NULL); + HANDLE h = GetStdHandle(STD_INPUT_HANDLE); + if (h == INVALID_HANDLE_VALUE) { + *supported = false; + return false; + } + DWORD mode; + if (!GetConsoleMode(h, &mode)) { + *supported = false; + return false; + } + *supported = (mode & ENABLE_VIRTUAL_TERMINAL_INPUT) != 0; + return true; +} + + bool Stdout::GetTerminalSize(intptr_t fd, int size[2]) { HANDLE h; if (fd == 1) { @@ -93,6 +120,28 @@ bool Stdout::GetTerminalSize(intptr_t fd, int size[2]) { return true; } + +bool Stdout::AnsiSupported(intptr_t fd, bool* supported) { + ASSERT(supported != NULL); + HANDLE h; + if (fd == 1) { + h = GetStdHandle(STD_OUTPUT_HANDLE); + } else { + h = GetStdHandle(STD_ERROR_HANDLE); + } + if (h == INVALID_HANDLE_VALUE) { + *supported = false; + return false; + } + DWORD mode; + if (!GetConsoleMode(h, &mode)) { + *supported = false; + return false; + } + *supported = (mode & ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0; + return true; +} + } // namespace bin } // namespace dart diff --git a/sdk/lib/_internal/js_runtime/lib/io_patch.dart b/sdk/lib/_internal/js_runtime/lib/io_patch.dart index d6647d7a37b..46f8404ab2d 100644 --- a/sdk/lib/_internal/js_runtime/lib/io_patch.dart +++ b/sdk/lib/_internal/js_runtime/lib/io_patch.dart @@ -250,11 +250,6 @@ class _Platform { static String _version() { throw new UnsupportedError("Platform._version"); } - - @patch - static bool _ansiSupported() { - throw new UnsupportedError("Platform._ansiSupported"); - } } @patch @@ -550,6 +545,11 @@ class Stdin { void set lineMode(bool enabled) { throw new UnsupportedError("Stdin.lineMode"); } + + @patch + bool get supportsAnsiEscapes { + throw new UnsupportedError("Stdin.supportsAnsiEscapes"); + } } @patch @@ -568,6 +568,11 @@ class Stdout { int _terminalLines(int fd) { throw new UnsupportedError("Stdout.terminalLines"); } + + @patch + static bool _supportsAnsiEscapes(int fd) { + throw new UnsupportedError("Stdout.supportsAnsiEscapes"); + } } @patch diff --git a/sdk/lib/io/platform.dart b/sdk/lib/io/platform.dart index 482898c8d01..b087b053c32 100644 --- a/sdk/lib/io/platform.dart +++ b/sdk/lib/io/platform.dart @@ -71,7 +71,6 @@ class Platform { static final _operatingSystem = _Platform.operatingSystem; static final _localHostname = _Platform.localHostname; static final _version = _Platform.version; - static final _ansiSupported = _Platform.ansiSupported; /** * Get the number of processors of the machine. @@ -125,14 +124,6 @@ class Platform { */ static final bool isFuchsia = (_operatingSystem == "fuchsia"); - /** - * When stdio is connected to a terminal, whether ANSI codes are supported. - * - * This value is hard-coded to `true`, except on Windows where only more - * recent versions of Windows 10 support the codes. - */ - static final bool ansiSupported = _ansiSupported; - /** * Get the environment for this process. * diff --git a/sdk/lib/io/platform_impl.dart b/sdk/lib/io/platform_impl.dart index 98ad5c1816d..5f68b7dbdef 100644 --- a/sdk/lib/io/platform_impl.dart +++ b/sdk/lib/io/platform_impl.dart @@ -11,7 +11,6 @@ class _Platform { external static _localHostname(); external static _executable(); external static _resolvedExecutable(); - external static bool _ansiSupported(); /** * Retrieve the entries of the process environment. @@ -46,7 +45,6 @@ class _Platform { static int get numberOfProcessors => _numberOfProcessors(); static String get pathSeparator => _pathSeparator(); static String get operatingSystem => _operatingSystem(); - static bool get ansiSupported => _ansiSupported(); static Uri script; static String get localHostname { diff --git a/sdk/lib/io/stdio.dart b/sdk/lib/io/stdio.dart index 29ea48d2a43..8f1c6256dcc 100644 --- a/sdk/lib/io/stdio.dart +++ b/sdk/lib/io/stdio.dart @@ -136,6 +136,30 @@ class Stdin extends _StdStream implements Stream> { */ external void set lineMode(bool enabled); + /** + * Whether connected to a terminal that supports ANSI escape sequences. + * + * Not all terminals are recognized, and not all recognized terminals can + * report whether they support ANSI escape sequences, so this value is a + * best-effort attempt at detecting the support. + * + * The actual escape sequence support may differ between terminals, + * with some terminals supporting more escape sequences than others, + * and some terminals even differing in behavior for the same escape + * sequence. + * + * The ANSI color selection is generally supported. + * + * Currently, a `TERM` environment variable containing the string `xterm` + * will be taken as evidence that ANSI escape sequences are supported. + * On Windows, only versions of Windows 10 after v.1511 + * ("TH2", OS build 10586) will be detected as supporting the output of + * ANSI escape sequences, and only versions after v.1607 ("Anniversery + * Update", OS build 14393) will be detected as supporting the input of + * ANSI escape sequences. + */ + external bool get supportsAnsiEscapes; + /** * Synchronously read a byte from stdin. This call will block until a byte is * available. @@ -178,7 +202,7 @@ class Stdout extends _StdFileSink implements IOSink { */ int get terminalColumns => _terminalColumns(_fd); - /** + /* * Get the number of lines of the terminal. * * If no terminal is attached to stdout, a [StdoutException] is thrown. See @@ -186,9 +210,34 @@ class Stdout extends _StdFileSink implements IOSink { */ int get terminalLines => _terminalLines(_fd); + /** + * Whether connected to a terminal that supports ANSI escape sequences. + * + * Not all terminals are recognized, and not all recognized terminals can + * report whether they support ANSI escape sequences, so this value is a + * best-effort attempt at detecting the support. + * + * The actual escape sequence support may differ between terminals, + * with some terminals supporting more escape sequences than others, + * and some terminals even differing in behavior for the same escape + * sequence. + * + * The ANSI color selection is generally supported. + * + * Currently, a `TERM` environment variable containing the string `xterm` + * will be taken as evidence that ANSI escape sequences are supported. + * On Windows, only versions of Windows 10 after v.1511 + * ("TH2", OS build 10586) will be detected as supporting the output of + * ANSI escape sequences, and only versions after v.1607 ("Anniversery + * Update", OS build 14393) will be detected as supporting the input of + * ANSI escape sequences. + */ + bool get supportsAnsiEscapes => _supportsAnsiEscapes(_fd); + external bool _hasTerminal(int fd); external int _terminalColumns(int fd); external int _terminalLines(int fd); + external static bool _supportsAnsiEscapes(int fd); /** * Get a non-blocking `IOSink`. diff --git a/tests/standalone/io/ansi_supported_test.dart b/tests/standalone/io/ansi_supported_test.dart index a0f46690eb0..643ae7a95b7 100644 --- a/tests/standalone/io/ansi_supported_test.dart +++ b/tests/standalone/io/ansi_supported_test.dart @@ -6,18 +6,30 @@ import 'dart:io'; import "package:expect/expect.dart"; -main() { +testStdout(Stdout s) { try { - Platform.ansiSupported; - } catch (e, s) { - Expect.fail("Platform.ansiSupported threw: $e\n$s\n"); + s.supportsAnsiEscapes; + } catch (e, st) { + Expect.fail("$s.supportsAnsiEscapes threw: $e\n$st\n"); } - Expect.isNotNull(Platform.ansiSupported); - Expect.isTrue(Platform.ansiSupported is bool); - if (stdout.hasTerminal && Platform.ansiSupported) { - stdout.writeln('\x1b[31mThis text has a red foreground using SGR.31.'); - stdout.writeln('\x1b[39mThis text has restored the foreground color.'); + Expect.isNotNull(s.supportsAnsiEscapes); + Expect.isTrue(s.supportsAnsiEscapes is bool); + if (s.supportsAnsiEscapes) { + s.writeln('\x1b[31mThis text has a red foreground using SGR.31.'); + s.writeln('\x1b[39mThis text has restored the foreground color.'); } else { - stdout.writeln('ANSI codes not supported on this platform'); + s.writeln('ANSI escape codes are not supported on this platform'); } } + +main() { + testStdout(stdout); + testStdout(stderr); + try { + stdin.supportsAnsiEscapes; + } catch (e, st) { + Expect.fail("stdin.supportsAnsiEscapes threw: $e\n$st\n"); + } + Expect.isNotNull(stdin.supportsAnsiEscapes); + Expect.isTrue(stdin.supportsAnsiEscapes is bool); +}