Dart CI Pub Publisher Pub Version GitHub License

A toolkit for working with ANSI escape codes and analyzing strings containing them.

ANSI escape sequences are a standard for in-band signaling to control cursor location, color, font styling, and other options on video text terminals and terminal emulators. Certain sequences of bytes, most starting with an ASCII escape character and a bracket character, are embedded into text. The terminal interprets these sequences as commands, rather than text to display verbatim. Wikipedia

Features

Table of contents

Quick start

How do I color text?

You can use different levels of coloring.

Close to ANSI standard

If you need a level that is as close as possible to ANSI, you can use ready-made constants that comply with the standard.

import 'package:ansi_escape_codes/ansi.dart';

void main() {
  const text = '$CSI$FG_GREEN$SGR Green text $CSI$FG_DEFAULT$SGR$LF'
    '$CSI$FOREGROUND;$COLOR_256;$RGB_520$SGR Orange text $CSI$RESET$SGR';

  print(text);
}

Most likely, this option will only be of interest to specialists in the standard.

All constants can be found in this folder: ansi

Maximum performance

A convenient and highly efficient option is to use ready-to-use values that hide the complexity of ANSI:

import 'package:ansi_escape_codes/ansi_escape_codes.dart';

void main() {
  const text = '$fgGreen Green text $resetFg'
    '$bgYellow Yellow background $resetBg'
    '$bold Bold text $resetBoldAndDim'
    '$italic Italic text $resetItalic'
    '$underline Underline text $resetUnderline'
    '$reset';

  print(text);
}

Its main feature is that it allows you to create constant strings that are ready to use.

fgGreen is an ANSI escape sequence that sets the text color to green. bgYellow sets the background color to yellow. And so on.

resetFg resets the text color to the default color set in your terminal. resetBg resets the background color to the default color. And so on.

Note

Please note the following example:

print('$fgGreen Green text $fgYellow Yellow text $resetFg Default text');

After resetFg, the text color will not revert to fgGreen, but will return to the standard terminal text color!

If you need the ability to roll back to the previous color, use styles or StackedPrinter.

Since you cannot set bold and dim at the same time, a single escape sequence is used in ANSI to reset both: resetBoldAndDim.

reset returns all settings to default.

All the ready-to-use values can be found in this folder: ready_to_use

The power of styles
import 'package:ansi_escape_codes/style.dart';

void main() {
  final defaultStyle = gray12;
  final greenStyle = green.bold;
  final highlighedStyle = red.bgYellow.underline;

  print(
    defaultStyle(
      'Normal text'
      ' ${greenStyle('Green ${highlighedStyle('Highlighted text')} text')}'
      ' Normal text',
    ),
  );
}

First, you can assemble your own style from any pieces:

final style = rgb050.bgRgb010.bold.italic.underline;

Second, styles can be nested: after completing the action of a nested style, the style will return to the parent style.

How can I change the default style?

You cannot set default colors for the entire terminal. However, Dart allows you to intercept calls to the print and override the default style in those calls.

Example (if you use styles):

import 'package:ansi_escape_codes/style.dart';

void main() {
  final greenStyle = green.bold;
  final highlighedStyle = red.bgYellow.underline;

  runZonedPrinter(
    defaultStyle: gray12,
    () {
      print(
        'Normal text'
        ' ${greenStyle('Green ${highlighedStyle('Highlighted text')} text')}'
        ' Normal text',
      );
    },
  );
}

If you are using ready-to-use values, you can also use runZonedPrinter. But in this case, all reset... functions will return defaultStyle:

import 'package:ansi_escape_codes/ansi_escape_codes.dart';

void main() {
  runZonedPrinter(
    defaultStyle: const Style(
      foreground: Color256.gray12,
    ),
    () {
      print(
        'Normal text'
        ' ${fgGreen}Green text ${fgRed}Highlighted text$resetFg Not a green text$resetFg'
        ' Normal text',
      );
    },
  );
}

If you need nested styles, use runZonedStackedPrinter:

import 'package:ansi_escape_codes/ansi_escape_codes.dart';

void main() {
  runZonedStackedPrinter(
    defaultStyle: const Style(
      foreground: Color256.gray12,
    ),
    () {
      print(
        'Normal text'
        ' ${fgGreen}Green text ${fgRed}Highlighted text$resetFg Green text$resetFg'
        ' Normal text',
      );
    },
  );
}

![IMPORTANT]

Please note that when using styles and applying ready-to-use values, different imports are used. This is because some of the names are the same in both options.

Control function constants and ready-to-use values

Strings containing ANSI escape codes can be constants:

const text = '$fgGreen Green text $resetFg'
    '$bgYellow Yellow background $resetBg'
    '$bold Bold text $resetBoldAndDim'
    '$italic Italic text $resetItalic'
    '$underline Underline text $resetUnderline';
print(text);

For complex cases there are functions:

final nonConstantText = '${fgRgb(255, 128, 0)} Orange text $resetFg';
print(nonConstantText);

But even in these cases it is possible to switch to constants:

const constantText = '${fgRgbOpen}255;128;0$fgRgbClose Orange text $resetFg';
print(constantText);

Of course, nothing prevents you from using the escape codes themselves directly. But even in this case you can use predefined constants to make the text more readable.

All of the following examples are equivalent:

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

print('\x1B[38;2;255;128;0m Orange text \x1B[0m');
print('$ESC[38;2;255;128;0m Orange text $ESC[0m');
print('${CSI}38;2;255;128;0$SGR Orange text ${CSI}0$SGR');
print('$CSI$FOREGROUND;$COLOR_RGB;255;128;0$SGR Orange text $CSI$RESET$SGR');
print('${fgRgbOpen}255;128;0$fgRgbClose Orange text $resetFg'); // Not constant!
print('${fgRgb(255, 128, 0)} Orange text $resetFg'); // Not constant!

Control codes are deliberately named in SCREAMING_SNAKE_CASE as opposed to the common Dart camelCase. First, this is how they are named in the Standard. Second, in this form they will not prevent you from naming your own variables. Thirdly, and most importantly, most users do not need to use them directly.

Control codes (C0 set)

These control functions (control codes) are represented by codes from 0x00 to 0x1F. Some control functions from the C0 set:

Constant Code Description
NUL \x00 Null
BEL \x07 Bell (terminals can block the bell)
BS \b or \x08 Backspace
HT \t or \x09 Horizontal tabulation
LF \n or \x0A Line feed
FF \f or \x0C Form feed
CR \r or \x0D Carriage return
ESC \x1B Escape (is used for code extension purposes)
import 'package:ansi_escape_codes/ansi.dart';

…

// The following examples are equivalent:
print('\t\r\n');
print('$HT$CR$LF');

Control functions ESC Fe (C1 set)

These control functions are represented by 2-character escape sequences of the form ESC Fe, where ESC is represented by code 0x1B and Fe is represented by codes from 0x40 to 0x5F.

Some control functions from the C1 set:

Constant Code Description
CSI ESC [ Control Sequence Introducer
ST ESC \ String Terminator
OSC ESC ] Operating System Command
HTS ESC H Character Tabulation Set
import 'package:ansi_escape_codes/ansi.dart';

…

// Clear screen
print('Erase screen${CSI}2JScreen erased');

// Set new tabulation stops
print('$HTS  $HTS  $HTS  $HTS');
print('1\t2\t3\t4'); // 1 2 3 4
print('${CSI}3g'); // Reset tabulations stops to default

// Link (it doesn't work everywhere)
print('Go to ${OSC}8;;https://pub.dev/packages/ansi_escape_codes${ST}pub.dev${OSC}8;;$ST')

Control sequences (CSI)

A control sequence is a string starting with the control function CONTROL SEQUENCE INTRODUCER CSI followed by one or more bytes representing parameters, if any, and by one or more bytes identifying the control function. The control function CSI itself is an element of the C1 set.

Some control functions from this set:

Constant Code Description
CUU CSI n A Cursor up by n lines
CUD CSI n B Cursor down by n lines
CUF CSI n C Cursor right (forward) by n characters
CUB CSI n D Cursor left (backward) by n characters
CUP CSI n;m H Cursor position to n-th line, m-th character
ED CSI s J Erase in page (in display) (s=2 - entire screen)
DCH CSI n P Delete n characters
ECH CSI n X Erase n characters
TBC CSI n g Tabulation clear (s=3 - all character tabulation stops are cleared)
SM CSI s h Set mode (s=4 - INSERTION REPLACEMENT MODE)
RM CSI s l Reset mode
SGR CSI s… m Select graphic rendition
import 'package:ansi_escape_codes/ansi.dart';

…

// Cursor left by 4 characters
// Delete 1 character ('1')
// Cursor right by 1 character
// Erase 1 character ('3')
print('1234${CSI}4$CUB$CSI$DCH$CSI$CUF$CSI$ECH'); // '2 4'

// Insertion mode
print('${CSI}4${SM}tree${CSI}3${CUB}h'); // three
print('${CSI}4${RM}tree${CSI}3${CUB}h'); // thee

// Italic text
print('${CSI}3$SGR Italic text ${CSI}0$SGR');

Ready-to-use functions and constants

Ready-to-use functions and constants replace the use of control functions with the style used in Dart.

Goal Using Description
Cursor up template: ${cursorUpOpen}$n$cursorUpClose
function: cursorUpN(int n)
default constant: cursorUp
Moves the cursor up n (default 1) lines.
Cursor down template: ${cursorDownOpen}$n$cursorDownClose
function: cursorDownN(int n)
default constant: cursorDown
Moves the cursor down n (default 1) lines.
Cursor forward template: ${cursorRightOpen}$n$cursorRightClose
function: cursorRightN(int n)
default constant: cursorRight
Moves the cursor right n (default 1) characters.
Cursor back template: ${cursorLeftOpen}$n$cursorLeftClose
function: cursorLeftN(int n)
default constant: cursorLeft
Moves the cursor left n (default 1) characters.
Cursor next line template: ${cursorNextLineOpen}$n$cursorNextLineClose
function: cursorNextLineN(int n)
default constant: cursorNextLine
Moves cursor to beginning of the line n (default 1) lines down.
Cursor prev line template: ${cursorPrevLineOpen}$n$cursorPrevLineClose
function: cursorPrevLineN(int n)
default constant: cursorPrevLine
Moves cursor to beginning of the line n (default 1) lines up.
Cursor horizontal pos template: ${cursorHPosOpen}$n$cursorHPosClose
function: cursorHPosN(int n)
default constant: cursorHPosToBegin
Moves the cursor to column n (default 1).
Cursor pos template: ${cursorPosOpen}$row;$col$cursorPosClose
function: cursorPosTo(int row, int col)
default constant: cursorPosToTopLeft
Moves the cursor to row and col.
Cursor horizontal and vertical pos template: ${cursorHVPosOpen}$row;$col$cursorHVPosClose
function: cursorHVPosTo(int row, int col)
default constant: cursorHVPosToTopLeft
Same as cursorPos, just with some differences.
Erase in page template: ${eraseInPageOpen}$s$eraseInPageClose
function:
default constants: erasePage, eraseInPageToBegin, eraseInPageToEnd
Erases part of the page: s=0 (or missing) - to end, s=1 - to beginning, s=2 - entire page.
Erase in line template: ${eraseInLineOpen}$s$eraseInLineClose
function:
default constant: eraseLine, eraseInLineToBegin, eraseInLineToEnd
Erases part of the line: s=0 (or missing) - to end, s=2 - to beginning, s=2 - entire line.
Scroll up template: ${scrollUpOpen}$n$scrollUpClose
function: scrollUpN(int n)
default constant: scrollUp
Scroll page up by n (default 1) lines. New lines are added at the bottom.
Scroll down template: ${scrollDownOpen}$n$scrollDownClose
function: scrollDownN(int n)
default constant: scrollDown
Scroll page down by n (default 1) lines. New lines are added at the top.
Hide cursor constant: hideCursor Shows the cursor.
Show cursor constant: showCursor Hides the cursor.
Save cursor constant: saveCursor Saves the cursor position, encoding shift state and formatting attributes.
Restore cursor constant: restoreCursor Restores the cursor position, encoding shift state and formatting attributes from the previous saveCursor if any, otherwise resets these all to their defaults.

All of the following examples are equivalent:

print('\x1B[4A');
print('${CSI}4$CUU');
print('${cursorUpOpen}4$cursorUpClose');
print(cursorUpN(4)); // Not constant!

Independent control functions ESC Fs

Note

The paragraph will appear later.

Select graphic rendition (SGR)

Template for working with graphic rendition:

CSI s… SGR

Or, on Dart:

const str = '$CSI$s$SGR';

Where s is:

Value Constant Using Description
0 RESET reset Default rendition (implementation-defined), cancels the effect of any preceding occurrence of SGR
1 BOLD bold Bold or increased intensity
2 DIM dim Dim, decreased intensity or second color
3 ITALIC italic Italic
4 UNDERLINE underline Underline
5 BLINK blink Blink
6 BLINK_RAPID blinkRapid Blink rapid
7 INVERSE inverse Inverse
8 INVISIBLE invisible Invisible characters
9 STRIKETHROUGH strikethrough Strikethrough (characters still legible but marked as to be deleted)
10 PRIMARY_FONT Primary (default) font
11 ALT_FONT_1 First alternative font
12 ALT_FONT_2 Second alternative font
13 ALT_FONT_3 Third alternative font
14 ALT_FONT_4 Fourth alternative font
15 ALT_FONT_5 Fifth alternative font
16 ALT_FONT_6 Sixth alternative font
17 ALT_FONT_7 Seventh alternative font
18 ALT_FONT_8 Eighth alternative font
19 ALT_FONT_9 Ninth alternative font
20 FRAKTUR Fraktur (Gothic)
21 DOUBLY_UNDERLINE doublyUnderline Doubly underline
22 NOT_BOLD_NOT_DIM resetBoldAndDim Normal colour or normal intensity (neither bold nor dim)
23 NOT_ITALIC resetItalic Not italic, not fraktur
24 NOT_UNDERLINE resetUnderline Not underline (neither singly nor doubly)
25 NOT_BLINK resetBlink Steady (not blink)
27 NOT_INVERSE resetInverse Positive image (not inverse)
28 NOT_INVISIBLE resetInvisible Revealed characters (not invisible)
29 NOT_STRIKETHROUGH resetStrikethrough Not strikethrough
30 FG_BLACK fgBlack Black display
31 FG_RED fgRed Red display
32 FG_GREEN fgGreen Green display
33 FG_YELLOW fgYellow Yellow display
34 FG_BLUE fgBlue Blue display
35 FG_MAGENTA fgMagenta Magenta display
36 FG_CYAN fgCyan Cyan display
37 FG_WHITE fgWhite White display
38 FOREGROUND fg256…/fgRgb… Display color from 256-color table or by RGB
39 FG_DEFAULT resetFg Default display color (implementation-defined)
40 BG_BLACK bgBlack Black background
41 BG_RED bgRed Red background
42 BG_GREEN bgGreen Green background
43 BG_YELLOW bgYellow Yellow background
44 BG_BLUE bgBlue Blue background
45 BG_MAGENTA bgMagenta Magenta background
46 BG_CYAN bgCyan Cyan background
47 BG_WHITE bgWhite White background
48 BACKGROUND bg256…/bgRgb… Background color from 256-color table or by RGB
49 BG_DEFAULT resetBg Default background color (implementation-defined)
51 FRAME frame Frame
52 ENCIRCLE encircle Encircle
53 OVERLINE overline Overline
54 NOT_FRAME_NOT_ENCIRCLE resetFrameAndEncircle Not frame, not encircle
55 NOT_OVERLINE resetOverline Not overline
58 UNDERLINE_COLOR underlineColor256…/underlineColorRgb… Underline color from 256-color table or by RGB
59 UNDERLINE_COLOR_DEFAULT underlineColorDefault Default underline color
73 SUPERSCRIPTED superscript Superscript
74 SUBSCRIPT subscript Subscript
75 NOT_SUPER_NOT_SUBSCRIPT resetSuperAnsSubscript Not superscript, not subscipt
90 FG_HIGH_BLACK fgHighBlack High black display
91 FG_HIGH_RED fgHighRed High red display
92 FG_HIGH_GREEN fgHighGreen High green display
93 FG_HIGH_YELLOW fgHighYellow High yellow display
94 FG_HIGH_BLUE fgHighBlue High blue display
95 FG_HIGH_MAGENTA fgHighMagenta High magenta display
96 FG_HIGH_CYAN fgHighCyan High cyan display
97 FG_HIGH_WHITE fgHighWhite High white display
100 BG_HIGH_BLACK bgHighBlack High black background
101 BG_HIGH_RED bgHighRed High red background
102 BG_HIGH_GREEN bgHighGreen High green background
103 BG_HIGH_YELLOW bgHighYellow High yellow background
104 BG_HIGH_BLUE bgHighBlue High blue background
105 BG_HIGH_MAGENTA bgHighMagenta High magenta background
106 BG_HIGH_CYAN bgHighCyan High cyan background
107 BG_HIGH_WHITE bgHighWhite High white background

All of the following examples are equivalent:

import 'package:ansi_escape_codes/ansi.dart';

print('\x1B[1m bold \x1B[0m');
print('$CSI$BOLD$SGR bold $SCI$RESET$SGR');
print('$bold bold $reset');

256-color table

en.wikipedia.org/wiki/ANSI_escape_code#8-bit

Template for setting the color from 256-color table:

CSI FOREGROUND;COLOR_256;n SGR
CSI BACKGROUND;COLOR_256;n SGR
CSI UNDERLINE_COLOR;COLOR_256;n SGR

Or, on Dart:

const str = '$CSI$FOREGROUND;$COLOR_256;$n$SGR';
const str = '$CSI$BACKGROUND;$COLOR_256;$n$SGR';
const str = '$CSI$UNDERLINE_COLOR;$COLOR_256;$n$SGR';

Where n is:

Value Constant Using Comment
0 BLACK fg256Black
bg256Black
underline256Black
1 RED fg256Red
bg256Red
underline256Red
2 GREEN fg256Green
bg256Green
underline256Green
3 YELLOW fg256Yellow
bg256Yellow
underline256Yellow
4 BLUE fg256Blue
bg256Blue
underline256Blue
5 MAGENTA fg256Magenta
bg256Magenta
underline256Magenta
6 CYAN fg256Cyan
bg256Cyan
underline256Cyan
7 WHITE fg256White
bg256White
underline256White
8 HIGH_BLACK fg256HighBlack
bg256HighBlack
underline256HighBlack
9 HIGH_RED fg256HighRed
bg256HighRed
underline256HighRed
10 HIGH_GREEN fg256HighGreen
bg256HighGreen
underline256HighGreen
11 HIGH_YELLOW fg256HighYellow
bg256HighYellow
underline256HighYellow
12 HIGH_BLUE fg256HighBlue
bg256HighBlue
underline256HighBlue
13 HIGH_MAGENTA fg256HighMagenta
bg256HighMagenta
underline256HighMagenta
14 HIGH_CYAN fg256HighCyan
bg256HighCyan
underline256HighCyan
15 HIGH_WHITE fg256HighWhite
bg256HighWhite
underline256HighWhite
16-231 RGB_<r><g><b> fg256Rgb<r><g><b>
bg256Rgb<r><g><b>
underline256Rgb<r><g><b>
r,g,b are numbers from 0 to 5 (6 × 6 × 6 cube (216 colors): 16 + 36 × r + 6 × g + b)
232-255 GRAY<n> fg256Gray<n>
bg256Gray<n>
underline256Gray<n>
n is a number from 0 to 23 (grayscale from dark to light in 24 steps)

All of the following examples are equivalent:

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

print('\x1B[38;5;3m Yellow text \x1B[39m');
print('$CSI$FOREGROUND;$COLOR_256;$YELLOW$SGR Yellow text $CSI$FG_DEFAULT$SGR');
print('$fg256Open$YELLOW$fg256Close Yellow text $resetFg');
print('$fg256Yellow Yellow text $resetFg');
print('${fg256(YELLOW)} Yellow text $resetFg'); // Not constant!

You can also use functions to get the color index:

int rgb(int r, int g, int b); // r,g,b are numbers from 0 to 5
int gray(int level); // level is number from 0 to 23

print('${fg256(rgb(5, 5, 0))}');
print('${fg256(gray(16))}');

And use next functions to set the color from 256-color table by index:

String fg256(int index); // index is number from 0 to 255
String bg256(int index);
String underline256(int index);

All of the following examples are equivalent:

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

print('\x1B[38;5;226m Yellow text \x1B[39m');
print('$CSI$FOREGROUND;$COLOR_256;$RGB_550$SGR Yellow text $CSI$FG_DEFAULT$SGR');
print('$fg256Open$RGB_550$fg256Close Yellow text $resetFg');
print('$fg256Rgb550 Yellow text $resetFg');
print('${fg256(RGB_550)} Yellow text $resetFg'); // Not constant!
print('${fg256(rgb(5, 5, 0))} Yellow text $resetFg'); // Not constant!

24-bit RGB colors

en.wikipedia.org/wiki/ANSI_escape_code#24-bit

Template for setting the color from 256-color table:

CSI FOREGROUND;COLOR_RGB;r;g;b SGR
CSI BACKGROUND;COLOR_RGB;r;g;b SGR
CSI UNDERLINE_COLOR;COLOR_RGB;r;g;b SGR

Or, on Dart:

const str = '$CSI$FOREGROUND;$COLOR_RGB;$r;$g;$b$SGR';
const str = '$CSI$BACKGROUND;$COLOR_RGB;$r;$g;$b$SGR';
const str = '$CSI$UNDERLINE_COLOR;$COLOR_RGB;$r;$g;$b$SGR';

Where r, g and b are the corresponding color components in the RGB form.

You can use next functions to set the color by RGB:

String fgRgb(int r, int g, int b); // r,g,b are numbers from 0 to 255
String bgRgb(int r, int g, int b);
String underlineRgb(int r, int g, int b);

All of the following examples are equivalent:

print('\x1B[48;2;44;43;124m Ultramarine \x1B[49m');
print('$CSI$BACKGROUND;$COLOR_RGB;44;43;124$SGR Ultramarine $CSI$BG_DEFAULT$SGR');
print('${bgRgbOpen}44;43;124$bgRgbClose Ultramarine $resetBg');
print('${bgRgb(44, 43, 124)} Ultramarine $resetBg'); // Not constant!

Analyzing and parsing

Parser

Parser allows you to analyze text containing escape codes:

import 'package:ansi_escape_codes/ansi_escape_codes.dart';

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

In this way we can, for example, remove all escape codes:

final parser = Parser('$bold Bold $fgCyan Bold+cyan $resetBoldAndDim Cyan ');
final buf = StringBuffer();
for (final m in parser.matches) {
  switch (m.entity) {
    case Text(:final string):
      buf.write(string);
    case EscapeCode():
      break;
  }
}
print(buf); // ' Bold  Bold+cyan  Cyan '

There is a ready-to-use method for this:

print(parser.removeAll());

Or replace the escape codes with a readable form:

final parser = Parser('$bold Bold $fgCyan Bold+cyan $resetBoldAndDim Cyan ');
final buf = StringBuffer();
for (final m in parser.matches) {
  final result = switch (m.entity) {
    EscapeCode(:final id) => '[$id]',
    Text(:final string) => string,
  };
  buf.write(result);
}
print(buf); // [bold] Bold [fgCyan] Bold+cyan [resetBoldAndDim] Cyan

You can also use ready-to-use methods for this:

print(parser.replaceAll((e) => '[${e.id}]'));
print(parser.showControlFunctions());

You can find out the length of plain text without escape codes using length:

print(parser.length == parser.removeAll().length); // true
print(parser.length); // 23

The style at a particular position can be found with stateAt.

final parser = Parser('$bold Bold $fgCyan Bold+cyan $resetBoldAndDim Cyan ');
final style = parser.stateAt(7);
print(style); // Style(bold, foreground: Color16(Colors.cyan))
print(style.isBold); // true
print(style.isItalic); // false
print(style.foreground?.id); // fgCyan
print(style.background?.id); // null

The position in stateAt is specified in the plaintext range (pos < parser.length) and can also point to the position behind the text (pos == parser.length) to find out the final state. The final state can also be obtained using finalState.

print(parser.stateAt(23) == parser.finalState); // true
print(parser.finalState); // Style(foreground: Color16.cyan)

In the above example, the text state was not set to default, i.e. the text was not closed:

const text = '$bold Bold $fgCyan Bold+cyan $resetBoldAndDim Cyan ';
final parser = Parser(text);
print(parser.isClosed); // false

The easiest way to close a text is to add a reset at the end of it:

const closedText = '$text$reset';
print(Parser(closedText).isClosed); // true

The substring method allows you to retrieve a piece of text by computing together its state:

final parser = Parser('$bold Bold $fgCyan Bold+cyan $resetBoldAndDim Cyan ');
final substr = parser.substring(7, maxLength: 9); // "Bold+cyan"
print(Parser(substr).ansiShowControlFunctions()); // [fgCyan;bold]Bold+cyan[reset]

By default, the substring is closed. Escape codes is always included in the string in optimized form:

final parser = Parser('$bold Bold $fgCyan Bold+cyan $resetBoldAndDim Cyan ');
final substr = parser.substring(7, maxLength: 9); // "Bold+cyan"
const test1 = '$fgCyan$bold';
final test2 = substr.substring(0, substr.indexOf('Bold'));
print(test1.showEscapeCodes()); // [CSI 36 SGR][CSI 1 SGR]
print(test2.showEscapeCodes()); // [CSI 36;1 SGR]
print(Parser(test1).showControlFunctions()); // [fgCyan][bold]
print(Parser(test2).showControlFunctions()); // [fgCyan;bold]
print(test1.length); // 9
print(test2.length); // 7

To optimize the entire string, there is an optimize method:

const text = '$fgWhite$bold$resetBoldAndDim$fgGreen$underline'
    "$resetUnderline$dim$dim What's in here? $resetBoldAndDim$resetFg";
print(text.length); // 63
final parser = Parser(text);
print(parser.showControlFunctions());
// [fgWhite][bold][resetBoldAndDim][fgGreen][underline][resetUnderline][dim][dim] What's in here? [resetBoldAndDim][resetFg]

final optimizedText = parser.optimize();
print(optimizedText.length); // 28
print(Parser(optimizedText).showControlFunctions());
// [fgGreen;dim] What's in here? [reset]

Quick analysis

You can quickly analyze a string without using Parser by using extensions.

import 'package:ansi_escape_codes/extensions.dart';

…

const text = '${fgRed}ERROR$reset';
print(text.ansiHasEscapeCodes); // true
print(text.ansiHasCsi); // true
print(text.ansiHasSgr); // true
print(text.ansiHasForeground); // true
print(text.ansiHasBackground); // false
print(text.ansiShowEscapeSequences()); // [CSI 31 SGR]ERROR[CSI 0 SGR]

The method ansiShowControlCodes allows to show all control codes from C0 set in a string:

const text = 'Tab: \t Line feed: \n Carriage return: \r Bell: \x07';

print(text.ansiShowControlCodes()); // preferStyle: ControlCodeStyle.escapeOrCharCode
// Tab: \t Line feed: \n Carriage return: \r Bell: \x07

print(text.ansiShowControlCodes(preferStyle: ControlCodeStyle.charCode));
// Tab: \x09 Line feed: \x0A Carriage return: \x0D Bell: \x07


print(text.ansiShowControlCodes(preferStyle: ControlCodeStyle.abbr));
// Tab: [HT] Line feed: [LF] Carriage return: [CR] Bell: [BEL]

print(text.ansiShowControlCodes(preferStyle: ControlCodeStyle.escapeOrAbbr));
// Tab: \t Line feed: \n Carriage return: \r Bell: [BEL]

print(text.ansiShowControlCodes(preferStyle: ControlCodeStyle.unicode));
// Tab: ␉ Line feed: ␊ Carriage return: ␍ Bell: ␇

print(text.ansiShowControlCodes(preferStyle: ControlCodeStyle.escapeOrUnicode));
// Tab: \t Line feed: \n Carriage return: \r Bell: ␇

You can quickly remove all codes using the methods:

const text =
    '$saveCursor$cursorRight$italic$bgGreen$fgYellow Text $resetFg$resetBg$resetItalic$restoreCursor';
print(Parser(text).showControlFunctions());
// [saveCursor][CSI CUF][italic][bgGreen][fgYellow] Text [resetFg][resetBg][resetItalic][restoreCursor]

final withoutBackground = text.ansiRemoveBackground();
print(Parser(withoutBackground).showControlFunctions());
// [saveCursor][CSI CUF][italic][fgYellow] Text [resetFg][resetItalic][restoreCursor]

final andWithoutForeground = withoutBackground.ansiRemoveForeground();
print(Parser(andWithoutForeground).showControlFunctions());
// [saveCursor][CSI CUF][italic] Text [resetItalic][restoreCursor]

final andWithoutSgr = andWithoutForeground.ansiRemoveSgr();
print(Parser(andWithoutSgr).showControlFunctions());
// [saveCursor][CSI CUF] Text [restoreCursor]

final andWithoutCsi = andWithoutSgr.ansiRemoveSgr();
print(Parser(andWithoutCsi).showControlFunctions());
// [saveCursor] Text [restoreCursor]

final withoutAllEscapeCodes = text.ansiRemoveEscapeCodes();
print(withoutAllEscapeCodes.ansiShowEscapeSequences());
// ' Text '

Printer

Escape codes do not allow you to set default values for your text. The foreground and background colors depend on the implementation of the terminal you are using. And so if you want to use some other values, you cannot use resetFg (CSI FOREGROUND_DEFAULT SGR) and resetBg (CSI BACKGROUND_DEFAULT SGR). Each time you will have to substitute your own values instead:

const text =
    '$bg256Rgb113$fg256Rgb442 Default text '
    '$bgWhite$fgBlack Highlighted text '
    '$bg256Rgb113$fg256Rgb442 Default text again $reset';
print(text);

You can move the color setting to constants and use them everywhere:

const defaultStyle = '$bg256Rgb113$fg256Rgb442';
const text = '$defaultStyle Default text '
    '$bgWhite$fgBlack Highlighted text '
    '$defaultStyle Default text again $reset';
print(text);

Or you can use Printer:

const text = ' Default text '
    '$bgWhite$fgBlack Highlighted text $reset'
    ' Default text again';
final printer = Printer(
  defaultStyle: const Style(
    background: Color256.rgb113,
    foreground: Color256.rgb442,
  ),
);
printer.print(text);

The printer itself will substitute the correct values where the state returns to default. The texts will remain clean, and you can change the default values or remove them altogether at any time.

Additionally, Dart allows you to use zones to hide the use of the printer under the hood:

void main() {
  runZonedPrinter(
    defaultStyle: const Style(
    background: Color256.rgb113,
    foreground: Color256.rgb442,
    ),
    () {
      // … Your application code …

      const text = ' Default text '
          '$bgWhite$fgBlack Highlighted text $reset'
          ' Default text again';

      print(text); // Use the usual print
    },
  );
}

All calls to the print function will be intercepted and modified to use the values you need.

If you need the codes for debugging Flutter apps, you'll notice that when debugging iOS apps, the console will receive messages with escaped escape codes in them. This is a known issue and is currently (02.2025) unresolved: https://github.com/flutter/flutter/issues/20663. There is no way around this issue. But there are two ways to minimize it.

The first way is to use the log method from 'dart:developer'. The log outputs the escape codes on iOS correctly:

import 'dart:developer';

…

runZonedPrinter(
  defaultState: const Style(
    background: Color16.green,
    foreground: Color16.yellow,
  ),
  output: log,
  () {
    const text = ' Default text '
        '$bgWhite$fgBlack Highlighted text $resetBg$resetFg'
        ' Default text again $reset';
    print(text);
  },
);

Unfortunately, the log method outputs long messages (more than 128 characters) as <collected>. And it is easy to exceed the allowed size when using escape codes. In the example above, the text does not fit in this size if RGB colors are used.

And secondly, log works only from IDE. Testers who don't use IDE won't see anything in the console.

So in most cases on iOS, it's left to disable escape codes for the most part:

runZonedPrinter(
  defaultStyle: …,
  ansiCodesEnabled: !Platform.isIOS,
  () {
    const text = ' Default text '
        '$bgWhite$fgBlack Highlighted text $reset'
        ' Default text again';
    print(text);
  },
);

StackedPrinter

Escape codes allow you to do simple text decoration. But a slightly more complex design requires much more effort. One example is given above, when you need a default style different from the one provided by the terminal.

Imagine that you have a template for text into which you will insert other text, that is sent to you externally. But the person who sends you this text decides to highlight it:

String makeMessage(String name) {
  const template = 'Dear {name}! We are pleased to present to you …';

  return template.replaceAll('{name}', name);
}

…

const name = '${bold}Sam$resetBoldAndDim';

…

final text = makeMessage(name);
print(text);
// Dear [bold]Sam[resetBoldAndDim]! We are pleased to present to you …

Without noticing it, at some point your designer decides to make changes to the template:

const template = '${bold}Dear {name}, welcome to us!$resetBoldAndDim We are pleased to present to you …';

…

final text = makeMessage(name);
print(text);
// [bold]Dear [bold]Sam[resetBoldAndDim], welcome to us![resetBoldAndDim] We are pleased to present to you …

But the escape codes don't accumulate, double bold equals single bold. And first resetBoldAndDim cancels the bold text. And we don't get what we want at all. To fix it, we need to return the state of the text after insertion to the state it was before insertion. But it makes it much more difficult to use the escape codes. StackedPrinter helps solve this problem:

final printer = StackedPrinter();
printer.print(text);
// [bold]Dear Sam, welcome to us![resetBoldAndDim] We are pleased to present to you …

StackedPrinter accumulates state changes and sequentially disables them, translating the current state into the standard escape sequence on output:

const text = '$bold 1 $bold 2 $bold 3 $resetBoldAndDim 2 $resetBoldAndDim 1 $resetBoldAndDim';
final printer1 = Printer();
final printer2 = StackedPrinter();
printer1.print(text); // '[bold] 1  2  3 [resetBoldAndDim] 2  1 '
printer2.print(text); // '[bold] 1  2  3  2  1 [resetBoldAndDim]'