TL;DR

Используя методы функционального программирования, мы можем легко создавать причудливые терминалы с динамическими компонентами на C ++. RxTerm - это библиотека C ++, которая предоставляет некоторые из необходимых строительных блоков для реализации этой концепции.

Те же идеи мы применили в Buckaroo. Вот результат:

Мотивация

Представьте, что мы хотим создать текстовое приложение, которое обновляет консоль при изменении ее состояния. Прекрасным примером этого является Curl, который отображает индикатор загрузки в реальном времени. Попробуйте это в своем теминале:

Такие интерфейсы реализованы с использованием управляющих кодов ANSI. Это невидимые символы, у которых есть побочные эффекты, такие как стирание символов и перемещение курсора.

Мы можем использовать эти escape-коды для создания текстовых интерфейсов в реальном времени. Простая реализация может обновить индикатор выполнения следующим образом:

Пока все хорошо, но что, если у нас есть более сложный случай? В идеале мы могли бы использовать библиотеку, которая разбирается в этих вещах за нас. Нам нужно только указать, как должен выглядеть вывод терминала, и библиотечные функции будут определять символы для печати, чтобы добраться до нас.

Итак, на высоком уровне нам нужны три вещи:

1. Переменная для отслеживания предыдущего состояния консоли
2. Функция для отображения текущего состояния приложения
3. Функция для преобразования консоли из предыдущего состояния в следующее состояние

Разработчикам React этот паттерн знаком. Это умная стратегия, которую можно применить к любому устройству ввода-вывода!

Кроме того, мы хотим иметь возможность использовать повторно используемые компоненты пользовательского интерфейса для таких вещей, как индикаторы выполнения, списки и т. Д. Что-то вроде HTML, но попроще, было бы идеально. Компонентом может быть любой объект, который не может быть отображен для вывода на консоль.

Пример простого API

Чтобы увидеть, к чему мы идем, давайте посмотрим на пример в RxTerm. С помощью RxTerm мы можем превратить базовые компоненты терминала в более сложные, используя композицию. Если состояние приложения изменяется, мы вычисляем новое представление для терминала и заменяем текущий видимый вывод.

В этом примере показано, как мы можем разработать новый компонент с именем fancyCounter, который печатает фреймы следующим образом:

Вот код:

Как видите, интерфейс очень высокоуровневый.

Коды выхода ANSI

Начнем с основ. Как мы можем использовать escape-коды ANSI, чтобы изменить цвет и удалить строку?

Эта функция печатает Hello красным шрифтом, а затем World цветом по умолчанию. Магические escape-последовательности (\e[31m и \e[0m) изменяют способ отображения символов терминалом.

Управляющая последовательность начинается с \e[, за которым следует список модификаторов, разделенных точкой с запятой, который заканчивается m. Например, \e[3;31;42mTEXT будет печатать TEXT красным курсивом на синем фоне.

Мы также можем напечатать \e[0m, чтобы вернуть терминал в состояние по умолчанию.

Вы можете найти весь список на bash-hackers.org.

Абстрагирование от терминального состояния

Чтобы иметь возможность составлять компоненты, нам нужно высокоуровневое представление состояния консоли. Поскольку мы хотим поддерживать цвета, интуитивно понятным представлением этого является карта от координат до пикселей:

Когда у нас есть базовая абстракция для компонентов,
мы можем воспользоваться стиранием типов, чтобы поддерживать семантику значений и сделать наследование деталью реализации:

Теперь мы можем создавать компоненты более высокого порядка, такие как объект Text:

Вы можете найти актуальную реализацию здесь.

Выполнение переходов между состояниями

Представьте, что мы хотим перейти от этого:

… к этому:

Самый простой способ - удалить последние 3 строки и распечатать новые. Мы можем удалить последнюю строку, напечатав \e[2K\r\e[1A (delete line; move cursor to the start of the line; move cursor up one line).

Более сложный подход заключался бы в вычислении разницы, перемещении в целевую позицию и редактировании только измененных конечных пикселей. Для простоты мы будем придерживаться первого подхода и объединим движущиеся части в один класс:

Фактическая реализация размещена на GitHub.

Вердикт

Мы увидели, как легко написать реактивную структуру терминала и управлять переходами между состояниями без особых проблем!

Реализацию некоторых базовых компонентов вы можете найти в нашем репо на GitHub. Нам бы очень хотелось, чтобы больше людей делали красивые интерфейсы терминалов!

Тизер: Компоненты стиля FRP с RxCpp

Наша следующая статья будет о том, как мы можем использовать RxCpp для написания высоко конкурентных приложений со сложным управлением состоянием.

Подробнее о Buckaroo

Мы создали Buckaroo, чтобы упростить повторное использование кода C ++. Подробнее об этом на Medium: