{"id":485947,"date":"2026-07-02T12:28:16","date_gmt":"2026-07-02T12:28:16","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=485947"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=485947","title":{"rendered":"Control Sequences (ANSI)"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>In this series of articles, we explore the capabilities of the <a href=\"https:\/\/www.autohotkey.com\/docs\/v2\/Language.htm\" rel=\"noopener noreferrer nofollow\">AutoHotkey<\/a> language by demonstrating how to output colored text to the terminal for the console version of <a href=\"https:\/\/github.com\/JoyHak\/Launcher\" rel=\"noopener noreferrer nofollow\">Launcher<\/a>.<\/p>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/1053052\/\" rel=\"noopener noreferrer nofollow\">Part 1: Naive algorithm and its optimization<\/a> <br \/>Part 2: Control sequences (you are here) <br \/>Part 3: Regular expressions <br \/>Part 4: Text styling and coloring library<\/p>\n<p>In the previous part, we rewrote the algorithm from recursive to iterative and sped up its execution by almost 22 times. However, the final code lost support for nested colors. In this part, we will try to understand why this happened, what ANSI codes are, and how they are processed by the terminal.<\/p>\n<h3>Control sequences<\/h3>\n<h4>Terminals<\/h4>\n<p>Today, a <strong>terminal<\/strong> is synonymous with a <strong>terminal emulator<\/strong> (e.g. Cmder, Windows Terminal, Ghostty, and many others), but once upon a time, terminals were quite simple devices: screen and keyboard for communicating with a remote server. These terminals received bytes of data but had no way to distinguish between different types of data received. There was no way to control the behavior of the terminal.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/31a\/50e\/f75\/31a50ef75d6f15e9ced25c0028a4c901.png\" alt=\"DEC VT-100 Terminal\" title=\"DEC VT-100 Terminal\" width=\"900\" height=\"506\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/31a\/50e\/f75\/31a50ef75d6f15e9ced25c0028a4c901.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/31a\/50e\/f75\/31a50ef75d6f15e9ced25c0028a4c901.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>DEC VT-100 Terminal<\/figcaption><\/div>\n<\/figure>\n<p>If you search for \u201cANSI codes\u201d from the previous part, the first result you will see is the ANSI website (American National Standards Institute). The term <strong>\u201cANSI codes\u201d does not exist<\/strong>, only the institute. ANSI created a standard for escaping characters in terminals, which was later replaced by the <a href=\"https:\/\/www.iso.org\/standard\/12782.html\" rel=\"noopener noreferrer nofollow\">ISO 6429<\/a> standard. ANSI also reserved some bytes for manufacturer-specific escape codes.<\/p>\n<p>Nowadays, these codes vary across different operating systems and terminal emulators. The emulator itself is responsible for interpreting ANSI. For example, in Cmder you can change the colors and termimnal behavior when it encounters ANSI.<\/p>\n<p>In PowerShell 3.0+ (which is emulated by pwsh, Cmder, and Windows Terminal), the PSReadLine module is responsible for ANSI and interactive input. Since version 5.0, it allows changing text colors using <code>$([char]0x1b)[91m<\/code> <em>(we\u2019ll explain what this means later)<\/em>, and since version 6.0, a more convenient syntax is available via <code>$PSStyle<\/code>:<\/p>\n<pre><code class=\"powershell\">$PSStyle.Formatting.Verbose = $PSStyle.Foreground.Cyan$PSStyle.Formatting.Debug   = $PSStyle.Foreground.DarkGray<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:87px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>In Windows, there is a mechanism called <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/console\/console-virtual-terminal-sequences\" rel=\"noopener noreferrer nofollow\">Console Virtual Terminal Sequences<\/a> at the console level. Starting with Windows 10, the console supports processing of <a href=\"https:\/\/vt100.net\" rel=\"noopener noreferrer nofollow\">VT100 sequences<\/a>, which allows PowerShell to correctly interpret ANSI codes from the previous part. Thanks to this mechanism, we can output identically styled text from AutoHotkey to both <code>cmd.exe<\/code> and <code>pwsh.exe<\/code> (or any other emulator).<\/p>\n<h4>Escaped sequence<\/h4>\n<p>To tell the terminal that we want to use ANSI codes for text styling, we must use an <strong>escaped sequence<\/strong>. For this purpose, there is a special <em>escape<\/em> character. In Unix-like systems, the <em>escape<\/em> character is usually <code>\\e<\/code> or <code>\\033<\/code>. In PowerShell 6.0+ and AutoHotkey 2.0+, the <em>escape<\/em> character is <strong>`e<\/strong>. In AutoHotkey, we used <code>esc := Chr(27)<\/code> for reliability.<\/p>\n<p>In PowerShell 5.1, you must use <code>$([char]0x1B)<\/code> or <code>[char]27<\/code> instead of <strong>`e<\/strong>.<\/p>\n<p>In this article, we will use the common notation <code>\\e<\/code>.<\/p>\n<p>Escaped sequences are mixed in with the main text when you type them. However, they disappear during output, since they provide information to the terminal, not to the reader of the message. Therefore, it is important to remember that the <strong>length of a message containing escaped sequence is always greater than the length of the output message<\/strong>!<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8ca\/868\/f79\/8ca868f79633f0770cc8e8bd244f3737.gif\" alt=\"The original message at the top contains more characters than the output message at the bottom\" title=\"The original message at the top contains more characters than the output message at the bottom\" width=\"1344\" height=\"474\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8ca\/868\/f79\/8ca868f79633f0770cc8e8bd244f3737.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8ca\/868\/f79\/8ca868f79633f0770cc8e8bd244f3737.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>The original message at the top contains more characters than the output message at the bottom<\/figcaption><\/div>\n<\/figure>\n<h4>Control sequence<\/h4>\n<p>A control sequence begins with the combination of characters <code>\\e<\/code> and <code>[<\/code> &#8212; this sequence (combination) is called CSI (<strong>Control Sequence Introducer<\/strong>). It is followed by several bytes (remember that 0 is also a number). Each sequence has its own set of rules and conventions about what it does and what arguments it expects. Within the scope of this article, we will consider only one subset of control sequences &#8212; changing text color and style. They have the form <code>CSI n1 [;n2 [; ...]] m<\/code>, where <code>m<\/code> is the SGR function (<strong>Select Graphic Rendition<\/strong>), and each <code>n1<\/code>, <code>n2<\/code>, \u2026 is an SGR parameter. We are working with single-byte set (<code>\\e[n1;n2m...<\/code>), so <code>n1<\/code> is a number from 0 to 7 (text or cursor style); <code>n1<\/code> is a number from 0 to 107 (color).<\/p>\n<p>In the early ANSI standard, only 8 colors were available for changing text and background color. The <code>n2<\/code> values 30-37 for the foreground color, and 40-47 for the background color. Later, high-intensity color codes appeared: 90-97 (foreground) and 100-107 (background). This set is called \u201csingle-byte\u201d and it is what we used in the previous part.<\/p>\n<p>The control sequence also disappears from the output message, since it is a form of escaped sequence.<\/p>\n<h4>256 colors<\/h4>\n<p>If 8 colors are not enough (which is quite possible), we can use a longer sequence for a palette of 256 colors. The ANSI sequence for using 8-bit color looks as follows: <code>\\e[&lt;text | background code&gt;;5;n<\/code>, where the first code is 38 (text color) or 48 (background color); <code>n<\/code> is a color from 0 to 255. For example, the sequence <code>\\e[38;5;220m<\/code> changes the text color to dark yellow. The range of 256 colors is divided into several sections: 0\u20137 are standard colors, 8\u201315 are bright colors, 16\u2013231 is a 6\u00d76\u00d76 color cube, and 232\u2013255 are grayscale shades.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4af\/da8\/d3c\/4afda8d3cbe19beab4def6bfd483bba7.png\" alt=\"Available spectrum\" title=\"Available spectrum\" width=\"722\" height=\"478\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/4af\/da8\/d3c\/4afda8d3cbe19beab4def6bfd483bba7.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/4af\/da8\/d3c\/4afda8d3cbe19beab4def6bfd483bba7.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Available spectrum<\/figcaption><\/div>\n<\/figure>\n<p>Various examples of sequences <a href=\"https:\/\/gist.github.com\/JBlond\/2fea43a3049b38287e5e9cefc87b2124\" rel=\"noopener noreferrer nofollow\">can be found here<\/a>.<\/p>\n<h4>Sequence termination<\/h4>\n<p>If the terminating combination of the sequence is not specified, it will be applied to all subsequent text output by our code to the screen (whether in PowerShell or AutoHotkey). When running the code snippet below, you will notice that the text on the new line is still bold and inverted. Additionally, the first line break character in the prompt has changed.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/7db\/abd\/828\/7dbabd828bf4367e57dbc61b51401758.png\" alt=\"Formatting affects the entire prompt and it doesn't updates\" title=\"Formatting affects the entire prompt and it doesn't updates\" width=\"1124\" height=\"142\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/7db\/abd\/828\/7dbabd828bf4367e57dbc61b51401758.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/7db\/abd\/828\/7dbabd828bf4367e57dbc61b51401758.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Formatting affects the entire prompt and it doesn&#8217;t updates<\/figcaption><\/div>\n<\/figure>\n<p>To terminate the sequence, it is sufficient to add <code>\\e[0m<\/code> at the end. This combination resets the previously applied style, color, and so on.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f82\/e4e\/f55\/f82e4ef55a778c0ba61f647e1119d96d.png\" alt=\"Full reset\" title=\"Full reset\" width=\"810\" height=\"160\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/f82\/e4e\/f55\/f82e4ef55a778c0ba61f647e1119d96d.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/f82\/e4e\/f55\/f82e4ef55a778c0ba61f647e1119d96d.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Full reset<\/figcaption><\/div>\n<\/figure>\n<p>An alternative option is to reset each code separately. For example, the sequence <code>\\e[1;7m<\/code> will make the font bold and invert the foreground and background colors. To reset only the inversion, you should add <code>\\e[27m<\/code>. The text will remain bold but not inverted. To reset the bold text, you need to add <code>\\e[22m<\/code>. The text would no longer be inverted or bold.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b1b\/7f1\/b23\/b1b7f1b2351a8128a931b770a0b4f802.png\" alt=\"Partial reset\" title=\"Partial reset\" width=\"1118\" height=\"148\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/b1b\/7f1\/b23\/b1b7f1b2351a8128a931b770a0b4f802.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/b1b\/7f1\/b23\/b1b7f1b2351a8128a931b770a0b4f802.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>Partial reset<\/figcaption><\/div>\n<\/figure>\n<h4>Control Sequences<\/h4>\n<p>Now that we know that we need to add <code>\\e[n1;n2m<\/code> at the beginning to change foreground color (e.g. <code>\\e[0;96m<\/code>) and <code>\\e[0m<\/code> at the end, it becomes obvious that the message consists of several <strong>control sequences<\/strong>:<\/p>\n<pre><code class=\"css\">\\e[0;96m message \\e[0m<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><em>Spaces are added for readability<\/em>. Henceforth in the article, we will use the phrase <strong>control sequences<\/strong>, since <code>\\e[0;96m<\/code> is one sequence; <code>\\e[0m<\/code> is also one sequence; and the plural form indicates the presence of several such <strong>sequences<\/strong> in the message.<\/p>\n<p>Now that we have figured out what ANSI codes from the previous part actually are, we can call them by their proper name: control sequences.<\/p>\n<h3>Solution for Nested Colors<\/h3>\n<h4>State Machine<\/h4>\n<p>Let\u2019s return to our console output algorithm and understand why nested colors is not working, i.e. changing the text (foreground) color inside already colored text.<\/p>\n<p>Control sequences operate on the principle of a <strong>finite state machine with a single set of current attributes<\/strong>. The terminal stores exactly one set of active graphic properties in memory: text color, background color, and style (italic, underline, etc.). Each new sequence <strong>overwrites<\/strong> the corresponding attributes rather than pushing them onto a stack.<\/p>\n<p>Suppose we have a message that we want to color as follows:<\/p>\n<pre><code class=\"xml\">&lt;cyan&gt;text&lt;green&gt;and&lt;\/green&gt;text&lt;\/cyan&gt;<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>HTML tags are provided for readability and understanding of colors. We need to wrap the text in control sequences:<\/p>\n<pre><code class=\"cmake\">\"text and text\".Color(\".+\", \"cyan\").Color(\"and\", \"green\")<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>The <code>Color()<\/code> method will apply control sequences in the following order:<\/p>\n<pre><code class=\"css\">\\e[0;96m text \\e[0;92m and \\e[0m text \\e[0m<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<div class=\"floating-image\">\n<figure class=\"float full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ba1\/e72\/81a\/ba1e7281a11f2d19fbf8f90a87bbce29.png\" width=\"591\" height=\"142\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/ba1\/e72\/81a\/ba1e7281a11f2d19fbf8f90a87bbce29.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/ba1\/e72\/81a\/ba1e7281a11f2d19fbf8f90a87bbce29.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>Let\u2019s break down step by step how the terminal interprets this:<\/p>\n<\/div>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">Step<\/p>\n<\/th>\n<th>\n<p align=\"left\">Sequence<\/p>\n<\/th>\n<th>\n<p align=\"left\">Terminal state after application<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">1<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>\\e[0;96m<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Reset all attributes, then set the text color to cyan (96)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">2<\/p>\n<\/td>\n<td>\n<p align=\"left\">Output <code>text<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Cyan text color<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">3<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>\\e[0;92m <\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Reset all attributes. Change the text color to green (92)<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">4<\/p>\n<\/td>\n<td>\n<p align=\"left\">Output <code>and<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Green text color<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">5<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>\\e[0m<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Reset all attributes<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">6<\/p>\n<\/td>\n<td>\n<p align=\"left\">Output <code>text<\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Default color<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">7<\/p>\n<\/td>\n<td>\n<p align=\"left\"><code>\\e[0m <\/code><\/p>\n<\/td>\n<td>\n<p align=\"left\">Reset all attributes (redundant, the state has already been reset)<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>On the third step a <strong>full reset<\/strong> occurs. The terminal does not remember that the color was cyan before. Due to the lack of a stack, this information is lost. Unlike HTML\/CSS, where each element has its own set of styles and the browser calculates the cascade, the terminal works with a <strong>global state<\/strong>.<\/p>\n<p>Thus, the correct sequence looks like this:<\/p>\n<pre><code class=\"css\">\\e[0;96m text \\e[0;92m and \\e[0;96m text \\e[0m<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f5e\/22b\/612\/f5e22b61226900d7fab9a26063e1a479.gif\" width=\"1459\" height=\"456\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f5e\/22b\/612\/f5e22b61226900d7fab9a26063e1a479.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/f5e\/22b\/612\/f5e22b61226900d7fab9a26063e1a479.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>You can test control sequences on the <a href=\"https:\/\/www.ansi101.com\" rel=\"noopener noreferrer nofollow\">ansi101<\/a> and <a href=\"http:\/\/ansi.tools\" rel=\"noopener noreferrer nofollow\">ansi.tools<\/a> websites.<\/p>\n<h4>Stack<\/h4>\n<p>In previous part we developed our own syntax that tells <code>Color()<\/code> where to add colors. Let\u2019s define pairs of characters that will change the text color to cyan and green. Let these be <code>\" \"<\/code> and <code>* *<\/code> respectively:<\/p>\n<pre><code class=\"markdown\">\"text *and* text\"<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>The idea of the correct algorithm is simple. Before each character, it is necessary to add some control sequence (color). If we are inside a pair of characters (e.g., <code>\" \"<\/code>) and have not found any new <em>nested<\/em> pair (<code>* *<\/code>), we simply add the opening and closing sequence before the opening and after the closing quote, respectively:<\/p>\n<pre><code class=\"bash\">\\e[96m\"# level 1text and text\"\\e[0m<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>For each nested pair <code>* <\/code><em> found inside <\/em><code><em>\" \"<\/em><\/code><em> we switch the color: open a new one for the opening <\/em> and switch to the previous one for <code>\"<\/code>:<\/p>\n<pre><code class=\"css\">\\e[96m  &lt;- own color\"  ; #1text     \\e[92m   &lt;- own color    *         ; #2      and     *    \\e[96m   &lt;- previous color    text\"\\e[0m   &lt;- no color<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>This means that the <strong>closing color<\/strong> of each pair (the nesting problem we are trying to solve) <strong>depends on the nesting level<\/strong> of that pair: each subsequent level is closed with the color of the level above. And so on until the first top-level pair found that is not nested:<\/p>\n<div>\n<div class=\"table\">\n<table>\n<tbody>\n<tr>\n<th>\n<p align=\"left\">Level<\/p>\n<\/th>\n<th>\n<p align=\"left\">Symbol<\/p>\n<\/th>\n<th>\n<p align=\"left\">Color<\/p>\n<\/th>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">1<\/p>\n<\/td>\n<td>\n<p align=\"left\">&#171;<\/p>\n<\/td>\n<td>\n<p align=\"left\">Cyan<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">2<\/p>\n<\/td>\n<td>\n<p align=\"left\">*<\/p>\n<\/td>\n<td>\n<p align=\"left\">Green<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">2<\/p>\n<\/td>\n<td>\n<p align=\"left\">*<\/p>\n<\/td>\n<td>\n<p align=\"left\">Cyan<\/p>\n<\/td>\n<\/tr>\n<tr>\n<td>\n<p align=\"left\">1<\/p>\n<\/td>\n<td>\n<p align=\"left\">&#171;<\/p>\n<\/td>\n<td>\n<p align=\"left\">0<\/p>\n<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<\/div>\n<p>For a top-level pair, there is no previous level, so it can be closed with <code>\\e[0m<\/code> or its own color <code>\\e[96m<\/code>. This entire table can be simplified to a simple stack that only stores the found characters:<\/p>\n<pre><code class=\"bash\">\"**\"<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>We can get the previous character from the top of the stack and switch to it\u2019s color, that can be found in <code>chrColors<\/code> &#8212; a HashMap with \u201ccharacter-code\u201d pairs:<\/p>\n<pre><code class=\"cmake\">\" 96* 92<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>The algorithm looks like this:<\/p>\n<pre><code class=\"coffeescript\">stack := []while (pos &lt;= len) {    if !RegExMatch(msg, regex, &amp;match, pos) {        ; Remaining text        clrMsg .= msg.Slice(pos)        break    }        ; Normal text before the match    clrMsg .= msg.Slice(pos, match.pos - pos)    if (stack.Has(-1) &amp;&amp; stack[-1] = match[1]) {        clrMsg .= match[1] . end        stack.Pop()    } else {        begin  := esc '[0;' chrColors[match[1]] 'm'        clrMsg .= begin . match[1]        stack.Push(match[1])    }        ; Move position forward    pos := match.pos + match.len}<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p><a href=\"https:\/\/habr.com\/ru\/articles\/990282\/\" rel=\"noopener noreferrer nofollow\">You can read what is RegExMatch here<\/a>. Here is a algorithm visualization. Created using <a href=\"http:\/\/staying.fun\" rel=\"noopener noreferrer nofollow\">staying.fun<\/a><\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8c1\/67f\/2f1\/8c167f2f1b3cfa8f23da26a9be1e3d34.gif\" width=\"893\" height=\"427\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8c1\/67f\/2f1\/8c167f2f1b3cfa8f23da26a9be1e3d34.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/8c1\/67f\/2f1\/8c167f2f1b3cfa8f23da26a9be1e3d34.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>On the <a href=\"https:\/\/www.ansi101.com\" rel=\"noopener noreferrer nofollow\">ansi101<\/a>, you can see what the final <code>clrMsg<\/code> message looks like.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/580\/d5e\/586\/580d5e586fceb692ba98d68b9756d8e5.gif\" width=\"1344\" height=\"474\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/580\/d5e\/586\/580d5e586fceb692ba98d68b9756d8e5.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/580\/d5e\/586\/580d5e586fceb692ba98d68b9756d8e5.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<h4>Char-color pairs<\/h4>\n<p>Obviously, it is inconvenient to pass a HashMap with numeric codes to the <code>Color()<\/code> function. We want to pass &#171;charl-color&#187; pairs:<\/p>\n<pre><code class=\"coffeescript\">msg.Color([   '\"',  'cyan',  '*',  'green'])<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<p>In this case, we pass an array instead of a HashMap for two reasons:<\/p>\n<ol>\n<li>\n<p>The order is important to us, as it determines the priority of each color.<\/p>\n<\/li>\n<li>\n<p>This is an intermediate container that is only needed for iteration.<\/p>\n<\/li>\n<\/ol>\n<p>The array is intermediate because we convert it into a HashMap <code>chrColors<\/code> with \u201csymbol-code\u201d pairs that we can use later:<\/p>\n<pre><code class=\"coffeescript\">regex     := '' ; resulted regular expressionchars     := '' ; string of characterschrColors := Map() ; there is no shortland for HashMap index := 1loop (aRegexColor.length \/ 2) {; Input array with \"char-color\" pair is `aRegexColor` variable    str   := aRegexColor[index++]    color := aRegexColor[index++]    chars .= str    chrColors[str] := colors[color]  ; `colors` is the HashMap of \"color-code\" pairs}if chars    regex .= '([' chars '])'else    regex := regex.RTrim('|')<\/code><div class=\"code-explainer\"><a href=\"https:\/\/sourcecraft.dev\/\" class=\"tm-button code-explainer__link\" style=\"visibility: hidden;\"><img style=\"width:14px;height:14px;object-fit:cover;object-position:left;\"\/><\/a><\/div><\/pre>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/fda\/a57\/c37\/fdaa57c374c34895823798fa7ebe973c.gif\" width=\"817\" height=\"676\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/fda\/a57\/c37\/fdaa57c374c34895823798fa7ebe973c.gif 780w,&#10;       https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/fda\/a57\/c37\/fdaa57c374c34895823798fa7ebe973c.gif 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>For optimization, we combine all the passed characters into a <a href=\"https:\/\/habr.com\/ru\/articles\/990282\/\" rel=\"noopener noreferrer nofollow\">RegEx set <code>[\"*]<\/code><\/a>, so parsing will go a little faster. Then each character will serve as a key to extract the color from <code>chrColors<\/code>.<\/p>\n<p><a href=\"https:\/\/github.com\/JoyHak\/Launcher\/blob\/e666e853253b6627bd6767a743ad3f47a557bcf6\/Lib\/output.ahk#L1\" rel=\"noopener noreferrer nofollow\">The source code of the text output algorithm<\/a> is available on GitHub. And <a href=\"https:\/\/github.com\/JoyHak\/Launcher\/blob\/main\/about-colors.md\" rel=\"noopener noreferrer nofollow\">here you can read about some additional features of this algorithm<\/a>.<\/p>\n<h3>Conclusion<\/h3>\n<p>Control sequences are a legacy from the days when terminals were physical machines rather than programs. Almost every text formatting library, such as <a href=\"https:\/\/github.com\/fmtlib\/fmt\/commits\/main\/\" rel=\"noopener noreferrer nofollow\">{fmt}<\/a>, adds these invisible characters to a message. Therefore, to work with text colors and styles, we need to understand them.<\/p>\n<p>In this article, we\u2019ve seen that it\u2019s possible to read control sequences and understand their meaning; we learned how the terminal interprets them, so we can \u201cask\u201d it to do exactly what we need.<\/p>\n<p>In the next article, we will add support for atomic expressions and explore some interesting capabilities of regular expressions. <a href=\"https:\/\/github.com\/JoyHak?tab=repositories\" rel=\"noopener noreferrer nofollow\">Check out my GitHub<\/a> if you want to see the AutoHotkey capabilities in action!<\/p>\n<\/div>\n<p>\u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/1054890\/\">https:\/\/habr.com\/ru\/articles\/1054890\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this series of articles, we explore the capabilities of the AutoHotkey language by demonstrating how to output colored text to the terminal for the console version of Launcher.Part 1: Naive algorithm and its optimization Part 2: Control sequences (you are here) Part 3: Regular expressions Part 4: Text styling and coloring libraryIn the previous part, we rewrote the algorithm from recursive to iterative and sped up its execution by almost 22 times. However, the final code lost support for nested colors. In this part, we will try to understand why this happened, what ANSI codes are, and how they are processed by the terminal.Control sequencesTerminalsToday, a terminal is synonymous with a terminal emulator (e.g. Cmder, Windows Terminal, Ghostty, and many others), but once upon a time, terminals were quite simple devices: screen and keyboard for communicating with a remote server. These terminals received bytes of data but had no way to distinguish between different types of data received. There was no way to control the behavior of the terminal.DEC VT-100 TerminalIf you search for \u201cANSI codes\u201d from the previous part, the first result you will see is the ANSI website (American National Standards Institute). The term \u201cANSI codes\u201d does not exist, only the institute. ANSI created a standard for escaping characters in terminals, which was later replaced by the ISO 6429 standard. ANSI also reserved some bytes for manufacturer-specific escape codes.Nowadays, these codes vary across different operating systems and terminal emulators. The emulator itself is responsible for interpreting ANSI. For example, in Cmder you can change the colors and termimnal behavior when it encounters ANSI.In PowerShell 3.0+ (which is emulated by pwsh, Cmder, and Windows Terminal), the PSReadLine module is responsible for ANSI and interactive input. Since version 5.0, it allows changing text colors using $([char]0x1b)[91m (we\u2019ll explain what this means later), and since version 6.0, a more convenient syntax is available via $PSStyle:$PSStyle.Formatting.Verbose = $PSStyle.Foreground.Cyan$PSStyle.Formatting.Debug   = $PSStyle.Foreground.DarkGrayIn Windows, there is a mechanism called Console Virtual Terminal Sequences at the console level. Starting with Windows 10, the console supports processing of VT100 sequences, which allows PowerShell to correctly interpret ANSI codes from the previous part. Thanks to this mechanism, we can output identically styled text from AutoHotkey to both cmd.exe and pwsh.exe (or any other emulator).Escaped sequenceTo tell the terminal that we want to use ANSI codes for text styling, we must use an escaped sequence. For this purpose, there is a special escape character. In Unix-like systems, the escape character is usually \\e or \\033. In PowerShell 6.0+ and AutoHotkey 2.0+, the escape character is `e. In AutoHotkey, we used esc := Chr(27) for reliability.In PowerShell 5.1, you must use $([char]0x1B) or [char]27 instead of `e.In this article, we will use the common notation \\e.Escaped sequences are mixed in with the main text when you type them. However, they disappear during output, since they provide information to the terminal, not to the reader of the message. Therefore, it is important to remember that the length of a message containing escaped sequence is always greater than the length of the output message!The original message at the top contains more characters than the output message at the bottomControl sequenceA control sequence begins with the combination of characters \\e and [ &#8212; this sequence (combination) is called CSI (Control Sequence Introducer). It is followed by several bytes (remember that 0 is also a number). Each sequence has its own set of rules and conventions about what it does and what arguments it expects. Within the scope of this article, we will consider only one subset of control sequences &#8212; changing text color and style. They have the form CSI n1 [;n2 [; &#8230;]] m, where m is the SGR function (Select Graphic Rendition), and each n1, n2, \u2026 is an SGR parameter. We are working with single-byte set (\\e[n1;n2m&#8230;), so n1 is a number from 0 to 7 (text or cursor style); n1 is a number from 0 to 107 (color).In the early ANSI standard, only 8 colors were available for changing text and background color. The n2 values 30-37 for the foreground color, and 40-47 for the background color. Later, high-intensity color codes appeared: 90-97 (foreground) and 100-107 (background). This set is called \u201csingle-byte\u201d and it is what we used in the previous part.The control sequence also disappears from the output message, since it is a form of escaped sequence.256 colorsIf 8 colors are not enough (which is quite possible), we can use a longer sequence for a palette of 256 colors. The ANSI sequence for using 8-bit color looks as follows: \\e[&lt;text | background code&gt;;5;n, where the first code is 38 (text color) or 48 (background color); n is a color from 0 to 255. For example, the sequence \\e[38;5;220m changes the text color to dark yellow. The range of 256 colors is divided into several sections: 0\u20137 are standard colors, 8\u201315 are bright colors, 16\u2013231 is a 6\u00d76\u00d76 color cube, and 232\u2013255 are grayscale shades.Available spectrumVarious examples of sequences can be found here.Sequence terminationIf the terminating combination of the sequence is not specified, it will be applied to all subsequent text output by our code to the screen (whether in PowerShell or AutoHotkey). When running the code snippet below, you will notice that the text on the new line is still bold and inverted. Additionally, the first line break character in the prompt has changed.Formatting affects the entire prompt and it doesn&#8217;t updatesTo terminate the sequence, it is sufficient to add \\e[0m at the end. This combination resets the previously applied style, color, and so on.Full resetAn alternative option is to reset each code separately. For example, the sequence \\e[1;7m will make the font bold and invert the foreground and background colors. To reset only the inversion, you should add \\e[27m. The text will remain bold but not inverted. To reset the bold text, you need to add \\e[22m. The text would no longer be inverted or bold.Partial resetControl SequencesNow that we know that we need to add \\e[n1;n2m at the beginning to change foreground color (e.g. \\e[0;96m) and \\e[0m at the end, it becomes obvious that the message consists of several control sequences:\\e[0;96m message \\e[0mSpaces are added for readability. Henceforth in the article, we will use the phrase control sequences, since \\e[0;96m is one sequence; \\e[0m is also one sequence; and the plural form indicates the presence of several such sequences in the message.Now that we have figured out what ANSI codes from the previous part actually are, we can call them by their proper name: control sequences.Solution for Nested ColorsState MachineLet\u2019s return to our console output algorithm and understand why nested colors is not working, i.e. changing the text (foreground) color inside already colored text.Control sequences operate on the principle of a finite state machine with a single set of current attributes. The terminal stores exactly one set of active graphic properties in memory: text color, background color, and style (italic, underline, etc.). Each new sequence overwrites the corresponding attributes rather than pushing them onto a stack.Suppose we have a message that we want to color as follows:&lt;cyan&gt;text&lt;green&gt;and&lt;\/green&gt;text&lt;\/cyan&gt;HTML tags are provided for readability and understanding of colors. We need to wrap the text in control sequences:&#187;text and text&#187;.Color(&#171;.+&#187;, &#171;cyan&#187;).Color(&#171;and&#187;, &#171;green&#187;)The Color() method will apply control sequences in the following order:\\e[0;96m text \\e[0;92m and \\e[0m text \\e[0mLet\u2019s break down step by step how the terminal interprets this:StepSequenceTerminal state after application1\\e[0;96mReset all attributes, then set the text color to cyan (96)2Output textCyan text color3\\e[0;92m Reset all attributes. Change the text color to green (92)4Output andGreen text color5\\e[0mReset all attributes6Output textDefault color7\\e[0m Reset all attributes (redundant, the state has already been reset)On the third step a full reset occurs. The terminal does not remember that the color was cyan before. Due to the lack of a stack, this information is lost. Unlike HTML\/CSS, where each element has its own set of styles and the browser calculates the cascade, the terminal works with a global state.Thus, the correct sequence looks like this:\\e[0;96m text \\e[0;92m and \\e[0;96m text \\e[0mYou can test control sequences on the ansi101 and ansi.tools websites.StackIn previous part we developed our own syntax that tells Color() where to add colors. Let\u2019s define pairs of characters that will change the text color to cyan and green. Let these be &#187; &#187; and * * respectively:&#187;text *and* text&#187;The idea of the correct algorithm is simple. Before each character, it is necessary to add some control sequence (color). If we are inside a pair of characters (e.g., &#187; &#171;) and have not found any new nested pair (* *), we simply add the opening and closing sequence before the opening and after the closing quote, respectively:\\e[96m&#187;# level 1text and text&#187;\\e[0mFor each nested pair *  found inside &#187; &#187; we switch the color: open a new one for the opening  and switch to the previous one for &#171;:\\e[96m  &lt;- own color&#187;  ; #1text     \\e[92m   &lt;- own color    *         ; #2      and     *    \\e[96m   &lt;- previous color    text&#187;\\e[0m   &lt;- no colorThis means that the closing color of each pair (the nesting problem we are trying to solve) depends on the nesting level of that pair: each subsequent level is closed with the color of the level above. And so on until the first top-level pair found that is not nested:LevelSymbolColor1&#8243;Cyan2*Green2*Cyan1&#8243;0For a top-level pair, there is no previous level, so it can be closed with \\e[0m or its own color \\e[96m. This entire&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-485947","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/485947","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=485947"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/485947\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=485947"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=485947"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=485947"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}