Building Reactive Terminal Interfaces in C++

18,761 reads

TL;DR

Using functional programming techniques, we can easily build fancy terminals with dynamic components in C++. RxTerm is a C++ library that provides some of the necessary building blocks for implementing this concept.

We applied the same ideas in Buckaroo. This is the result:

Motivation

Imagine that we want to build a text-based application that updates the console as its state changes. A great example of this is Curl, which gives a live progress bar for downloads. Try this in your teminal:

Such interfaces are implemented using ANSI escape codes. These are invisible characters that have side-effects, such as erasing characters and moving the cursor.

We can use these escape codes to create live text interfaces. A simple implementation might update a progress bar like this:

So far so good, but what if we have more complicated case? Ideally, we would leverage a library that figures these things out for us. We should only have to specify what the terminal output should look like, and the library functions would figure out the characters to print to get us there.

So, at a high-level, we need three things:

1. A variable to track the previous state of the console

2. A function for rendering the current application state

3. A function for transforming the console from the previous state to the next state

React developers will be familiar with this pattern. It’s a clever strategy that can actually be applied to any I/O device!

Additionally, we want to be able leverage reusable UI components for things like progress-bars, lists and so on. Something like HTML, but simpler, would be ideal. A component would be any object than can be rendered to console output.

Simple API Example

To see where we are going, let’s take a look at an example in RxTerm. With RxTerm, we can turn basic terminal components into more complex ones using composition. If the app-state changes, then we compute a new view for the terminal and replace the currently visible output.

This example shows how we can design a new component called fancyCounter , which prints frames like this:

Here’s the code:

As you can see, the interface is very high-level.

ANSI Escape Codes

Let’s start with the basics. How can we use ANSI escape codes to change the color and delete a line?

This function prints Hello in red type followed by World in the default color. The magic escape sequences ( \e[31m and \e[0m ) modify how the terminal displays characters.

An escape sequence is introduced with \e[ followed by a semicolon-separated-list of modifiers, terminated with m . For instance, \e[3;31;42mTEXT would print TEXT in red italics on a blue background.

We can also print \e[0m to reset the terminal back to its default state.

You can find a whole list on bash-hackers.org.

Abstracting the Terminal State

To be able to compose components, we need a high-level representation of the state of the console. Since we want to support colors, an intuitive representation of this is a map from coordinates to pixels:

Once we have the basic abstraction for components,

we can take advantage of type-erasure to maintain value-semantics and make inheritance an implementation detail:

Now, we can build higher order components like a Text object:

You can find the actual implementation here.

Performing State Transitions

Imagine we want to transition from this:

… to this:

The simplest way would be to delete the latest 3 lines and print the new lines. We can delete the latest line by printing \e[2K\r\e[1A ( delete line ; move cursor to the start of the line ; move cursor up one line ).

A more sophisticated approach would be to compute the difference and move to the target position and edit only the terminal pixels that changed. For simplicity, we will stick to the first approach and combine the moving parts into one class:

The actual implementation is hosted on GitHub.

Verdict

We saw how easy it is to write a reactive terminal framework and manage the state transitions without too many headaches!

You can find the implemention of some basic components in our repo on GitHub. We would love to see more people doing beautiful terminal interfaces!

Teaser: FRP Style Components With RxCpp

Our next article will be about how we can leverage RxCpp to write highly concurrent applications with complex state management.

More About Buckaroo

We created Buckaroo to make C++ code-reuse easier. Read more about it on Medium:

Tags