ansi_escape_codes
It's yet another one of many packages to work with ANSI escape codes. But there are a few key features:
- focused on using constants instead of functions, methods and classes
- analyzing and parsing strings containing escape codes
Table of contents
-
Control function constants and predefined values
1.2. Control functions ESC Fe (C1 set)
1.4. Predefined values
1.5. Independent control functions ESC Fs
1.6. Select graphic rendition (SGR)
1.7. 256-color table
1.8. 24-bit RGB colors
-
2.1. AnsiParser
2.2. Quick analysis
2.3. AnsiPrinter
2.4. Stacked AnsiPrinter
1. Control function constants and predefined values
Strings containing ANSI escape codes can be constants:
const text = '$fgGreen Green text $resetFg'
'$bgYellow Yellow background $resetBg'
'$bold Bold text $resetBoldAndFaint'
'$italicized Italicized text $resetItalicized'
'$underlined Underlined text $resetUnderlined';
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:
import 'package:ansi_escape_codes/controls.dart';
…
const text1 = '\x1B[38;2;255;128;0m Orange text \x1B[0m';
const text2 = '$ESC[38;2;255;128;0m Orange text $ESC[0m';
const text3 = '${CSI}38;2;255;128;0$SGR Orange text ${CSI}0$SGR';
const text4 = '$CSI$FOREGROUND;$COLOR_RGB;255;128;0$SGR Orange text $CSI$RESET$SGR';
print(text1 == text2); // true
print(text2 == text3); // true
print(text3 == text4); // true
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.
1.1. 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/controls.dart';
…
print('\t\r\n' == '$HT$CR$LF'); // true
1.2. 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/controls.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')
1.3. 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/controls.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
// Italicized text
print('${CSI}3$SGR Italicized text ${CSI}0$SGR');
1.4. Predefined values
Predefined values replace the use of control functions with the style used in Dart.
Goal | Template | Function | Default constant | Description |
---|---|---|---|---|
Cursor up | ${cursorUpOpen}$n$cursorUpClose |
cursorUpN(int n) |
cursorUp |
Moves the cursor up n (default 1) lines. |
Cursor down | ${cursorDownOpen}$n$cursorDownClose |
cursorDownN(int n) |
cursorDown |
Moves the cursor down n (default 1) lines. |
Cursor forward | ${cursorRightOpen}$n$cursorRightClose |
cursorRightN(int n) |
cursorRight |
Moves the cursor right n (default 1) characters. |
Cursor back | ${cursorLeftOpen}$n$cursorLeftClose |
cursorLeftN(int n) |
cursorLeft |
Moves the cursor left n (default 1) characters. |
Cursor next line | ${cursorNextLineOpen}$n$cursorNextLineClose |
cursorNextLineN(int n) |
cursorNextLine |
Moves cursor to beginning of the line n (default 1) lines down. |
Cursor prev line | ${cursorPrevLineOpen}$n$cursorPrevLineClose |
cursorPrevLineN(int n) |
cursorPrevLine |
Moves cursor to beginning of the line n (default 1) lines up. |
Cursor horizontal pos | ${cursorHPosOpen}$n$cursorHPosClose |
cursorHPosN(int n) |
cursorHPos |
Moves the cursor to column n (default 1). |
Cursor pos | ${cursorPosOpen}$row;$col$cursorPosClose |
cursorPosTo(int row, int col) |
cursorPos |
Moves the cursor to row and col . |
Cursor horizontal and vertical pos | ${cursorHVPosOpen}$row;$col$cursorHVPosClose |
cursorHVPosTo(int row, int col) |
cursorHVPos |
Same as cursorPos , just with some differences. |
Erase in page | ${eraseInPageOpen}$s$eraseInPageClose |
eraseInPage… |
Erases part of the page: s =0 (or missing) - to end (eraseInPageToEnd ), s =1 - to beginning (eraseInPageToBegin ), s =2 - entire page (erasePage ). |
|
Erase in line | ${eraseInLineOpen}$s$eraseInLineClose |
eraseInLine… |
Erases part of the line: s =0 (or missing) - to end (eraseInLineToEnd ), s =2 - to beginning (eraseInLineToBegin ), s =2 - entire line (eraseLine ). |
|
Scroll up | ${scrollUpOpen}$n$scrollUpClose |
scrollUpN(int n) |
scrollUp |
Scroll page up by n (default 1) lines. New lines are added at the bottom. |
Scroll down | ${scrollDownOpen}$n$scrollDownClose |
scrollDownN(int n) |
scrollDown |
Scroll page down by n (default 1) lines. New lines are added at the top. |
Hide cursor | hideCursor |
Shows the cursor. | ||
Show cursor | showCursor |
Hides the cursor. | ||
Save cursor | saveCursor |
Saves the cursor position, encoding shift state and formatting attributes. | ||
Restore cursor | restoreCursor |
Restores the cursor position, encoding shift state and formatting attributes from the previous saveCursor if any, otherwise resets these all to their defaults. |
print('${CSI}4$CUU' == cursorUpN(4)); // true
print('${CSI}4$CUU' == '${cursorUpOpen}4$cursorUpClose'); // true
print('$CSI$CUU' == cursorUp); // true
1.5. Independent control functions ESC Fs
The paragraph will appear later.
1.6. Select graphic rendition (SGR)
Template for working with graphic rendition:
CSI s… SGR
Where s
is:
Index | Constant | Predefined value | 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 | FAINT |
faint |
Faint, decreased intensity or second color |
3 | ITALICIZED |
italicized |
Italicized |
4 | UNDERLINED |
underlined |
Singly underlined |
5 | SLOWLY_BLINKING |
slowlyBlinking |
Slowly blinking (less then 150 per minute) |
6 | RAPIDLY_BLINKING |
rapidlyBlinking |
Rapidly blinking (150 per minute or more) |
7 | NEGATIVE |
negative |
Negative image |
8 | CONCEALED |
concealed |
Concealed characters |
9 | CROSSEDOUT |
crossedOut |
Crossed-out (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_UNDERLINED |
doublyUnderlined |
Doubly underlined |
22 | NOT_BOLD_NOT_FAINT |
resetBoldAndFaint |
Normal colour or normal intensity (neither bold nor faint) |
23 | NOT_ITALICIZED |
resetItalicized |
Not italicized, not fraktur |
24 | NOT_UNDERLINED |
resetUnderlined |
Not underlined (neither singly nor doubly) |
25 | NOT_BLINKING |
resetBlinking |
Steady (not blinking) |
27 | NOT_NEGATIVE |
resetNegative |
Positive image (not negative) |
28 | NOT_CONCEALED |
resetConcealed |
Revealed characters (not concealed) |
29 | NOT_CROSSEDOUT |
resetCrossedOut |
Not crossed out |
30 | FG_BLACK |
fgBlack |
Black display (color #0 from 256-color table) |
31 | FG_RED |
fgRed |
Red display (color #1 from 256-color table) |
32 | FG_GREEN |
fgGreen |
Green display (color #2 from 256-color table) |
33 | FG_YELLOW |
fgYellow |
Yellow display (color #3 from 256-color table) |
34 | FG_BLUE |
fgBlue |
Blue display (color #4 from 256-color table) |
35 | FG_MAGENTA |
fgMagenta |
Magenta display (color #5 from 256-color table) |
36 | FG_CYAN |
fgCyan |
Cyan display (color #6 from 256-color table) |
37 | FG_WHITE |
fgWhite |
White display (color #7 from 256-color table) |
38 | FOREGROUND |
fg256…/fgRgb… |
Display color from 256-color table or by RGB |
39 | FG_DEFAULT |
fgDefault |
Default display color (implementation-defined) |
40 | BG_BLACK |
bgBlack |
Black background (color #0 from 256-color table) |
41 | BG_RED |
bgRed |
Red background (color #1 from 256-color table) |
42 | BG_GREEN |
bgGreen |
Green background (color #2 from 256-color table) |
43 | BG_YELLOW |
bgYellow |
Yellow background (color #3 from 256-color table) |
44 | BG_BLUE |
bgBlue |
Blue background (color #4 from 256-color table) |
45 | BG_MAGENTA |
bgMagenta |
Magenta background (color #5 from 256-color table) |
46 | BG_CYAN |
bgCyan |
Cyan background (color #6 from 256-color table) |
47 | BG_WHITE |
bgWhite |
White background (color #7 from 256-color table) |
48 | BACKGROUND |
bg256…/bgRgb… |
Background color from 256-color table or by RGB |
49 | BG_DEFAULT |
bgDefault |
Default background color (implementation-defined) |
51 | FRAMED |
framed |
Framed |
52 | ENCIRCLED |
encircled |
Encircled |
53 | OVERLINED |
overlined |
Overlined |
54 | NOT_FRAMED_NOT_ENCIRCLED |
resetFramedAndEncircled |
Not framed, not encircled |
55 | NOT_OVERLINED |
resetOverlined |
Not overlined |
58 | UNDERLINE_COLOR |
underlineColor256…/underlineColorRgb… |
Underline color from 256-color table or by RGB |
59 | UNDERLINE_COLOR_DEFAULT |
underlineColorDefault |
Default underline color |
73 | SUPERSCRIPTED |
superscripted |
Superscripted |
74 | SUBSCRIPTED |
subscripted |
Subscripted |
75 | NOT_SUPER_NOT_SUBSCRIPTED |
resetSuperAnsSubscripted |
Not superscripted, not subscipted |
90 | FG_HIGH_BLACK |
fgHighBlack |
High black display (color #8 from 256-color table) |
91 | FG_HIGH_RED |
fgHighRed |
High red display (color #9 from 256-color table) |
92 | FG_HIGH_GREEN |
fgHighGreen |
High green display (color #10 from 256-color table) |
93 | FG_HIGH_YELLOW |
fgHighYellow |
High yellow display (color #11 from 256-color table) |
94 | FG_HIGH_BLUE |
fgHighBlue |
High blue display (color #12 from 256-color table) |
95 | FG_HIGH_MAGENTA |
fgHighMagenta |
High magenta display (color #13 from 256-color table) |
96 | FG_HIGH_CYAN |
fgHighCyan |
High cyan display (color #14 from 256-color table) |
97 | FG_HIGH_WHITE |
fgHighWhite |
High white display (color #15 from 256-color table) |
100 | BG_HIGH_BLACK |
bgHighBlack |
High black background (color #8 from 256-color table) |
101 | BG_HIGH_RED |
bgHighRed |
High red background (color #9 from 256-color table) |
102 | BG_HIGH_GREEN |
bgHighGreen |
High green background (color #10 from 256-color table) |
103 | BG_HIGH_YELLOW |
bgHighYellow |
High yellow background (color #11 from 256-color table) |
104 | BG_HIGH_BLUE |
bgHighBlue |
High blue background (color #12 from 256-color table) |
105 | BG_HIGH_MAGENTA |
bgHighMagenta |
High magenta background (color #13 from 256-color table) |
106 | BG_HIGH_CYAN |
bgHighCyan |
High cyan background (color #14 from 256-color table) |
107 | BG_HIGH_WHITE |
bgHighWhite |
High white background (color #15 from 256-color table) |
Example:
print('${CSI}4$SGR' == underlined); // true
print('$CSI$UNDERLINED$SGR' == underlined); // true
print('$underlined Underlined text $resetUnderlined');
print('$fgYellow$bgGreen Yellow on green $resetBg$resetFg');
1.7. 256-color table
en.wikipedia.org/wiki/ANSI_escape_code#8-bit
Template for setting the color from 256-color table:
CSI FOREGROUND/BACKGROUND/UNDERLINE_COLOR;COLOR_256;n SGR
Where n
is:
Index | Constant | Predefined value | Comment |
---|---|---|---|
0 | BLACK |
(fg /bg /underline )256Black |
|
1 | RED |
(fg /bg /underline )256Red |
|
2 | GREEN |
(fg /bg /underline )256Green |
|
3 | YELLOW |
(fg /bg /underline )256Yellow |
|
4 | BLUE |
(fg /bg /underline )256Blue |
|
5 | MAGENTA |
(fg /bg /underline )256Magenta |
|
6 | CYAN |
(fg /bg /underline )256Cyan |
|
7 | WHITE |
(fg /bg /underline )256White |
|
8 | HIGH_BLACK |
(fg /bg /underline )256HighBlack |
|
9 | HIGH_RED |
(fg /bg /underline )256HighRed |
|
10 | HIGH_GREEN |
(fg /bg /underline )256HighGreen |
|
11 | HIGH_YELLOW |
(fg /bg /underline )256HighYellow |
|
12 | HIGH_BLUE |
(fg /bg /underline )256HighBlue |
|
13 | HIGH_MAGENTA |
(fg /bg /underline )256HighMagenta |
|
14 | HIGH_CYAN |
(fg /bg /underline )256HighCyan |
|
15 | HIGH_WHITE |
(fg /bg /underline )256HighWhite |
|
16-231 | RGB_<r><g><b> |
(fg /bg /underline )256Rgb<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> |
(fg /bg /underline )256Gray<n> |
n is a number from 0 to 23 (grayscale from dark to light in 24 steps) |
Example:
const text1 = '$CSI$FOREGROUND;$COLOR_256;$YELLOW$SGR Yellow text $CSI$FG_DEFAULT$SGR';
const text2 = '$fg256Open$YELLOW$fg256Close Yellow text $resetFg';
final text3 = '${fg256(YELLOW)} Yellow text $resetFg'; // Not constant!
const text4 = '$fg256Yellow Yellow text $resetFg';
print(text1 == text2); // true
print(text2 == text3); // true
print(text3 == text4); // true
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
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);
Example:
const text1 = '$fg256Rgb550 Yellow text $resetFg';
const text2 = '$fg256Open$RGB_550$fg256Close Yellow text $resetFg';
final text3 = '${fg256(RGB_550)} Yellow text $resetFg';
final text4 = '${fg256(rgb(5, 5, 0))} Yellow text $resetFg';
print(text1 == text2); // true
print(text2 == text3); // true
print(text3 == text4); // true
1.8. 24-bit RGB colors
en.wikipedia.org/wiki/ANSI_escape_code#24-bit
Template for setting the color from 256-color table:
CSI FOREGROUND/BACKGROUND/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);
Example:
const text1 = '$CSI$BACKGROUND;$COLOR_RGB;44;43;124$SGR Ultramarine $CSI$FG_DEFAULT$SGR';
const text2 = '${bgRgbOpen}44;43;124$fg256Close Ultramarine $resetFg';
final text3 = '${bgRgb(44, 43, 124)} Ultramarine $resetFg'; // Not constant!
print(text1 == text2); // true
print(text2 == text3); // true
2. Analyzing and parsing
2.1. AnsiParser
AnsiParser allows you to analyze text containing escape codes:
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)))
In this way we can, for example, remove all escape codes:
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-made method for this:
print(parser.removeAll());
Or replace the escape codes with a readable form:
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 [resetBoldAndFaint] Cyan
You can also use ready-made 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 state at a particular position can be found with stateAtPos
.
final state = parser.stateAtPos(7);
print(state); // SgrState(bold, foreground: Color16(Colors.cyan))
print(state.isBold); // true
print(state.isItalicized); // false
print(state.foreground?.id); // fgCyan
print(state.background?.id); // null
The position in stateAtPos
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.stateAtPos(23) == parser.finalState); // true
print(parser.finalState); // SgrState(foreground: Color16(Colors.cyan))
In the above example, the text state was not set to default, i.e. the text was not closed:
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(AnsiParser(closedText).isClosed); // true
The substring
method allows you to retrieve a piece of text by computing
together its state:
final substring = parser.substring(7, maxLength: 9);
print(AnsiParser(substring).showControlFunctions()); // [fgCyan;bold]Bold+cyan[reset]
By default, the substring is closed. Escape codes is always included in the string in optimized form:
const test1 = '$fgCyan$bold';
final test2 = substring.substring(0, substring.indexOf('Bold'));
print(test1.showEscapeCodes()); // [CSI 36 SGR][CSI 1 SGR]
print(test2.showEscapeCodes()); // [CSI 36;1 SGR]
print(AnsiParser(test1).showControlFunctions()); // [fgCyan][bold]
print(AnsiParser(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$resetBoldAndFaint$fgGreen$underlined'
"$resetUnderlined$faint$faint What's in here? $resetBoldAndFaint$resetFg";
print(text.length); // 63
final parser = AnsiParser(text);
print(parser.showControlFunctions());
// [fgWhite][bold][resetBoldAndFaint][fgGreen][underlined][resetUnderlined][faint][faint] What's in here? [resetBoldAndFaint][resetFg]
final optimizedText = parser.optimize();
print(optimizedText.length); // 28
print(AnsiParser(optimizedText).showControlFunctions());
// [fgGreen;faint] What's in here? [reset]
2.2. Quick analysis
You can quickly analyze a string without using AnsiParser
by using
extensions.
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]
The method showControlCodes
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.showControlCodes());
// Tab: \t Line feed: \n Carriage return: \r Bell: \x07
print(text.showControlCodes(preferStyle: ControlCodeStyle.charCode));
// Tab: \x09 Line feed: \x0A Carriage return: \x0D Bell: \x07
print(text.showControlCodes(preferStyle: ControlCodeStyle.abbr));
// Tab: [HT] Line feed: [LF] Carriage return: [CR] Bell: [BEL]
print(text.showControlCodes(preferStyle: ControlCodeStyle.unicodeSymbol));
// Tab: ␉ Line feed: ␊ Carriage return: ␍ Bell: ␇
You can quickly remove all codes using the methods:
const text = '$saveCursor$cursorRight$italicized$bgGreen$fgYellow Text $resetFg$resetBg$resetItalicized$restoreCursor';
print(AnsiParser(text).showControlFunctions());
// [saveCursor][CSI CUF][italicized][bgGreen][fgYellow] Text [resetFg][resetBg][resetItalicized][restoreCursor]
print(AnsiParser(text.removeBackground()).showControlFunctions());
// [saveCursor][CSI CUF][italicized][fgYellow] Text [resetFg][resetItalicized][restoreCursor]
print(AnsiParser(text.removeBackground().removeForeground()).showControlFunctions());
// [saveCursor][CSI CUF][italicized] Text [resetItalicized][restoreCursor]
print(AnsiParser(text.removeSgr()).showControlFunctions());
// [saveCursor][CSI CUF] Text [restoreCursor]
print(AnsiParser(text.removeCsi()).showControlFunctions());
// [saveCursor] Text [restoreCursor]
print(text.removeEscapeCodes().showEscapeCodes());
// ' Text '
2.3. AnsiPrinter
Escape codes do not allow you to set default values for your text. The
foreground ans 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 =
'${bgRgbOpen}44;43;124$bgRgbClose${fgRgbOpen}224;192;64$fgRgbClose Default text '
'$bgWhite$fgBlack Highlighted text '
'${bgRgbOpen}44;43;124$bgRgbClose${fgRgbOpen}224;192;64$fgRgbClose Default text again $reset';
print(text);
You can move the color setting to constants and use them everywhere:
const bgDefault = '${bgRgbOpen}44;43;124$bgRgbClose';
const fgDefault = '${fgRgbOpen}224;192;64$fgRgbClose';
const text = '$bgDefault$fgDefault Default text '
'$bgWhite$fgBlack Highlighted text '
'$bgDefault$fgDefault Default text again $reset';
print(text);
Or you can use 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);
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() {
runZonedAnsiPrinter(
defaultState: SgrPlainState(
background: ColorRgb(44, 43, 124),
foreground: ColorRgb(224, 192, 64),
),
() {
// … Your application code …
const text = ' Default text '
'$bgWhite$fgBlack Highlighted text '
'$resetBg$resetFg Default text again $reset';
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';
…
runZonedAnsiPrinter(
defaultState: const SgrPlainState(
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:
runZonedAnsiPrinter(
defaultState: …,
ansiCodesEnabled: !Platform.isIOS,
() {
const text = ' Default text '
'$bgWhite$fgBlack Highlighted text '
'$resetBg$resetFg Default text again $reset';
print(text);
},
);
2.4. Stacked AnsiPrinter.
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$resetBoldAndFaint';
…
final text = makeMessage(name);
print(text);
// Dear [bold]Sam[resetBoldAndFaint]! 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!$resetBoldAndFaint We are pleased to present to you …';
…
final text = makeMessage(name);
print(text);
// [bold]Dear [bold]Sam[resetBoldAndFaint], welcome to us![resetBoldAndFaint] We are pleased to present to you …
But the escape codes don't accumulate, double bold
equals single bold
. And
first resetBoldAndFaint
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. AnsiPrinter
helps solve this problem:
final printer = AnsiPrinter(stacked: true);
printer.print(text);
// [bold]Dear Sam, welcome to us![resetBoldAndFaint] We are pleased to present to you …
AnsiPrinter with the stacked
parameter 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 $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]'
That's all for now.