The ANSI Terminal
In the beginning, there were very few computers, and they were centrally located and often many people needed to use the computer at once. As a consequence, most access to the computer happened through a terminal. Terminals (or TTYs) were not themselves “computers” but only sent characters to the computer and received results (over a wired connection, like POTS), displaying the results to the user. The first terminals (such as Teletype model 33) displayed results in the form of a printout, but in the late 1960s “glass TTYs” that used video displays instead of physical printouts were introduced.
Video terminals had new affordances, like being able to display text at arbitrary positions on the screen or in different colors, that were impossible (or difficult) to achieve on hard copy terminals. Terminal vendors introduced command sequences that the computer could send to the terminal to achieve these kinds of effects. To make it easier to write programs that could interoperate with terminals from many different vendors, the American National Standards Institute developed a standard set of command sequences, and this standard was widely adopted by terminal vendors. The standard (ANSI X3.64) is still supported to this day in terminal emulator programs (like macOS Terminal) that run on contemporary computers.
In this tutorial, I’m using a terminal emulator written entirely in JavaScript that runs in the browser: xterm.js. You can write text and ANSI escape sequences to this terminal emulator with a JavaScript program. I created a JavaScript bundle for in-browser use that also includes a JavaScript library called ansi-escape-sequences that provides easy-to-use functions that print ANSI escape sequences by name, so you don’t have to remember the codes (very handy).
You can get a quick start by opening this example in the p5.js web
editor and making
your own version (File > Duplicate
and making your own version (File >
Duplicate
). This sketch already has the necessary JavaScript bundle included.
It’s also using the p5.js framework, which gives us a
number of handy behaviors and functions right out of the box; mainly, we’ll be
taking advantage of the draw()
function, which is called automatically every
n
frames (you can set n
with a call to frameCount()
). See the p5js
reference for other functions that might pop up in the
examples.
In the following code snippets, I’ll assume you’re using the p5js editor version.
Terminal and cursor
By default, the framework creates an xterm.js terminal that takes up the entire
screen, and sets the number of rows and columns in the terminal to fill up the
available space. (You can control the font and font size by changing parameters
in xterm-ansi-setup.js
.) There’s a global variable term
, which has a method
.write()
that takes the string you pass to it and prints it to the screen:
term.write("Hello, world");
… writes Hello, world
to the terminal.
Every terminal has a “cursor,” which is the position on the screen where text
will appear when you call .write()
. Each (non-control) character in the
string passed to write
advances the character one column to the right; if it
advances past the last column, it will relocate to the first column in the
subsequent row. For this reason, although the first call to write
starts in
the upper left-hand corner, subsequent calls to term.write()
will write text
starting at the row and column where the last write left off.
The control characters you might already know
There are two control characters that you can use in a terminal that you
probably already know about: \n
(Newline) moves the cursor one row
down, and \r
(Carriage
return) moves the cursor back
to the beginning of the line. When writing to a terminal, you need to use both
to get the usual “new line” behavior:
term.write("Hello, world\r\n");
See “Hello, world” with newline and carriage return in action
The character \t
(Tab) advances the cursor by a set amount, and the character
\b
(Backspace) moves the cursor back one column.
Here’s an example of the backspace character in action.
ANSI escape sequences
The control codes introduced in the ANSI standards allow you to do a number of
useful things, like setting the position of the cursor to an arbitrary position
and changing the color of the text. To use a control code (often called an
“escape sequence”), you need to send a sequence of characters to the terminal.
Every ANSI control code begins with the ASCII “Escape” character (written as
\x1b
in JavaScript) followed by [
. For example, the code to advance the
cursor by one column is “Escape” followed by [<n>C
, where <n>
is the number
of columns to advance. Writing this to our xterm.js terminal would look like:
term.write("\x1b[4C"); // advances 4 columns
Or, if you wanted to advance a number of columns from a variable:
let colCount = 9;
term.write("\x1b["+colCount+"C");
Another example: The command “Escape” followed by [<n>;<m>H
moves the cursor
position to row <n>
, column <m>
. For example, the following code prints
this is fun!
at row 5, column 13:
term.write("\x1b[5;13Hthis is fun!");
And the command “Escape” followed by [<nn>m
sets the color of the text. If
you write a number between 30 and 37, it sets the foreground color; if a number
between 40 and 47, the background color. See the full list of colors on
Wikipedia. For example,
the following code prints this is fun!
in red:
term.write("\x1b[31mthis is fun!");
And this code writes the same text on a blue background:
term.write("\x1b[44mthis is fun!");
You can set both the foreground and background colors using the syntax
\x1b[<nn>;<mm>m
where <nn>
is the foreground and <mm>
is the background:
term.write("\x1b[31;44mthis is lots of fun!");
This prints this is lots of fun!
in red on a blue background.
Using a library
The control codes themselves are strange and difficult to remember, so a number
of people have made libraries that “wrap” the control codes in functions that
you can call with easier-to-remember names. The bundled code includes
one such library: ansi-escape-sequences. The library is available as the global variable ansi
.
For example, the following function call returns a string with the control code necessary to set the cursor position to row 4, column 10:
ansi.cursor.position(4, 10)
This evaluates to \x1b[4;10H
. (More verbose, but easier to remember!) To actually
set the cursor to this position, you have to send the command sequence to the
terminal:
term.write(ansi.cursor.position(4, 10));
There are a handful of ways to set the color of the text with
ansi-escape-sequences
. One is to use the ansi.styles()
function, which
returns a string containing command codes to set the output to display with
the ANSI styles you specify in a list. For example, the following code:
ansi.styles(['red', 'bold'])
… evaluates to \x1b[31;1m
. Again, to actually instruct the terminal to change
colors, you need to write the command sequence:
term.write(ansi.styles(['red', 'bold']) + "this is bold red text!");
The ansi.format()
function is a convenience function to set the styles you
specify to a given string, and then reset back to the default. For example,
this call:
ansi.format("this is fun!", ["green", "bold", "bg-magenta"])
… evaluates to the string \x1b[32;1;45mthis is fun!\x1b[0m
. (The \x1b[0m
at the end of the string resets the terminal to default styles.) Sending this to
the terminal would display this is fun!
in bold green text on a magenta
background:
term.write(ansi.format("this is fun!", ["green", "bold", "bg-magenta"]))
Examples
- Starter sketch
- Example without ansi library
- Scrolling
- Implementation of 10 PRINT
- Stars
- Ghost train (homage to bpNichol)
References
- The ansi-escape-sequences API
- Wikipedia has a full list of ANSI escape codes (see also the following “SGR” and “color” sections)
Further reading
- everything you ever wanted to know about terminals by Lexi Summer Hale
- Textfiles ANSI art collection
- Playscii, “an open source ASCII art, animation, and game creation program”
- sixteencolors.net (archived), an extensive gallery of ANSI art
- Blocktronics ACiD Trip, A Super Long Piece of Collaborative ANSI Art
- terminal-kit is a full-featured library for making text user interfaces, including UI widgets and mouse handling. (Unfortunately I couldn’t find an easy way to make it work with xtermjs, but this is great if you’re working directly in a terminal with node)