Flutter ANSI转义码处理插件ansi_escape_codes的使用

Flutter ANSI转义码处理插件ansi_escape_codes的使用

在Flutter开发中,处理终端文本格式化时常常需要用到ANSI转义码。ansi_escape_codes 插件可以帮助开发者轻松处理这些转义码。本文将详细介绍如何使用 ansi_escape_codes 插件来处理ANSI转义码。

1. 控制功能常量和预定义值

1.1 控制代码(C0集)

控制代码(C0集)由0x00到0x1F范围内的代码表示。以下是部分控制代码:

常量 代码 描述
NUL \x00 空字符
BEL \x07 铃声(终端可以阻止铃声)
BS \b\x08 退格
HT \t\x09 水平制表符
LF \n\x0A 行结束符
FF \f\x0C 分页符
CR \r\x0D 回车
ESC \x1B 转义(用于扩展代码)
import 'package:ansi_escape_codes/controls.dart';

...

print('\t\r\n' == '$HT$CR$LF'); // true

1.2 控制功能 ESC Fe(C1集)

这些控制功能由形式为 ESC Fe 的两个字符转义序列表示,其中 ESC 由代码 0x1B 表示,Fe 由代码 0x40 到 0x5F 表示。以下是一些C1集中的控制功能:

常量 代码 描述
CSI ESC [ 控制序列引入器
ST ESC \ 字符串终止符
OSC ESC ] 操作系统命令
HTS ESC H 字符制表符设置
import 'package:ansi_escape_codes/controls.dart';

...

// 清除屏幕
print('Erase screen${CSI}2JScreen erased');

// 设置新的制表符位置
print('$HTS        $HTS    $HTS  $HTS');
print('1\t2\t3\t4'); // 1       2   3 4
print('${CSI}3g'); // 重置制表符位置为默认

1.3 控制序列(CSI)

控制序列是一个以控制功能 CSI 开头的字符串,后面跟着表示参数的一个或多个字节(如果有),再后面跟着一个或多个字节表示控制功能。CSI 本身是C1集中的元素。以下是一些CSI集中的控制功能:

常量 代码 描述
CUU CSI n A 光标向上移动 n 行
CUD CSI n B 光标向下移动 n 行
CUF CSI n C 光标向右移动 n 个字符
CUB CSI n D 光标向左移动 n 个字符
CUP CSI n;m H 光标定位到第 n 行,第 m 列
ED CSI s J 清除页面(显示)(s=2 - 整个屏幕)
DCH CSI n P 删除 n 个字符
ECH CSI n X 清除 n 个字符
TBC CSI n g 制表符清除(s=3 - 清除所有字符制表符)
SM CSI s h 设置模式(s=4 - 插入替换模式)
RM CSI s l 重置模式
SGR CSI s… m 选择图形表现
import 'package:ansi_escape_codes/controls.dart';

...

// 光标向左移动4个字符
// 删除1个字符('1')
// 光标向右移动1个字符
// 清除1个字符('3')
print('1234${CSI}4$CUB$CSI$DCH$CSI$CUF$CSI$ECH'); // '2 4'

// 插入模式
print('${CSI}4${SM}tree${CSI}3${CUB}h'); // three
print('${CSI}4${RM}tree${CSI}3${CUB}h'); // thee

// 斜体文本
print('${CSI}3$SGR Italicized text ${CSI}0$SGR');

1.4 预定义值

预定义值用Dart风格替换控制功能的使用。以下是一些常用的预定义值:

目标 模板 函数 默认常量 描述
光标上移 ${cursorUpOpen}$n$cursorUpClose cursorUpN(int n) cursorUp 光标上移 n 行(默认1行)
光标下移 ${cursorDownOpen}$n$cursorDownClose cursorDownN(int n) cursorDown 光标下移 n 行(默认1行)
光标前移 ${cursorRightOpen}$n$cursorRightClose cursorRightN(int n) cursorRight 光标右移 n 个字符(默认1个字符)
光标后移 ${cursorLeftOpen}$n$cursorLeftClose cursorLeftN(int n) cursorLeft 光标左移 n 个字符(默认1个字符)
光标下一行 ${cursorNextLineOpen}$n$cursorNextLineClose cursorNextLineN(int n) cursorNextLine 光标移动到下一行的开始处(默认1行)
光标上一行 ${cursorPrevLineOpen}$n$cursorPrevLineClose cursorPrevLineN(int n) cursorPrevLine 光标移动到上一行的开始处(默认1行)
光标水平位置 ${cursorHPosOpen}$n$cursorHPosClose cursorHPosN(int n) cursorHPos 光标移动到第 n 列(默认1列)
光标位置 ${cursorPosOpen}$row;$col$cursorPosClose cursorPosTo(int row, int col) cursorPos 光标移动到第 row 行,第 col 列
光标水平和垂直位置 ${cursorHVPosOpen}$row;$col$cursorHVPosClose cursorHVPosTo(int row, int col) cursorHVPos 与光标位置相同,但有一些差异
print('${CSI}4$CUU' == cursorUpN(4)); // true
print('${CSI}4$CUU' == '${cursorUpOpen}4$cursorUpClose'); // true
print('$CSI$CUU' == cursorUp); // true

2. 分析和解析

2.1 AnsiParser

AnsiParser 可以用来分析包含转义码的文本:

const text = '$bold Bold $fgCyan Bold+cyan $resetBoldAndFaint Cyan ';
final parser = AnsiParser(text);
parser.matches.forEach(print);
// Match(start: 0, end: 4, entity: Sgr(bold), state: SgrState(bold))
// Match(start: 4, end: 10, entity: Text(' Bold '), state: SgrState(bold))
// Match(start: 10, end: 15, entity: Sgr(fgCyan), state: SgrState(bold, foreground: Color16(Colors.cyan)))
// Match(start: 15, end: 26, entity: Text(' Bold+cyan '), state: SgrState(bold, foreground: Color16(Colors.cyan)))
// Match(start: 26, end: 31, entity: Sgr(resetBoldAndFaint), state: SgrState(foreground: Color16(Colors.cyan)))
// Match(start: 31, end: 37, entity: Text(' Cyan '), state: SgrState(foreground: Color16(Colors.cyan)))

2.2 快速分析

你可以通过使用扩展来快速分析字符串,而无需使用 AnsiParser

import 'package:ansi_escape_codes/extensions.dart';

...

const text = '${fgRed}ERROR$reset';
print(text.hasEscapeCodes); // true
print(text.hasCsi); // true
print(text.hasSgr); // true
print(text.hasForeground); // true
print(text.hasBackground); // false
print(text.showEscapeCodes()); // [CSI 31 SGR]ERROR[CSI 0 SGR]

2.3 AnsiPrinter

AnsiPrinter 可以用来设置默认值并替换转义码:

const text = ' Default text '
    '$bgWhite$fgBlack Highlighted text '
    '$resetBg$resetFg Default text again $reset';
final printer = AnsiPrinter(
  defaultState: SgrPlainState(
    background: ColorRgb(44, 43, 124),
    foreground: ColorRgb(224, 192, 64),
  ),
);
printer.print(text);

2.4 堆叠式 AnsiPrinter

堆叠式 AnsiPrinter 可以累积状态变化并依次禁用它们,将当前状态转换为标准转义序列输出:

const text = '$bold 1 $bold 2 $bold 3 $resetBoldAndFaint 2 $resetBoldAndFaint 1 $resetBoldAndFaint';
final printer1 = AnsiPrinter();
final printer2 = AnsiPrinter(stacked: true);
printer1.print(text); // '[bold] 1  2  3 [resetBoldAndFaint] 2  1 '
printer2.print(text); // '[bold] 1  2  3  2  1 [resetBoldAndFaint]'

示例代码

以下是一个完整的示例代码,展示了如何使用 ansi_escape_codes 插件进行ANSI转义码处理:

import 'dart:io';

import 'package:ansi_escape_codes/ansi_escape_codes.dart';
import 'package:ansi_escape_codes/controls.dart';

void main() {
  {
    const text1 = '$fgGreen(fgGreen)$resetFg'
        ' $fgHighGreen(fgHighGreen)$resetFg'
        ' $fg256Green(fg256Green)$resetFg'
        ' $fg256Open$HIGH_GREEN$fg256Close(fg256Open highGreen fg256Close)$resetFg'
        ' $fg256Rgb050(fg256Rgb050)$resetFg'
        ' ${fgRgbOpen}0;255;0$fgRgbClose(fgRgbOpen 0;255;0 fgRgbClose)$resetFg';
    const text2 = '$fgGreen'
        '$bgYellow(bgYellow)$resetBg'
        ' $bgHighYellow(bgHighYellow)$resetBg'
        ' $bg256Yellow(bg256Yellow)$resetBg'
        ' $bg256Open$HIGH_YELLOW$bg256Close(bg256Open highYellow bg256Close)$resetBg'
        ' $bg256HighYellow(bg256HighYellow)$resetBg'
        ' $bg256Rgb550(bg256Rgb550)$resetBg'
        ' ${bgRgbOpen}255;255;0$bgRgbClose(bgRgbOpen 255;255;0 bgRgbClose)$resetBg'
        '$resetFg';
    const text3 = '$negative$text2$resetNegative';
    const text4 = 'default $bold(bold)$resetBoldAndFaint'
        ' $faint(faint)$resetBoldAndFaint'
        ' $italicized(italicized)$resetItalicized'
        ' $underlined(underlined)$resetUnderlined'
        ' $doublyUnderlined(doublyUnderlined)$resetUnderlined'
        ' $crossedOut(crossedOut)$resetCrossedOut'
        ' $concealed(concealed)$resetConcealed'
        ' $slowlyBlinking(slowlyBlinking)'
        ' $rapidlyBlinking(rapidlyBlinking)$resetBlinking';
    const text5 = '$negative$text4$resetNegative';
    const texts = [text1, text2, text3, text4, text5];

    for (final (index, text) in texts.indexed) {
      print('${index + 1}: $text (${text.length})');
    }

    print('');
    for (final (index, text) in texts.indexed) {
      final parser = AnsiParser(text);
      final textWithoutEscapeCodes = parser.removeAll();
      print('${index + 1}: $textWithoutEscapeCodes (${parser.length})');
    }

    // showControlFunctions.
    print('');
    final parser = AnsiParser(text4);
    print(
      '4: '
      '${parser.showControlFunctions(
        open: '$faint[',
        close: ']$resetBoldAndFaint',
      )}',
    );

    // matches.
    print('');
    final buf = StringBuffer('4: ');
    for (final m in parser.matches) {
      switch (m.entity) {
        case Text(:final string):
          buf.write(string);

        case EscapeCode():
        // no-op
      }
    }
    print(buf);
  }

  // Invalid values.
  {
    print('');
    print('Invalid values:');
    print(
      AnsiParser(
        '$CSI$FOREGROUND;$COLOR_256;256$SGR'
        '$CSI$UNDERLINE_COLOR;3;1;2;3$SGR'
        '$CSI$BACKGROUND;$COLOR_RGB;100$SGR'
        '$CSI$BACKGROUND;$COLOR_RGB;200$SGR'
        '$CSI$BACKGROUND;$COLOR_RGB;200;$SGR'
        '$CSI$BACKGROUND;$COLOR_RGB;200;;$SGR'
        '$CSI$BACKGROUND;$COLOR_RGB;200;0$SGR'
        '$CSI$BACKGROUND;$COLOR_RGB;200;0;$SGR'
        '${CSI}256%',
      ).showControlFunctions(),
    );
  }

  // stateAtPos.
  {
    print('');
    const text =
        '${bold}bold ${italicized}bold+italic ${resetBoldAndFaint}italic$resetItalicized';
    final parser = AnsiParser(text);
    print(text);
    final stateAtPos0 = parser.stateAtPos(0);
    print(
      '^-- isBold=${stateAtPos0.isBold}'
      ', isItalicized=${stateAtPos0.isItalicized}',
    );
    final stateAtPos5 = parser.stateAtPos(5);
    print(
      '${' ' * 5}^-- isBold=${stateAtPos5.isBold}'
      ', isItalicized=${stateAtPos5.isItalicized}',
    );
    final stateAtPos17 = parser.stateAtPos(17);
    print(
      '${' ' * 17}^-- isBold=${stateAtPos17.isBold}'
      ', isItalicized=${stateAtPos17.isItalicized}',
    );
    final stateAtPos23 = parser.stateAtPos(23);
    print(
      '${' ' * 23}^-- isBold=${stateAtPos23.isBold}'
      ', isItalicized=${stateAtPos23.isItalicized}',
    );
  }

  // substring.
  {
    print('');
    final parser = AnsiParser(
      '$fgWhite${bold}Lorem$resetBoldAndFaint '
      '$bgHighRed$fgHighWhite${italicized}ipsum dolor sit$resetItalicized$resetBg$resetFg'
      '$fgWhite amet, consectetur $fgRed${underlined}adipiscing$resetUnderlined'
      '$fgWhite elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.$reset',
    );
    for (var i = 0; i < 40; i += 2) {
      print('"${parser.substring(i, maxLength: 40)}"');
    }
  }

  // AnsiPrinter.
  {
    print('');
    const text = ' default colors'
        ' $fg256Rgb550$bg256Rgb031 yellow on green'
        ' $resetFg$resetBg default colors ';
    print('Standart output (the default colors are set by the terminal):');
    print(text);

    const defaultState = SgrPlainState(
      foreground: Color256(Colors.rgb555),
      background: Color256(Colors.rgb320),
    );

    print('');
    print('With AnsiPrinter (the default state is overrided):');
    final printer = AnsiPrinter(
      defaultState: defaultState,
    );
    printer.print(text);

    print('');
    print('With runZonedAnsiPrinter (the default state is overrided):');
    runZonedAnsiPrinter(
      defaultState: defaultState,
      () {
        print(text);
      },
    );
  }

  // Stacked AnsiPrinter.
  {
    const importantNote = '${italicized}Important note$resetItalicized';
    const text1 = 'Normal text $importantNote Normal text';
    const text2 =
        '${italicized}Important text $importantNote Important text$resetItalicized';

    print('');
    print(text1); // Normal text &lt;i&gt;Important&lt;/i&gt; note Normal text

    print('');
    print('Insertion breaks the style of the text:');
    print(text2); // &lt;i&gt;Important text Important note&lt;/i&gt; Important text

    print('');
    print('The state is accumulated in the stack:');
    final stackedPrinter = AnsiPrinter(stacked: true);
    stackedPrinter
        .print(text2); // &lt;i&gt;Important text Important note Important text&lt;/i&gt;
  }

  // Tabs.
  if (stdout.hasTerminal) {
    print('');
    const text = '0\t1\t2\t3\t4\t5\t6\t7\t8\t9';
    print(text);
    tabs(defaultTab: 12);
    print(text);
    tabs();
  }
}

更多关于Flutter ANSI转义码处理插件ansi_escape_codes的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter ANSI转义码处理插件ansi_escape_codes的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,ansi_escape_codes 是一个用于处理ANSI转义码的插件。ANSI转义码通常用于在终端中设置文本颜色、背景颜色、文本样式等。ansi_escape_codes 插件可以帮助你在Flutter应用中解析和处理这些转义码。

安装插件

首先,你需要在 pubspec.yaml 文件中添加 ansi_escape_codes 依赖:

dependencies:
  flutter:
    sdk: flutter
  ansi_escape_codes: ^1.0.0  # 请检查最新版本

然后运行 flutter pub get 来安装依赖。

使用插件

ansi_escape_codes 插件提供了一些工具函数来解析和处理ANSI转义码。以下是一些基本的使用示例:

1. 解析ANSI转义码

import 'package:ansi_escape_codes/ansi_escape_codes.dart';

void main() {
  String ansiString = '\x1B[31mThis is red text\x1B[0m';
  List<AnsiCode> codes = parseAnsiCodes(ansiString);

  for (var code in codes) {
    print('Code: ${code.code}, Text: ${code.text}');
  }
}

在这个例子中,parseAnsiCodes 函数会将包含ANSI转义码的字符串解析为一个 AnsiCode 对象的列表,每个对象包含转义码和对应的文本。

2. 移除ANSI转义码

如果你只想移除字符串中的ANSI转义码,可以使用 stripAnsiCodes 函数:

import 'package:ansi_escape_codes/ansi_escape_codes.dart';

void main() {
  String ansiString = '\x1B[31mThis is red text\x1B[0m';
  String plainText = stripAnsiCodes(ansiString);

  print(plainText);  // 输出: This is red text
}

3. 应用ANSI转义码

你还可以使用 applyAnsiCodes 函数将ANSI转义码应用到文本上:

import 'package:ansi_escape_codes/ansi_escape_codes.dart';

void main() {
  String text = 'This is red text';
  String ansiString = applyAnsiCodes(text, [AnsiCode(31, '')]);

  print(ansiString);  // 输出: \x1B[31mThis is red text\x1B[0m
}
回到顶部