在 Dart 中使用 ANSI 转义码 —— stdout.supportsAnsiEscapes、chalkdart、tint
Dart 的 `dart:io` 已通过 `stdout` 暴露了其他生态通常要你自己实现的标准能力检测 —— `stdout.supportsAnsiEscapes` 在懂得 VT 序列的 TTY 上返回 `true`,被管道、重定向,或运行在未启用 VT 的旧 Conhost 上时返回 `false`。搭配原始 `stdout.write('\x1B[31m…\x1B[0m')`(Dart 同时接受 `\x1B` 与 `\u001B`),即可在 macOS、Linux 与 Windows Terminal / Conhost 1709+ 上获得零依赖的可移植起点。需要顺手的彩色 API 时选 `chalkdart`(Dart 中最受欢迎的 ANSI 库 —— 链式扩展 API 仿自 Node 的 `chalk`)、`tint`(紧凑现代)或 `cli_util`(Google 官方 CLI 工具集,内含 ANSI 模块)。Flutter SDK 自带的 `flutter` CLI 即用这些原语 —— 其进度 / 构建状态输出是 Dart 风格能力门控的标准参考。
推荐库
- chalkdart
Dart 中最受欢迎的 ANSI 库 —— 链式扩展 API,仿照 Node 的 `chalk`:`print(chalk.red.bold('error'))`。支持 TrueColor(`chalk.rgb(255,128,0)`)、256 色、命名颜色、背景对、下划线变体。纯 Dart,无原生依赖。
- tint
紧凑的现代替代品 —— `String` 扩展如 `'error'.red().bold()`。表面积小于 chalkdart;与已经使用扩展方法风格的代码搭配良好。自动尊重 `NO_COLOR`。
- cli_util
Google 官方 Dart CLI 辅助 —— `dart` 与 `flutter` 工具链所用。包含 `ansi` 模块,提供能力感知的 `green`、`red` 等,以及 `Ansi(supportsAnsi)` 构造器做显式门控。Dart 生产级 CLI 的标准模式。
- ansi_styles
底层转义序列映射 —— `AnsiStyles.red('text')`、`AnsiStyles.bgBlue('text')`,以及向外暴露的转义字符串字段,方便需要原始字节的场景(如模板拼装到自定义渲染器)。不做能力检测 —— 请配合 `stdout.supportsAnsiEscapes` 使用。
常用写法
import 'dart:io';
void main() {
// Dart accepts both \x1B (hex) and \u001B (Unicode) — pick one.
stdout.write('\x1B[1;31merror:\x1B[0m permission denied\n');
stdout.write('\x1B[33mwarn:\x1B[0m deprecated flag\n');
stdout.write('\x1B[32mok:\x1B[0m 142 tests passed\n');
// Truecolor (38;2;R;G;B):
stdout.write('\x1B[38;2;255;128;0morange truecolor\x1B[0m\n');
}import 'dart:io';
// dart:io's stdout already exposes the portable capability check.
// supportsAnsiEscapes returns false on a piped stdout, on a non-TTY,
// and on legacy Windows console without VT enabled.
String style(String text, String sgr) {
if (!stdout.supportsAnsiEscapes) return text;
return '\x1B[${sgr}m${text}\x1B[0m';
}
void main() {
print(style('OK', '32'));
print(style('FAIL', '1;31'));
// Bonus — also reflects redirection state independently:
if (stdout.hasTerminal) {
final cols = stdout.terminalColumns; // dynamic width for bars / tables
print('terminal width: $cols');
}
}// pubspec.yaml:
// dependencies:
// chalkdart: ^3.0.0
import 'package:chalkdart/chalk.dart';
void main() {
print(chalk.red.bold('error:') + ' permission denied');
print(chalk.yellow('warn:') + ' deprecated flag');
print(chalk.green('ok:') + ' 142 tests passed');
// Background + foreground composition:
print(chalk.white.bold.bgRed(' FATAL '));
// Truecolor via rgb / hex:
print(chalk.rgb(255, 128, 0)('orange truecolor'));
print(chalk.hex('#ff8000')('same, hex form'));
}import 'dart:io';
bool ansiCapable() {
// Honour the Unix-standard kill switch first.
if (Platform.environment['NO_COLOR'] != null) return false;
// dart:io's own portable TTY check.
if (!stdout.supportsAnsiEscapes) return false;
return true;
}
void main() {
if (ansiCapable()) {
stdout.write('\x1B[32mready\x1B[0m\n');
} else {
stdout.write('ready\n');
}
}
// Flutter caveats:
// 1) print() in a Flutter app routes to platform logs (logcat / NSLog),
// which do NOT parse ANSI — your bytes appear verbatim. Gate output
// with kReleaseMode / kDebugMode or use the developer.log API.
// 2) flutter run --machine emits JSON, never ANSI — supportsAnsiEscapes
// returns false in that mode.
// 3) On Windows, supportsAnsiEscapes already accounts for VT-enabled
// Conhost (1709+) and Windows Terminal — no SetConsoleMode needed.