I've always known that it was posible for simple terminal applications to output collored text, but up until recently I had no idea how. While the process is rather simple, I struggled to find a good guide on how to do it, so I decided I'd do the world a favor and write one myself.
ANSI escape sequences (which are sometimes refered to as CSI sequences) are what allow us to modify text sent to standard output. The basic syntax of these is:
ESC[<paramaters><suffix>
The ESC character is represented in code as either
\e
or \033
the paramaters
are decimal numbers separated by semicolons and the suffix is a single character
which determines how the paramaters will be interpreted. We will only be discussing
codes with the m
suffix here as that is the
suffix used when changing the color and similar properties (or the SGR paramaters) of text on standard
output, although I encourage you to look into what else
you can do with other suffixes.
The most complete list of possible SGR paramaters I've found is in the SGR section of this Wikipedia page but not everything in the table is supported universially so I'd be careful about including certain paramaters, and if you intend for a program to run on many different types of machines it is probably best to stick to the more basic options.
All of the following code examples will be presented in C++, but the use of ANSI escape sequences is not exclusive to C++, other languages should be able to use them similarly to how they are used in C++.
We should all be familiar with a simple hello world program like this:
#include <iostream>
using namespace std;
int main()
{
cout << "Hello World!\n";
return 0;
}
There is nothing special about the above hello world program. If we wanted to change the color we'd have to use an ANSI escape sequence. Codes 30 - 37 and 40 - 47 are designated to a handful of basic defined colors, so if we wanted our program to print red text we would use this cout statement insead:
cout << "\e[31mHello World!\n";
This line will get the job done, but it may produce unexpected consequenses if you plan to output anything else after that line and you don't want it to be red. Ecape sequences affect everything sent to standard output after them, including things that are outside of the code instruction they are fist called in, so it is best to use a reset code at the end of the text you are printing here. There are multiple different reset codes which can affect different SGR paramaters, but it is simplest to just use 0 which resets everything. Our line would look like this with a reset code:
cout << "\e[31mHello World!\n\e[0m";
So now we know how to change the color of our text, but what if we want to do more? Looking back at the basic syntax of an ANSI escape sequence, we can specify multiple paramaters in a single sequence, if we wanted our text to be printed both red and bold we would simply add the bold code (1) to the one where we put the red code:
cout << "\e[31;1mHello World!\n\e[0m";
We could also add another color into the mix if we wanted "Hello" to be red and "World!" to be green we would use this line:
cout << "\e[31;1mHello \e[32mWorld!\n\e[0m";
Notice that I left the bold code in the first escape sequence. The 32 (the green code) in the second escape seqence will overwrite the 31 from the first but not the 1 so "World!" will still be printed in bold letters even though there is no 1 in the escape sequence closest to it.
Because an escape sequence will only overwrite a previous one if their actions are mutually exclusive, you don't have to put multiple paramaters in one sequence if you don't want to. The following three lines of code produce the same output:
cout << "\e[31;1mHello World!\n\e[0m";
cout << "\e[31m\e[1mHello World!\n\e[0m";
cout << "\e[31m" << "\e[1m" << "Hello World!\n" << "\e[0m";
While you would probably never write out one of the last two lines above, it is important to realize that they are functionally the same as the first one, because chances are you don't really want to have to write out that first one either since you probably don't want to have to keep refrencing a table to remember which SGR paramaters do what. Because those three lines are all functionally the same the following code will also do the same thing:
string red = "\e[31m";
string bold = "\e[1m";
string reset = "\e[0m";
cout << red << bold << "Hello World!\n" << reset;
It is much easier to work with variables than constantly having to refrence a table to know what does what. If you are going to be using these codes I'd reccomend defining them as global variables in whatever program you are writing. If you think you'll use them often it would be a good idea to define them all in a header file that you can refrence whenever you need to.
This was just a small part of what can be done with ANSI escape sequences. There is a lot more that can be done and I encourage you to look into them more to find out what else is possible.