tset.de » TermFX: terminal programming for lua » termfx: README

README

TermFX

Author: Gunnar Zötl , 2014–2015.
Released the terms of the MIT license. See file LICENSE for details.

Introduction

TermFX is a library for fancy terminal output based on termbox. It means to reduce the weirdness termbox has in places, and provide some additional utilities like offscreen buffers, and generally a more lua like interface. TermFX has been tested with lua 5.1 to 5.3 beta on Linux and Mac OS X.

Basic usage is very similar to termbox, but also different enough that it warrants some of its own documentation.

Like in termbox, the screen is made up of cells, each consisting of a character, a foreground- and a background attribute. For an explanation of these see termfx.attribute(). Unlike termbox, the top left coordinate is 1, 1, and named colors work in all output modes.

All rendering is done into a buffer, whose contents is then drawn to the actual display using the function termfx.present(). In the following discussion of the functions, when I write that something operates on the terminal, it actually operates on this buffer.

Installing

The version of termbox I built this against is included in the archive. So all you need to do is to cd into the source directory and call

make && make install

If you want to build termfx against a newer version of termbox, just type

make distclean

That will remove the termbox subdirectory. If you subsequently call make, the most recent version of termbox will be pulled from the github repo. If you don’t have git installed, you can download the most recent termbox branch from https://github.com/nsf/termbox/archive/master.zip, unpack it into the source directory, rename the resulting termbox-master directory to termbox, and then the above should work.

When using luarocks, a simple

luarocks install termfx

should do the trick, or alternatively, if you’ve already downloaded it, enter the source directory and type

sudo luarocks make

which should do what you want. Again, if you want to build against a more recent version of termbox than the included, just call make distclean before that, and all should be well.

Using

Load the module with:

termfx = require "termfx"

You must call termfx.init() before you can do any output. At the end of your program, you should call termfx.shutdown(), however, the library registers an atexit handler that does this when the interpreter exits, if it has not been done before.

All coordinates on termfx are 1-based, meaning the top left corner of the terminal has the coordinates 1, 1 and the bottom right corner has the coordinates termfx.width(), termfx.height().

Note that termfx.init() sets the terminal into a mode where you can no longer see any error messages the program might throw. It is therefore recommended that, at least during development, you wrap your main loop into a pcall()ed function, like so:

termfx.init()
...
ok, err = pcall(function()
    repeat
        evt = termfx.pollevent()
        ...
    until some_exit_condition
end)
termfx.shutdown()
if not ok then print("Error: "..err) end

Functions

Main functions

termfx.init()
initializes the library and the display. termfx.init() may fail and in that case throws an error. All of the other functions, except for buffer and cell constructors and methods, work only after the terminal has been initialized using this function, and will silently fail, or in the case of queries return nil, if that is not the case. Because of this, you can for example check whether the terminal has been initialized by querying its width and checking the result against nil.
termfx.shutdown()
shuts the library down and puts the terminal into a sane mode for normal usage.
w = termfx.width()
returns the width of the terminal.
h = termfx.height()
returns the height of the terminal.
w, h = termfx.size()
returns width and height of the terminal.
termfx.clear([fg, bg])
clears the terminal. If no arguments are given, the default foreground and background attributes are used (see termfx.attributes()), otherwise the default attributes are set from the arguments passed to termfx.clear() and then used.
fg, bg = termfx.attributes([fg, bg])
sets or, if called without arguments, gets the default foreground and background attributes. Attributes are colors and formatting like bold or underline. Colors are simple integers in the range 0–255, or one of the color constants from termfx.color, and formatting is one or more of the constants from termfx.format. Attributes and constants can be added together for the final foreground or background attribute. Returns the default foreground and background attributes, or those just set.
col = termfx.rgb2color(r, g, b)
maps a set of rgb values to a xterm color number. r, g, b must be in the range 0..5, which makes up the 216 available colors. Returns a color number, which is corrected for the output mode in use, or nil if the input could not be mapped to a color number. This function only works in termfx.output.COL216 and termfx.output.COL256 modes, in the other modes it will always return nil.
col = termfx.grey2color(v)
maps a single greyscale value to an xterm color number. v must be in the range 0..25, which makes for 26 grey values. Returns a color number, which is corrected for the output mode in use, or nil if the input could not be mapped to a color number. This function only works in termfx.output.COL256 and termfx.output.GREYSCALE modes, in the other modes it will always return nil. Also, as in tfx.output.GREYSCALE mode there are only 24 available grey values, the values 0 and 1 map to the same color number, as do 24 and 25.
value, r, g, b = termfx.colorinfo(n)

returns the color value (a rgb string with format “#abcdef”) and red, green and blue values for a color number. The range of n depends on the current output mode:

  • 0..255 for termfx.output.COL256
  • 0..215 for termfx.output.COL216
  • 0..23 for termfx.output.GREYSCALE
  • 0..15 for termfx.output.NORMAL

For values outside of the valid range for the current output mode, the function will return nil.

termfx.present()
output the contents of TermFX’s back buffer, into which the rendering is done, to the terminal.
termfx.setcursor(x, y)
sets the cursor to position x, y and unhides it.
termfx.hidecursor()
hides the cursor.
cel = termfx.newcell([, ch [, fg [, bg]]])
creates a new offscreen cell. You can use cells in functions like termfx.setcell() or termfx.rect(). If you pass any or all of the arguments, they are used in the construction of the cell, otherwise the respective values default to space (’ ’) for the char, and the default foreground and background attributes. The returned cell has 3 attributes you can read or set, which are ch, fg and bg. For more information see the section on TermFX’s cells below.
termfx.setcell(x, y [, cel]) or termfx.setcell(x, y [, ch [, fg [, bg]]])
sets the cell with the coordinates x, y on the terminal. For the cells attributes, you can pass in a cell as created by termfx.newcell(), or values. If you pass any or all of the arguments, they are used for the cell on the terminal, otherwise the respective values default to space (’ ’) for the char, and the default foreground and background attributes.
cel = termfx.getcell(x, y)
gets the cell from the coordinates x, y on the terminal. If x or y lie outside of the terminal, nil is returned.
buf = termfx.newbuffer(w, h)
creates a new offscreen buffer with width w and height h. The new buffer gets the default foreground and background attributes as its local defaults. This may be changed with a method call on the buffer. For more information see the section on buffer methods below.
termfx.blit(x, y, buf)
blits the contents of the buffer to the terminal placing the buffers top left corner at the coordinates x, y.
termfx.rect(x, y, w, h [, cel]) or ok = termfx.rect(x, y, w, h [, ch [, fg [, bg]]])
draws a rectangle on the terminal with top left at x, y and width w and height h. For the rectangles attributes, you can pass in a cell as created by termfx.newcell(), or the values. If you pass any or all of the arguments, they are used for the rectangle on the terminal, otherwise the respective values default to space (’ ’) for the char, and the default foreground and background attributes.
termfx.copyregion(tx, ty, x, y, w, h)
copy the the rectangle starting at x, y with width w and height h to position tx, ty on the terminal. Source and target rectangle may overlap.
termfx.scrollregion(x, y, w, h [, sx [, sy]])
scrolls the rectangle starting at x, y with width w and height h on the terminal by sx and sy. sx and sy are optional and default to 0. If they have other values, only their sign is used. Scrolling is by 1 column or row at most.
termfx.printat(x, y, what [, w])
prints a string to the terminal at position x, y up to maximum width w. If w is omitted, it defaults to the length of what. what can be a string, or a table of cells. If it is a string, it is printed in the default foreground and background colors, if it is a table of cells, the char and attributes from each cell are used.
mode = termfx.inputmode([newmode])

sets the terminal input mode. Valid modes are termfx.input.ESC, termfx.input.ALT. If the newmode argument was not given, returns the current input mode, otherwise returns the mode just set.

Modes are:

  • termfx.input.ESC: when an ESC sequence is in the buffer and it doesn’t match any known ESC sequence, ESC means termfx.key.ESC
  • termfx.input.ALT: when an ESC sequence is in the buffer and it doesn’t match any known sequence, ESC enables "ALT" modifier for the next keyboard event.
  • termfx.input.MOUSE: specify this if you are interested in mouse events. This can be added / or’ed to termfx.input.ESC or termfx.input.ALT
mode = termfx.outputmode([newmode])
sets the terminal output mode. See the section on colors and attributes below. If the newmode argument was not given, returns the current output mode, otherwise returns the mode just set.
evt = termfx.pollevent([timeout])

waits for an event to occur. If the timeout argument is given, the function will time out after timeout milliseconds, otherwise it will wait forever. On Timeout, the function returns nil, on error it returns nil+error message, otherwise it returns a table with information about the event. Known events are "key" for a keyboard event, "mouse" for a mouse event or "resize" if the terminal was resized. Depending on which event occurred, the result table looks like this:

For the event "key":

  • type: "key"
  • elapsed: the number of milliseconds spent in termfx.pollevent()
  • mod: nil or "ALT", no other modifiers are returned
  • key: the numeric code of the key pressed, if it was a special key
  • ch: the numeric code of the key pressed for standard keys
  • char: the char (possibly utf8 encoded) for ch

either the field key or the field ch is nil. If the key field is not nil, it can be checked against one of the constants in termfx.key. If the ch field is not nil, it is the unicode value of the char that was pressed on the keyboard. In that case, the char field will contain a string containing the character represented by ch.

For the event "mouse":

  • type: "mouse"
  • elapsed: the number of milliseconds spent in termfx.pollevent()
  • key: the numeric code of the button pressed
  • x, y: the terminal coordinates where the mouse event occurred.

on release of any button, the value of key will be termfx.key.MOUSE_RELEASED

For the event "resize":

  • type: "resize"
  • elapsed: the number of milliseconds spent in termfx.pollevent()
  • w: the new width
  • h: the new height

Buffer methods

The buffer created by termfx.newbuffer() has a few methods associated with it. They work just like their global counterparts, only on the buffer on which they are called. So for a detailed description of the individual methods see above.

buf:attributes([bg, fg])
set or get default cell attributes for the buffer.
buf:clear([fg, bg])
clear the buffer.
buf:setcell(x, y [, cel]) or buf:setcell(x, y [, ch [, fg [, bg]]])
set a cell in the buffer.
cel = buf:getcell(x, y)
gets a cell from a buffer.
buf:blit(x, y, src)
blits the contents of the buffer src to position x, y of buffer buf.
buf:rect(x, y, w, h [, cel]) or ok = buf:rect(x, y, w, h [, ch [, fg [, bg]]])
draw a rectangle in the buffer.
buf:copyregion(tx, ty, x, y, w, h)
copy a region of the buffer.
buf:scrollregion(x, y, w, h [, sx [, sy]])
scrolls a region of the buffer.
buf:printat(x, y, what [, w])
print a string to the buffer.
w = buf:width()
return the buffer width.
h = buf:height()
return the buffer height.
w, h = buf:size()
return the buffer width and height.

Colors and attributes

How TermFX handles colors depends on the chosen output mode. There are a few functions to help you with the colors, these are termfx.rgb2color(), termfx.grey2color()and termfx.colorinfo(), see the function list above for an explanation of those functions. They work based on the standard xterm colormap. If, for some reason, you have a different colormap, these functions will not give you the results you want.

There are 8 predefined colors, which are available in all output modes:

The output modes are:

termfx.output.NORMAL
this is an 8 color mode, in which only the 8 predefined colors are available as colors 1–8, plus a default color at position 0.
termfx.output.COL256
this is a mode with 256 colors. * the first 8 colors (0–7) are the 8 predefined colors.
* the next 8 colors (8–15) are the first 8 colors + termfx.attr.BOLD
* then (16–231) follow 216 different colors
* finally (232–255) 24 shades of grey
termfx.output.COL216
this is a mode that supports only the 3rd range of COL256, i.e. 216 different colors, but ranging from 0–215. The 8 default colors are mapped to their closest representations in the palette.
termfx.output.GRAYSCALE
this is a mode in which only the 4th range from COL256 is available, i.e. 24 shades of grey. but ranging from 0–23. The 8 default colors are mapped to grey values.

A table of the colors available for xterms is available here:
https://en.wikipedia.org/wiki/Xterm_%28software%29#mediaviewer/File:Xterm_256color_chart.svg

The behaviour of the predefined colors is different from termbox, where they only make sense for the NORMAL output mode. But this means that they need to be determined for each output mode, which means that when you for some reason cache the values from termfx.color or use them in a buffer or a cell, and then switch output modes, you will need to re-cache them, as they will have changed.

In addition to colors, cells can also have formatting attributes:

In order to construct a cell attribute, you can start with a color and just add the desired formatting options to it (but only once, because, of course, they are actually meant to be or’ed to the color)

Key Constants

When termfx.pollevent() returns with a type field of “key” and its key field set, then the value in the key field may be checked against one of the constants in termfx.key, as listed below. However, due to window managers interfering and whatnot, not all of these special keys may be available to your terminal.

If mouse support is available, you also have these constants:

Unicode

TermFX works with utf8 or plain 8 bit encoded charsets. Apart from the encoding, TermFX makes no assumptions about the characters in a string. Of course, in order to use utf8, the terminal has to support unicode. If you need to manipulate utf8 encoded strings, use either lua 5.3 or a proper utf8 library like luautf8.

Example

A very simple program using this library might look like this:

tfx = require "termfx"
tfx.init()
tfx.clear(tfx.color.WHITE, tfx.color.BLACK)
s = "Hello World"
x = math.floor((tfx.width() - #s) / 2)
y = math.floor(tfx.height() / 2)
for i=1, #s do
    tfx.setcell(x, y, string.sub(s, i, i))
    x = x + 1
end
tfx.present()
quit = false
repeat
    evt = tfx.pollevent()
    quit = evt.char == "q"
until quit
tfx.shutdown()

You can find more examples in the samples directory.

References

This is built using termbox, so when in doubt, a look at its documentation may help. Said documentation is contained within the file termbox.h, which can be found here:

https://github.com/nsf/termbox/blob/master/src/termbox.h