blob: af7868982677e0e3d57ecc8529c97d363cc6c25a [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// TODO(ahe): Originally copied from sdk/pkg/compiler/lib/src/colors.dart,
// merge these two packages.
library colors;
import 'dart:convert' show jsonEncode;
import 'dart:io' show Platform, Process, ProcessResult, stderr, stdout;
/// ANSI/xterm termcap for setting default colors. Output from Unix
/// command-line program `tput op`.
const String DEFAULT_COLOR = "\x1b[39;49m";
/// ANSI/xterm termcap for setting black text color. Output from Unix
/// command-line program `tput setaf 0`.
const String BLACK_COLOR = "\x1b[30m";
/// ANSI/xterm termcap for setting red text color. Output from Unix
/// command-line program `tput setaf 1`.
const String RED_COLOR = "\x1b[31m";
/// ANSI/xterm termcap for setting green text color. Output from Unix
/// command-line program `tput setaf 2`.
const String GREEN_COLOR = "\x1b[32m";
/// ANSI/xterm termcap for setting yellow text color. Output from Unix
/// command-line program `tput setaf 3`.
const String YELLOW_COLOR = "\x1b[33m";
/// ANSI/xterm termcap for setting blue text color. Output from Unix
/// command-line program `tput setaf 4`.
const String BLUE_COLOR = "\x1b[34m";
/// ANSI/xterm termcap for setting magenta text color. Output from Unix
/// command-line program `tput setaf 5`.
const String MAGENTA_COLOR = "\x1b[35m";
/// ANSI/xterm termcap for setting cyan text color. Output from Unix
/// command-line program `tput setaf 6`.
const String CYAN_COLOR = "\x1b[36m";
/// ANSI/xterm termcap for setting white text color. Output from Unix
/// command-line program `tput setaf 7`.
const String WHITE_COLOR = "\x1b[37m";
/// All the above codes. This is used to compare the above codes to the
/// terminal's. Printing this string should have the same effect as just
/// printing [DEFAULT_COLOR].
const String ALL_CODES = BLACK_COLOR +
RED_COLOR +
GREEN_COLOR +
YELLOW_COLOR +
BLUE_COLOR +
MAGENTA_COLOR +
CYAN_COLOR +
WHITE_COLOR +
DEFAULT_COLOR;
const String TERMINAL_CAPABILITIES = """
colors
setaf 0
setaf 1
setaf 2
setaf 3
setaf 4
setaf 5
setaf 6
setaf 7
op
""";
/// Boolean value caching whether or not we should display ANSI colors.
///
/// If `null`, we haven't decided whether we should display ANSI colors or not.
bool? _enableColors;
/// Finds out whether we are displaying ANSI colors.
///
/// The first time this getter is invoked (either by a client or by an attempt
/// to use a color), it decides whether colors should be used based on the
/// logic in [_computeEnableColors] (unless a value has previously been set).
bool get enableColors => _enableColors ??= _computeEnableColors();
/// Allows the client to override the decision of whether to disable ANSI
/// colors.
void set enableColors(bool value) {
// ignore: unnecessary_null_comparison
assert(value != null);
_enableColors = value;
}
String wrap(String string, String color) {
return enableColors ? "${color}$string${DEFAULT_COLOR}" : string;
}
String black(String string) => wrap(string, BLACK_COLOR);
String red(String string) => wrap(string, RED_COLOR);
String green(String string) => wrap(string, GREEN_COLOR);
String yellow(String string) => wrap(string, YELLOW_COLOR);
String blue(String string) => wrap(string, BLUE_COLOR);
String magenta(String string) => wrap(string, MAGENTA_COLOR);
String cyan(String string) => wrap(string, CYAN_COLOR);
String white(String string) => wrap(string, WHITE_COLOR);
/// Returns whether [sink] supports ANSI escapes or `null` if it could not be
/// determined.
bool? _supportsAnsiEscapes(sink) {
try {
// ignore: undefined_getter
return sink.supportsAnsiEscapes;
} on NoSuchMethodError {
// Ignored: We're running on an older version of the Dart VM which doesn't
// implement `supportsAnsiEscapes`.
return null;
}
}
/// Callback used by [_computeEnableColors] to report why it has or hasn't
/// chosen to use ANSI colors.
void Function(String) printEnableColorsReason = (_) {};
/// True if we should enable colors in output.
///
/// We enable colors when both `stdout` and `stderr` support ANSI escapes.
///
/// On non-Windows platforms, this functions checks the terminal capabilities,
/// on Windows we only enable colors if the VM getters are present and returned
/// `true`.
///
/// Note: do not call this method directly, as it is expensive to
/// compute. Instead, use [CompilerContext.enableColors].
bool _computeEnableColors() {
bool? stderrSupportsColors = _supportsAnsiEscapes(stdout);
bool? stdoutSupportsColors = _supportsAnsiEscapes(stderr);
if (stdoutSupportsColors == false) {
printEnableColorsReason(
"Not enabling colors, stdout does not support ANSI colors.");
return false;
}
if (stderrSupportsColors == false) {
printEnableColorsReason(
"Not enabling colors, stderr does not support ANSI colors.");
return false;
}
if (Platform.isWindows) {
if (stderrSupportsColors != true || stdoutSupportsColors != true) {
// In this case, either [stdout] or [stderr] did not support the
// property `supportsAnsiEscapes`. Since we do not have another way
// to determine support for colors, we disable them.
printEnableColorsReason("Not enabling colors as ANSI is not supported.");
return false;
}
printEnableColorsReason("Enabling colors as OS is Windows.");
return true;
}
// We have to check if the terminal actually supports colors. Currently,
// to avoid linking the Dart VM with ncurses, ANSI escape support is reduced
// to `Platform.environment['TERM'].contains("xterm")`.
// The `-S` option of `tput` allows us to query multiple capabilities at
// once.
ProcessResult result = Process.runSync(
"/bin/sh", ["-c", "printf '%s' '$TERMINAL_CAPABILITIES' | tput -S"]);
if (result.exitCode != 0) {
printEnableColorsReason("Not enabling colors, running tput failed.");
return false;
}
List<String> lines = result.stdout.split("\n");
if (lines.length != 2) {
printEnableColorsReason("Not enabling colors, unexpected output from tput: "
"${jsonEncode(result.stdout)}.");
return false;
}
String numberOfColors = lines[0];
if ((int.tryParse(numberOfColors) ?? -1) < 8) {
printEnableColorsReason(
"Not enabling colors, less than 8 colors supported: "
"${jsonEncode(numberOfColors)}.");
return false;
}
String allCodes = lines[1].trim();
if (ALL_CODES != allCodes) {
printEnableColorsReason("Not enabling colors, color codes don't match: "
"${jsonEncode(ALL_CODES)} != ${jsonEncode(allCodes)}.");
return false;
}
printEnableColorsReason("Enabling colors.");
return true;
}