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
- coloring: you can use ready-to-use values to create constant strings and maximize performance, or choose the power of styles.
- cursor and terminal control
- analyzing and parsing strings containing escape codes
- default style for all application output
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 |
fg256Blackbg256Blackunderline256Black |
|
| 1 | RED |
fg256Redbg256Redunderline256Red |
|
| 2 | GREEN |
fg256Greenbg256Greenunderline256Green |
|
| 3 | YELLOW |
fg256Yellowbg256Yellowunderline256Yellow |
|
| 4 | BLUE |
fg256Bluebg256Blueunderline256Blue |
|
| 5 | MAGENTA |
fg256Magentabg256Magentaunderline256Magenta |
|
| 6 | CYAN |
fg256Cyanbg256Cyanunderline256Cyan |
|
| 7 | WHITE |
fg256Whitebg256Whiteunderline256White |
|
| 8 | HIGH_BLACK |
fg256HighBlackbg256HighBlackunderline256HighBlack |
|
| 9 | HIGH_RED |
fg256HighRedbg256HighRedunderline256HighRed |
|
| 10 | HIGH_GREEN |
fg256HighGreenbg256HighGreenunderline256HighGreen |
|
| 11 | HIGH_YELLOW |
fg256HighYellowbg256HighYellowunderline256HighYellow |
|
| 12 | HIGH_BLUE |
fg256HighBluebg256HighBlueunderline256HighBlue |
|
| 13 | HIGH_MAGENTA |
fg256HighMagentabg256HighMagentaunderline256HighMagenta |
|
| 14 | HIGH_CYAN |
fg256HighCyanbg256HighCyanunderline256HighCyan |
|
| 15 | HIGH_WHITE |
fg256HighWhitebg256HighWhiteunderline256HighWhite |
|
| 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]'