CLI приложения #7. ANSI-коды

Цветной вывод в консоли выглядит как что-то, для чего нужна библиотека. На самом деле ничего особо сложного там нет: цвет — это обычный текст, просто терминал показывает его не как текст, а исполняет как команду.
Можно легко убедиться одной строкой:

Console.WriteLine("\u001b[31mОшибка\u001b[0m: файл не найден");

Слово «Ошибка» станет красным, остальное — обычным. Разберём, что здесь происходит.
\u001b — это символ Escape, тот самый, что на клавише Esc. Терминал, встретив его, понимает: дальше идёт не текст для показа, а инструкция. Следом [ открывает саму инструкцию, потом число-параметр, потом буква — что с этим параметром делать. Буква m означает «поменять оформление текста».
Получается Esc + [ + 31 + m — «включи красный». А \u001b[0m — «сбрось всё обратно».
Основных кодов не так уж много и их несложно запомнить:

0  — сброс всего оформления
1  — жирный
4  — подчёркнутый

30–37 — цвет текста: чёрный, красный, зелёный,
        жёлтый, синий, пурпурный, голубой, белый
40–47 — то же самое, но для фона
90–97 — яркие варианты цветов текста

Параметры можно совмещать через точку с запятой: \u001b[1;31m — жирный красный.
Но, #мыжпрограммисты, поэтому вместо бесконечного указания в коде кодов (да-да, это тавтология) заведём helper:
csharp

internal static class Ansi
{
    private const string Esc = "\u001b[";

    public const string Reset = Esc + "0m";
    public const string Bold = Esc + "1m";
    public const string Red = Esc + "31m";
    public const string Green = Esc + "32m";
    public const string Yellow = Esc + "33m";

    // 16 миллионов цветов, если терминал умеет
    public static string Rgb(int r, int g, int b) => $"{Esc}38;2;{r};{g};{b}m";

    public static string Paint(string text, string color) => $"{color}{text}{Reset}";
}

И пользоваться становится проще, а код нагляднее и понятнее:
csharp

Console.Error.WriteLine(Ansi.Paint("Ошибка", Ansi.Red) + ": файл не найден");
Console.WriteLine(Ansi.Paint("Готово", Ansi.Green));
Console.WriteLine(Ansi.Paint("Фирменный цвет", Ansi.Rgb(0, 122, 204)));

Резонный вопрос: а зачем всё это, если в .NET есть Console.ForegroundColor? Он проще, но умеет рисовать только шестнадцать цветов и не умеет форматировать текст — ни жирного, ни подчёркивания в нём нет. Плюс это глобальное состояние: забыли вызвать Console.ResetColor() — и пользователь остался с зелёным приглашением командной строки. ANSI-последовательность живёт внутри одной строки и никуда не утекает.

Ещё нюанс: в Windows Terminal и в современных версиях PowerShell всё заработает сразу, а вот старая консоль cmd.exe может показать сами коды в виде мусора вроде ←[31m. Так что проверять стоит не только на своей машине.

И вот тут возникает главный вопрос: а что, если вывод вообще никто не видит? Если его перенаправили в файл, все эти коды окажутся внутри него мусором. Как это определить и что с этим делать? Об этом и не только поговорим в следующем посте.