{"id":485367,"date":"2026-06-28T19:50:12","date_gmt":"2026-06-28T19:50:12","guid":{"rendered":"https:\/\/savepearlharbor.com\/?p=485367"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=485367","title":{"rendered":"Colorful console output on AutoHotkey"},"content":{"rendered":"<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<h3>Problem<\/h3>\n<p>Not long ago, I started to notice that many console utilities can output colored text. I was curious if I could also add colors to the output of my console version of <a href=\"https:\/\/github.com\/JoyHak\/Launcher\" rel=\"noopener noreferrer nofollow\">Launcher<\/a>.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fe0\/f45\/889\/fe0f45889f0fe7559e47e89043ab1987.png\" width=\"1015\" height=\"985\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/fe0\/f45\/889\/fe0f45889f0fe7559e47e89043ab1987.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/fe0\/f45\/889\/fe0f45889f0fe7559e47e89043ab1987.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/figure>\n<p>The goal was to write an algorithm that can apply different colors to the fragments of the text message. In this article, we will take a detailed look at how to write such an algorithm in my favorite language &#8212; <a href=\"https:\/\/www.autohotkey.com\/docs\/v2\/Language.htm\" rel=\"noopener noreferrer nofollow\">AutoHotkey<\/a> &#8212; how to optimize this algorithm, and what to consider when writing high-level code.<\/p>\n<p><em>If you are not familiar with AutoHotkey syntax, don\u2019t worry, I\u2019ll guide you through it. After all, one of the goals of this article is to introduce you to this language.<\/em><\/p>\n<h3>Naive Solution<\/h3>\n<h4>Types of Functions<\/h4>\n<p>Let\u2019s start with the idea of an early algorithm. Our goal is to change the color of a message when it is output to the console <em>(also referred to as the terminal, as I work in the <\/em><a href=\"https:\/\/github.com\/cmderdev\/cmder\" rel=\"noopener noreferrer nofollow\"><em>Cmder<\/em><\/a><em> emulator with PowerShell)<\/em>. To do this, there must first be some markers in the message itself that will indicate the color for each part of the text. For example, HTML tags can serve as markers: <code>&lt;color&gt;text&lt;\/color&gt;<\/code>. Then we need to find all such tags in the text, extract the color name, and apply it to the console.<\/p>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c80\/b51\/6b8\/c80b516b8bddffbee94209151cfe7199.png\" alt=\"raw source code\" title=\"raw source code\" width=\"1795\" height=\"317\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/c80\/b51\/6b8\/c80b516b8bddffbee94209151cfe7199.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/c80\/b51\/6b8\/c80b516b8bddffbee94209151cfe7199.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>raw source code<\/figcaption><\/div>\n<\/figure>\n<figure class=\"full-width \"><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/89f\/fa7\/5f2\/89ffa75f253e77c8dbe75632e652d1c7.png\" alt=\"colorful text in the console\" title=\"colorful text in the console\" width=\"1372\" height=\"325\" sizes=\"auto, (max-width: 780px) 100vw, 50vw\" srcset=\"https:\/\/habrastorage.org\/r\/w780\/getpro\/habr\/upload_files\/89f\/fa7\/5f2\/89ffa75f253e77c8dbe75632e652d1c7.png 780w,&#10;       https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/89f\/fa7\/5f2\/89ffa75f253e77c8dbe75632e652d1c7.png 781w\" loading=\"lazy\" decode=\"async\"\/><\/p>\n<div><figcaption>colorful text in the console<\/figcaption><\/div>\n<\/figure>\n<p>Fortunately, Microsoft allows us to access the Windows OS and ask it to do something. For this purpose, <a href=\"https:\/\/en.wikipedia.org\/wiki\/Windows_API\" rel=\"noopener noreferrer nofollow\">Windows API<\/a> exists (Windows Application Programming Interface, also known as WinApi or simply API functions). Each function has its own documentation on the <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/console\/setconsoletextattribute\" rel=\"noopener noreferrer nofollow\">Microsoft website<\/a>. When the AutoHotkey interpreter starts, it loads dynamic-link libraries (DLL), that contains this functions. We can call this functions using the built-in language function <a href=\"https:\/\/www.autohotkey.com\/boards\/viewtopic.php?f=7&amp;t=63708&amp;p=273028&amp;hilit=dllcall+struct+constant+message+binding+msdn+Microsoft+windows+win32+terminator#p273028\" rel=\"noopener noreferrer nofollow\">DllCall<\/a>. All built-in AutoHotkey functions like <a href=\"https:\/\/www.autohotkey.com\/docs\/v2\/lib\/MsgBox.htm\" rel=\"noopener noreferrer nofollow\">MsgBox<\/a> or <a href=\"https:\/\/www.autohotkey.com\/docs\/v2\/lib\/FileAppend.htm\" rel=\"noopener noreferrer nofollow\">FileAppend<\/a> call one or another WinApi function, but if you really want to, you can \u201cgo down to a lower level\u201d and call them manually.<\/p>\n<blockquote>\n<p>Don\u2019t confuse: <strong>user functions<\/strong> are defined manually; <strong>built-in functions<\/strong> are provided by the interpreter; <strong>WinApi functions<\/strong> can only be called via DllCall.<\/p>\n<\/blockquote>\n<p>To change the color in the console, there is a WinApi function called <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/console\/setconsoletextattribute\" rel=\"noopener noreferrer nofollow\">SetConsoleTextAttribute<\/a>. It takes the console handle (its unique number\/id) and a number that \u201crepresents\u201d the color. This is not an RGB or HEX representation, but a special internal flag that we will not focus on.<\/p>\n<p>For further output to the console, there is a language function called <a href=\"https:\/\/www.autohotkey.com\/docs\/v2\/lib\/FileAppend.htm\" rel=\"noopener noreferrer nofollow\">FileAppend<\/a>, which can output a message to text or to the console (the special file name &#8212; <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/console\/console-handles\" rel=\"noopener noreferrer nofollow\">CONOUT$<\/a>).<\/p>\n<p>In the earliest version of this algorithm, I tried recursive HTML color tags processing with subsequent <code>SetConsoleTextAttribute()<\/code> calls, followed by <code>FileAppend()<\/code> calls:<\/p>\n<pre><code class=\"javascript\">Print(text, color := 'white') {    ; Dictionary\/HashMap that \"maps\" color name with it's special code.    ; \"static\" means \"initialized only once at script startup\"    ; (improves performance a bit)    static colors := Map(        'black',    0,        'blue',     1,        'green',    2,        'cyan',     3,        'red',      4,        'magenta',  5,        'yellow',   6,        'white',    7,        'gray',     8,    )        ; Get default color code, it will be used later     ; as default text color    normalColor := colors.Get(color, 7)        _Print(msg, _color := normalColor) {        ; Closure: user-defined function inside other user-defined function        ; Very useful for repeating tasks like console output        ; Get console uniq id.         ; \"static\" because console id doesn't changes,         ; Therefore we can intialize this variable only once and re-use it        static hConsole := GetOutputHandle()        DllCall(            'SetConsoleTextAttribute',             'ptr', hConsole,             'uint', _color        )                ; Append colorful text to the console        FileAppend(msg, 'CONOUT$')    }    pos := 1    while (pos &lt;= text.length) {        ; Search for color tags starting from `pos`,         ; store found result in match variable        if (RegExMatch(text, 's)&lt;(\\w+)&gt;(.*?)&lt;\/\\1&gt;', &amp;match, pos)) {            ; Print normal text before the match            normalText := text.Slice(pos, match.pos - pos)            if (normalText)                _Print(normalText)    ; call closure above                        ; Handle nested tags            ; Recursive call to this function with 2nd captured group as a message            ; and 1st captured group as a color            Print(match[2], match[1])                        ; Move position forward            pos := match.pos + match.len        } else {            ; Print remaining text            _Print(text.Slice(pos))            break        }    }}Message(msg, icon := '', normalColor := 'white') {    if IsConsole        return Print(msg '`n', normalColor)         msg := RegExReplace(msg, 's)&lt;(\\w+)&gt;(.*?)&lt;\/\\1&gt;', '$2')    return MsgBox(msg, A_ScriptName, icon)}<\/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><a href=\"https:\/\/www.autohotkey.com\/docs\/v2\/lib\/RegExMatch.htm\" rel=\"noopener noreferrer nofollow\">RegExMatch<\/a> is built-in function that <a href=\"https:\/\/habr.com\/ru\/articles\/990282\/\" rel=\"noopener noreferrer nofollow\">allows to parse text faster<\/a> without high-level language drawbacks.<\/p>\n<p>As you can see, it looks pretty simple: find the color, apply the color, output to the console, repeat. This algorithm also handles nested tags: <code>&lt;yellow&gt;my colorful&lt;cyan&gt;message&lt;\/cyan&gt;&lt;\/yellow&gt;<\/code><\/p>\n<p>Since the <code>Print()<\/code> function only processes and outputs the message, we need an additional <code>Color()<\/code> function that will add HTML tags to the text:<\/p>\n<pre><code class=\"javascript\">Color(str, regex, colorTag) {    return RegExReplace(        str,         regex,        Format('&lt;{1}&gt;$0&lt;\/{1}&gt;', colorTag)    )}<\/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:\/\/www.autohotkey.com\/docs\/v2\/lib\/RegExReplace.htm\" rel=\"noopener noreferrer nofollow\">Another built-in function<\/a> that searches for a piece of text and wraps it in tags. Minimalistic and convenient.<\/p>\n<h4>New String Methods<\/h4>\n<p>For convenience, we can <a href=\"https:\/\/www.autohotkey.com\/docs\/v2\/Objects.htm#delegation\" rel=\"noopener noreferrer nofollow\">declare additional methods for strings<\/a>, similar to those provided in Python. However, in AutoHotkey, unlike Python, we can declare any strings methods we want, such as <code>Color()<\/code> method:<\/p>\n<pre><code class=\"javascript\">'My colorful message'.Color('message', 'cyan'); or'-help, -h or -? displays help message'.Color('-\\w+', '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>As you may have guessed, the <code>Color()<\/code> method will call our <code>Color()<\/code> function with the string as 1st argument. That\u2019s it, <code>'My colorful message'.Color('message', 'cyan')<\/code> will be equivalent to <code>Color('My colorful message', 'message', 'cyan')<\/code>.<\/p>\n<p><a href=\"https:\/\/www.autohotkey.com\/docs\/v2\/lib\/Object.htm#DefineProp\" rel=\"noopener noreferrer nofollow\">This construction<\/a> declares a new <code>Color()<\/code> method for primitive of type <code>String<\/code>:<\/p>\n<pre><code class=\"javascript\">({}.DefineProp)(String.prototype, 'Color', {call: 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>In the algorithm above, you may have noticed the <code>Slice()<\/code> method and the <code>length<\/code> property. In fact, these are also \u201cadded methods\u201d that are wrappers around language functions. They make the code more readable.<\/p>\n<p>Let\u2019s see how we can use our algorithm. In codebase there is <code>PrintHelp()<\/code> function with the help messages: \u201cusage\u201d part contains tags but \u201csynopsis\u201d part is not. Below is a small part of it (since this messages are quite large):<\/p>\n<pre><code class=\"cmake\">PrintHelp() {    usage :=     (    \"&lt;gray&gt;Launch saved scripts and applications.    Copyright (c) 2026 Rafaello    https:\/\/github.com\/JoyHak\/Launcher&lt;\/gray&gt;        Usage:      launcher --param=script1&lt;gray&gt;[;script2;script3...]&lt;\/gray&gt;      launcher --param=&lt;yellow&gt;@file&lt;\/yellow&gt;      launcher -switch      launcher &lt;cyan&gt;variable&lt;\/cyan&gt;=value\"    )    synopsis :=     (    \"Parameters:      --run     run script(s)      --close   close script(s)      --add     add script(s)      --remove  remove script(s)      --sep     set separator between scripts\"    )    synopsis := synopsis      .Color('[\\-]+[\\-\\w]+', 'cyan')        ; String concatenation: a . b    Message(usage . synopsis)<\/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>usage<\/code> message already contains color tags, <code>Color()<\/code> is applied only to the <code>synopsis<\/code> message and colors the <code>--switches<\/code>.<\/p>\n<p>It looks good. But what lies under the hood of these functions and how many operations do they perform on each call? The <code>FileAppend()<\/code> function repeatedly opens and closes access to the console. The <code>SetConsoleTextAttribute<\/code> function performs a number of operations that are unrelated to color changes: polling the console mode, checking the free output buffer, etc. Finally, there is a risk that recursion in <code>Print()<\/code> may fail with deeply nested tags and cause a stack overflow.<\/p>\n<h4>Execution Time<\/h4>\n<p>Let\u2019s try to measure the execution time of the algorithm. To do this, we will use a wrapper around the WinApi <a href=\"https:\/\/learn.microsoft.com\/en-us\/windows\/win32\/api\/profileapi\/nf-profileapi-queryperformancecounter\" rel=\"noopener noreferrer nofollow\">QueryPerformanceCounter <\/a>function:<\/p>\n<pre><code class=\"javascript\">Timer(_start := false) {    static previous  := 0,            frequency := 0,            current   := DllCall(\"QueryPerformanceFrequency\", \"Int64*\", &amp;frequency)        _result := !DllCall(\"QueryPerformanceCounter\", \"Int64*\", &amp;current)    if _start {        previous := current        _result += current \/ frequency    } else {        _result += (current - previous) \/ frequency    }        return _result * 1000  ; seconds to milliseconds}<\/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 usage is simple: <code>Timer(1)<\/code> starts a timer, and <code>Timer(0)<\/code> stops it and returns the time in milliseconds. Let\u2019s measure the execution time of the large help message processing and write this time to the file:<\/p>\n<pre><code class=\"javascript\">PrintHelp() {; ...Timer(1)    synopsis := synopsis      .Color('[\\-]+[\\-\\w]+', 'cyan'); switches          examples := examples      .Color('(mainDir|AhkDir)(?=\\=)', 'cyan') ; variables names      .Color('%[^%]+%',                'blue') ; variables values      .Color('@(file|list\\.ini)',      'yellow') ; list        Message(usage . synopsis . examples)    time := Timer(0)        FileAppend(        Format('Color tags, multiple SetConsoleTextAttribute() calls: {:.4f}ms`n', time),         'C:\\Temp\\launcher_bench.log'    )<\/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>Since output to the terminal works for executable files only, we will first build our <code>launcher.ahk<\/code> into <code>launcher.exe<\/code> and save it in the <code>v1<\/code> directory. After running <code>&amp;\"C:\\Temp\\v1\\launcher.exe\" -help<\/code> in the terminal we can see the 77.6859ms exec. time in the file.<\/p>\n<p>Not bad, although we can see that text appears part by part. Can we optimize this code?<\/p>\n<h3>Efficient Solution<\/h3>\n<h4>ANSI Codes<\/h4>\n<p>As I wrote above, the slowest part here is the combination of <code>SetConsoleTextAttribute<\/code> and <code>FileAppend<\/code>. It would be great to leave only one call to <code>FileAppend<\/code>. But that means we\u2019ll have to preprocess the message manually, without calling <code>SetConsoleTextAttribute<\/code>.<\/p>\n<p>I found out that <code>SetConsoleTextAttribute<\/code> adds <a href=\"https:\/\/en.wikipedia.org\/wiki\/ANSI_escape_code\" rel=\"noopener noreferrer nofollow\">special codes<\/a> to the message. Although they are historically called <a href=\"https:\/\/en.wikipedia.org\/wiki\/ANSI_escape_code\" rel=\"noopener noreferrer nofollow\">ANSI codes<\/a>, they are actually an extension of the current encoding (usually Windows-1252).<\/p>\n<p>Furthermore, the function itself is quite limited, as the number of these codes (and encoding methods) is much larger. My <a href=\"https:\/\/github.com\/cmderdev\/cmder\" rel=\"noopener noreferrer nofollow\">Cmder<\/a> emulator, like many other good terminal emulators, is capable of processing these codes and displaying 256+ colors. I\u2019m not sure if Windows Terminal is capable of this.<\/p>\n<p>Thus, our algorithm must be able to append ANSI codes to the message. For simplicity, we will use a <a href=\"https:\/\/gist.github.com\/JBlond\/2fea43a3049b38287e5e9cefc87b2124\" rel=\"noopener noreferrer nofollow\">single-byte character set<\/a>. This codes looks like this: <code>\\e[0;31m<\/code> &#8212; red, <code>\\e[0;34m<\/code> &#8212; blue, etc. It also allows to encode text emphasis, like <code>\\e[1;31m<\/code> &#8212; bold red or <code>\\e[0;41m<\/code> &#8212; red background, but we\u2019ll only look at the foreground colors<\/p>\n<p>To color the message, it is enough to \u201cwrap\u201d the found text part with the specific ANSI code according to the found color tag: <code>\\e[1;31mmessage\\e[0m<\/code> where <code>\\e[0m<\/code> is the \u201cstop\u201d code. The text will be rendered with default color after this code (white by default, but depends on the emulator settings).<\/p>\n<p>In that case, let\u2019s rewrite our algorithm so that it adds this codes. We\u2019ll give each code a meaningful name, which we\u2019ll expect as the <code>color<\/code> parameter in the input:<\/p>\n<pre><code class=\"javascript\">Color(text, color := 'white') {    ; Dictionary\/HashMap that \"maps\" color name with ANSI code.    static colors := Map(        'black',    30,        'red',      31,        'orange',   33,        'magenta',  35,        'gray',     90,        'crimson',  91,        'green',    92,        'yellow',   93,        'blue',     94,        'purple',   95,        'cyan',     96,    )        ; Default color for this part of the text    normalColor := colors.Get(color, 37)        static esc := Chr(27)     ; ASCI escape character \\e    static end := esc '[0m'   ; Stop color processing: \\e[0m     begin  := esc '[0;' normalColor 'm'  ; Start color processing, code like \\e[0;37m                 clrText := ''  ; result: colored message    pos := 1       ; text beginning        while (pos &lt;= text.length) {        ; Search for color tags starting from `pos`,         ; store found result in match variable        if (RegExMatch(text, 's)&lt;(\\w+)&gt;(.*?)&lt;\/\\1&gt;', &amp;match, pos)) {            ; Normal text before the match with default\/input color             ; (depends on recursion level)            clrText .= begin . text.Slice(pos, match.pos - pos) . end                        ; Nested tags            clrText .= Output(match[2], match[1])                        ; Move position forward            pos := match.pos + match.len        } else {            ; Remaining text            clrText .= begin . text.Slice(pos) . end            break        }    }        return clrText}Print(msg, icon := '') {    if IsConsole        return FileAppend(msg '`n', 'CONOUT$')       msg := RegExReplace(msg, 's)&lt;(\\w+)&gt;(.*?)&lt;\/\\1&gt;', '$2')    return MsgBox(msg, A_ScriptName, icon)}<\/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 has become much simpler. Instead of two independent algorithms for text highlighting and parsing, we got one small algorithm in the <code>Color()<\/code> and a small algorithm in the <code>Print()<\/code>, which only outputs the finished message with one <code>FileAppend()<\/code> call.<\/p>\n<p>After running <code>&amp;\"C:\\Temp\\v2\\launcher.exe\" -help<\/code> we\u2019ll see 3.5338ms. 21.9836 times faster than naive solution! Nice example of why many functions calls are bad.<\/p>\n<h3>Solution Without Tags<\/h3>\n<p>Despite the convenient algorithm, we still have some drawbacks:<\/p>\n<ol>\n<li>\n<p>HTML tags are inconvenient to write by hand in some text editors like <a href=\"https:\/\/github.com\/JoyHak\/awesome-notepad-plus-plus\" rel=\"noopener noreferrer nofollow\">Notepad++<\/a>.<\/p>\n<\/li>\n<li>\n<p>HTML tags lengthen the message and slow down parsing.<\/p>\n<\/li>\n<\/ol>\n<p>Let\u2019s try to reduce the number of characters in the help text. If you are familiar with <a href=\"https:\/\/github.com\/JoyHak\/MarkdownToBBCode\" rel=\"noopener noreferrer nofollow\">Markdown<\/a>, you know the text formatting symbols: # __ `<\/p>\n<p>When rendering a <code>.md<\/code> document, they disappear to focus attention on the text using formatting rather than symbols.<\/p>\n<p>Instead of HTML tags, we can create a similar syntax like <code>#header#<\/code>, <code><strong>magenta<\/strong><\/code> etc., which is easy to parse:<\/p>\n<pre><code class=\"javascript\">PrintHelp(*) {; ...    msg := usage . synopsis . examples        Timer(1)    msg :=       msg        .Color('(@(file|list\\.ini))',    'yellow')   ; list        .Color('(mainDir|AhkDir)(?=\\=)', 'purple')   ; variables names        .Color('(%[^%]+%)',              'blue')     ; variables values        .Color('(\\-+[\\-\\w]+)(?=[ =])',   'cyan')     ; switches        .Color('\\*\\*([^\\*]+)\\*\\*',       'crimson')          .Color('__([^_]+)__',            'magenta')          .Color('(~)',                    'gray')        .Color('(``)',                   'green')         .Color('(#)',                    'orange')      .Print()          time := Timer(0)        FileAppend(        Format('ANSI codes, multiple .Color() calls: {:.4f}ms`n', time),         'C:\\Temp\\launcher_bench.log'    )}<\/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 code we expect that it is possible to color the text inside the characters by passing the corresponding expression and the expected color. For example, for blue text surrounded by percent signs <code>%<\/code>, we pass pattern like <code>(%[^%]+%)<\/code>.<\/p>\n<p>To support these expectations, it is necessary to rewrite the <code>Color()<\/code>:<\/p>\n<pre><code class=\"javascript\">Color(msg, regex, color) {    ; Dictionary\/HashMap that \"maps\" color name with ANSI code.    static colors := Map(        'black',    30,        'red',      31,        'orange',   33,        'magenta',  35,        'gray',     90,        'crimson',  91,        'green',    92,        'yellow',   93,        'blue',     94,        'purple',   95,        'cyan',     96,    )        static esc := Chr(27)     ; ASCI escape character \\e    static end := esc '[0m'   ; Stop color processing: \\e[0m     begin := esc '[0;' colors.Get(color, 37) 'm'    ; Current text color code        ; Parse the message    pos := 1    len := msg.length    clrMsg := ''  ; result (colorful message)        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)        ; Apply color code        clrMsg .= begin . match[1] . end        ; Move position forward        pos    := match.pos + match.len       }        return clrMsg}Print(msg, icon := '') {    if IsConsole        return FileAppend(msg '`n', 'CONOUT$')       msg := RegExReplace(str, 'U)' esc '\\[\\d+(;\\d+)?m')    return MsgBox(msg, A_ScriptName, icon)}; Declare additional String methods({}.DefineProp)(String.prototype, 'Color',  {call: Color})({}.DefineProp)(String.prototype, 'Print',  {call: Print})<\/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>Now we iterate through each part of the text using a loop and with added <code>Print()<\/code> method we can create calls chain: <code>\"My colorful message\".Color(\"message\", \"cyan\").Print()<\/code>.<\/p>\n<p>After running <code>&amp;\"C:\\Temp\\v3\\launcher.exe\" -help<\/code> we\u2019ll see 2.5428 ms. Awesome! The algorithm has gone from recursive to iterative, and readability has improved slightly.<\/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>The first solution is not always the best one. Speed matters if you\u2019re writing a small AutoHotkey script. But if you\u2019re working on full-fledged projects that handle complex tasks, it makes sense to take a step back and try to make the codebase more readable and the algorithms more efficient.<\/p>\n<p>In this article, we\u2019ve seen that AutoHotkey is not a primitive language for writing macros and remapping keys. It\u2019s a complete programming language on which you can write algorithms and your own syntax sugar.<\/p>\n<p>In the next article, we\u2019ll try to further optimize this algorithm, explore nested ANSI colors, and look at some interesting features 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\/1053052\/\">https:\/\/habr.com\/ru\/articles\/1053052\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>ProblemNot long ago, I started to notice that many console utilities can output colored text. I was curious if I could also add colors to the output of my console version of Launcher.The goal was to write an algorithm that can apply different colors to the fragments of the text message. In this article, we will take a detailed look at how to write such an algorithm in my favorite language &#8212; AutoHotkey &#8212; how to optimize this algorithm, and what to consider when writing high-level code.If you are not familiar with AutoHotkey syntax, don\u2019t worry, I\u2019ll guide you through it. After all, one of the goals of this article is to introduce you to this language.Naive SolutionTypes of FunctionsLet\u2019s start with the idea of an early algorithm. Our goal is to change the color of a message when it is output to the console (also referred to as the terminal, as I work in the Cmder emulator with PowerShell). To do this, there must first be some markers in the message itself that will indicate the color for each part of the text. For example, HTML tags can serve as markers: &lt;color&gt;text&lt;\/color&gt;. Then we need to find all such tags in the text, extract the color name, and apply it to the console.raw source codecolorful text in the consoleFortunately, Microsoft allows us to access the Windows OS and ask it to do something. For this purpose, Windows API exists (Windows Application Programming Interface, also known as WinApi or simply API functions). Each function has its own documentation on the Microsoft website. When the AutoHotkey interpreter starts, it loads dynamic-link libraries (DLL), that contains this functions. We can call this functions using the built-in language function DllCall. All built-in AutoHotkey functions like MsgBox or FileAppend call one or another WinApi function, but if you really want to, you can \u201cgo down to a lower level\u201d and call them manually.Don\u2019t confuse: user functions are defined manually; built-in functions are provided by the interpreter; WinApi functions can only be called via DllCall.To change the color in the console, there is a WinApi function called SetConsoleTextAttribute. It takes the console handle (its unique number\/id) and a number that \u201crepresents\u201d the color. This is not an RGB or HEX representation, but a special internal flag that we will not focus on.For further output to the console, there is a language function called FileAppend, which can output a message to text or to the console (the special file name &#8212; CONOUT$).In the earliest version of this algorithm, I tried recursive HTML color tags processing with subsequent SetConsoleTextAttribute() calls, followed by FileAppend() calls:Print(text, color := &#8216;white&#8217;) {    ; Dictionary\/HashMap that &#171;maps&#187; color name with it&#8217;s special code.    ; &#171;static&#187; means &#171;initialized only once at script startup&#187;    ; (improves performance a bit)    static colors := Map(        &#8216;black&#8217;,    0,        &#8216;blue&#8217;,     1,        &#8216;green&#8217;,    2,        &#8216;cyan&#8217;,     3,        &#8216;red&#8217;,      4,        &#8216;magenta&#8217;,  5,        &#8216;yellow&#8217;,   6,        &#8216;white&#8217;,    7,        &#8216;gray&#8217;,     8,    )        ; Get default color code, it will be used later     ; as default text color    normalColor := colors.Get(color, 7)        _Print(msg, _color := normalColor) {        ; Closure: user-defined function inside other user-defined function        ; Very useful for repeating tasks like console output        ; Get console uniq id.         ; &#171;static&#187; because console id doesn&#8217;t changes,         ; Therefore we can intialize this variable only once and re-use it        static hConsole := GetOutputHandle()        DllCall(            &#8216;SetConsoleTextAttribute&#8217;,             &#8216;ptr&#8217;, hConsole,             &#8216;uint&#8217;, _color        )                ; Append colorful text to the console        FileAppend(msg, &#8216;CONOUT$&#8217;)    }    pos := 1    while (pos &lt;= text.length) {        ; Search for color tags starting from `pos`,         ; store found result in match variable        if (RegExMatch(text, &#8216;s)&lt;(\\w+)&gt;(.*?)&lt;\/\\1&gt;&#8217;, &amp;match, pos)) {            ; Print normal text before the match            normalText := text.Slice(pos, match.pos &#8212; pos)            if (normalText)                _Print(normalText)    ; call closure above                        ; Handle nested tags            ; Recursive call to this function with 2nd captured group as a message            ; and 1st captured group as a color            Print(match[2], match[1])                        ; Move position forward            pos := match.pos + match.len        } else {            ; Print remaining text            _Print(text.Slice(pos))            break        }    }}Message(msg, icon := &#187;, normalColor := &#8216;white&#8217;) {    if IsConsole        return Print(msg &#8216;`n&#8217;, normalColor)         msg := RegExReplace(msg, &#8216;s)&lt;(\\w+)&gt;(.*?)&lt;\/\\1&gt;&#8217;, &#8216;$2&#8217;)    return MsgBox(msg, A_ScriptName, icon)}RegExMatch is built-in function that allows to parse text faster without high-level language drawbacks.As you can see, it looks pretty simple: find the color, apply the color, output to the console, repeat. This algorithm also handles nested tags: &lt;yellow&gt;my colorful&lt;cyan&gt;message&lt;\/cyan&gt;&lt;\/yellow&gt;Since the Print() function only processes and outputs the message, we need an additional Color() function that will add HTML tags to the text:Color(str, regex, colorTag) {    return RegExReplace(        str,         regex,        Format(&#8216;&lt;{1}&gt;$0&lt;\/{1}&gt;&#8217;, colorTag)    )}Another built-in function that searches for a piece of text and wraps it in tags. Minimalistic and convenient.New String MethodsFor convenience, we can declare additional methods for strings, similar to those provided in Python. However, in AutoHotkey, unlike Python, we can declare any strings methods we want, such as Color() method:&#8217;My colorful message&#8217;.Color(&#8216;message&#8217;, &#8216;cyan&#8217;); or&#8217;-help, -h or -? displays help message&#8217;.Color(&#8216;-\\w+&#8217;, &#8216;green&#8217;)As you may have guessed, the Color() method will call our Color() function with the string as 1st argument. That\u2019s it, &#8216;My colorful message&#8217;.Color(&#8216;message&#8217;, &#8216;cyan&#8217;) will be equivalent to Color(&#8216;My colorful message&#8217;, &#8216;message&#8217;, &#8216;cyan&#8217;).This construction declares a new Color() method for primitive of type String:({}.DefineProp)(String.prototype, &#8216;Color&#8217;, {call: Color})In the algorithm above, you may have noticed the Slice() method and the length property. In fact, these are also \u201cadded methods\u201d that are wrappers around language functions. They make the code more readable.Let\u2019s see how we can use our algorithm. In codebase there is PrintHelp() function with the help messages: \u201cusage\u201d part contains tags but \u201csynopsis\u201d part is not. Below is a small part of it (since this messages are quite large):PrintHelp() {    usage :=     (    &#171;&lt;gray&gt;Launch saved scripts and applications.    Copyright (c) 2026 Rafaello    https:\/\/github.com\/JoyHak\/Launcher&lt;\/gray&gt;        Usage:      launcher &#8212;param=script1&lt;gray&gt;[;script2;script3&#8230;]&lt;\/gray&gt;      launcher &#8212;param=&lt;yellow&gt;@file&lt;\/yellow&gt;      launcher -switch      launcher &lt;cyan&gt;variable&lt;\/cyan&gt;=value&#187;    )    synopsis :=     (    &#171;Parameters:      &#8212;run     run script(s)      &#8212;close   close script(s)      &#8212;add     add script(s)      &#8212;remove  remove script(s)      &#8212;sep     set separator between scripts&#187;    )    synopsis := synopsis      .Color(&#8216;[\\-]+[\\-\\w]+&#8217;, &#8216;cyan&#8217;)        ; String concatenation: a . b    Message(usage . synopsis)The usage message already contains color tags, Color() is applied only to the synopsis message and colors the &#8212;switches.It looks good. But what lies under the hood of these functions and how many operations do they perform on each call? The FileAppend() function repeatedly opens and closes access to the console. The SetConsoleTextAttribute function performs a number of operations that are unrelated to color changes: polling the console mode, checking the free output buffer, etc. Finally, there is a risk that recursion in Print() may fail with deeply nested tags and cause a stack overflow.Execution TimeLet\u2019s try to measure the execution time of the algorithm. To do this, we will use a wrapper around the WinApi QueryPerformanceCounter function:Timer(_start := false) {    static previous  := 0,            frequency := 0,            current   := DllCall(&#171;QueryPerformanceFrequency&#187;, &#171;Int64*&#187;, &amp;frequency)        _result := !DllCall(&#171;QueryPerformanceCounter&#187;, &#171;Int64*&#187;, &amp;current)    if _start {        previous := current        _result += current \/ frequency    } else {        _result += (current &#8212; previous) \/ frequency    }        return _result * 1000  ; seconds to milliseconds}The usage is simple: Timer(1) starts a timer, and Timer(0) stops it and returns the time in milliseconds. Let\u2019s measure the execution time of the large help message processing and write this time to the file:PrintHelp() {; &#8230;Timer(1)    synopsis := synopsis      .Color(&#8216;[\\-]+[\\-\\w]+&#8217;, &#8216;cyan&#8217;); switches          examples := examples      .Color(&#8216;(mainDir|AhkDir)(?=\\=)&#8217;, &#8216;cyan&#8217;) ; variables names      .Color(&#8216;%[^%]+%&#8217;,                &#8216;blue&#8217;) ; variables values      .Color(&#8216;@(file|list\\.ini)&#8217;,      &#8216;yellow&#8217;) ; list        Message(usage . synopsis . examples)    time := Timer(0)        FileAppend(        Format(&#8216;Color tags, multiple SetConsoleTextAttribute() calls: {:.4f}ms`n&#8217;, time),         &#8216;C:\\Temp\\launcher_bench.log&#8217;    )Since output to the terminal works for executable files only, we will first build our launcher.ahk into launcher.exe and save it in the v1 directory. After running &amp;&#187;C:\\Temp\\v1\\launcher.exe&#187; -help in the terminal we can see the 77.6859ms exec. time in the file.Not bad, although we can see that text appears part by part. Can we optimize this code?Efficient SolutionANSI CodesAs I wrote above, the slowest part here is the combination of SetConsoleTextAttribute and FileAppend. It would be great to leave only one call to FileAppend. But that means we\u2019ll have to preprocess the message manually, without calling&#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-485367","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/485367","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=485367"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/485367\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=485367"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=485367"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=485367"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}