跳到主要内容
ansicode
Dart

在 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` 使用。

常用写法

直接 stdout.write 配合 \x1B
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');
}
标准库能力检测 —— stdout.supportsAnsiEscapes
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');
  }
}
chalkdart —— 链式扩展 API
// 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'));
}
NO_COLOR + Platform.environment + Flutter 注意点
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.

相关序列

其他语言