CLI приложения #15. Спиннеры и статусы

Прогресс-бар хорош, когда известен объём работы. Но огромная часть операций устроена иначе: запрос к серверу, ожидание ответа базы, сборка проекта. Сколько это займёт — неизвестно, и проценты показывать не из чего.
Показывать всё равно надо, иначе пользователь смотрит в застывший терминал и гадает, работает программа или повисла. Для этого есть спиннер — крутящийся индикатор, который говорит ровно одно: «программа работает, идёт фоновый процесс».

AnsiConsole.Status()
    .Start("Подключение к серверу...", ctx =>
    {
        Thread.Sleep(2000);
    });

Три строки, и пользователь видит вращающийся индикатор с подписью.
Самое полезное здесь — возможность менять подпись по ходу дела. Операция длинная, но её этапы обычно известны:

AnsiConsole.Status()
    .Spinner(Spinner.Known.Dots)
    .SpinnerStyle(Style.Parse("green"))
    .Start("Подключение...", ctx =>
    {
        Connect();

        ctx.Status("Загрузка метаданных...");
        LoadMetadata();

        ctx.Status("Проверка целостности...");
        Verify();
    });

Пользователь видит не просто «что-то происходит», а на каком именно шаге программа находится. Если она застрянет, сразу понятно, где именно, — и это экономит массу времени при разборе проблем.
Вариантов спиннеров в библиотеке несколько десятков: Dots, Line, Star, Arrow, Clock и другие. Посмотреть все можно на сайте библиотеки — spectreconsole.net.
Есть и более мощный инструмент — живое обновление произвольного содержимого. Не просто строка со спиннером, а целая таблица, которая меняется на глазах:

var table = new Table();
table.AddColumn("Сервис");
table.AddColumn("Статус");

AnsiConsole.Live(table)
    .Start(ctx =>
    {
        foreach (var service in services)
        {
            table.AddRow(
                Markup.Escape(service.Name),
                CheckHealth(service) ? "[green]OK[/]" : "[red]недоступен[/]");

            ctx.Refresh();
        }
    });

Таблица растёт построчно, прямо на экране. Тот же приём работает с панелями, деревьями и любым другим элементом Spectre.

Важно!
Ограничения те же, что у прогресс-бара из прошлого поста: пока активен живой блок, нельзя писать в консоль напрямую, и одновременно может работать только один такой блок. Всё, что нужно показать, меняйте внутри самого элемента и вызывайте ctx.Refresh().

В качестве совета: спиннер уместен для операций примерно от секунды-двух. Если работа занимает меньше, он мелькнёт и исчезнет — это выглядит так себе. А вот для всего, что тянется дольше пары секунд, индикатор обязателен: молчащая программа воспринимается как зависшая, даже если внутри всё идёт по плану.
На этом блок про внешний вид закончен. Дальше двинемся в сторону взаимодействия: научим приложение задавать вопросы, принимать данные из других программ и корректно реагировать на Ctrl+C.