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: