ROTE is a simple C language library used to emulate
a VT100 terminal . It creates a terminal and provides access to its state in the form of a C language structure. In the terminal, you can start a child process, press keys in it and see what it draws on the terminal. In addition, there is a function for drawing the state of the terminal in the curses window.
Why in practice it may be necessary to emulate a terminal and interact through it with a child process? First of all, it is necessary for automatic testing of programs that draw something on the terminal using curses, in my opinion. How else to write tests for a program that expects the user to press a key and displays the results at a certain place on the screen using curses?
')
Despite all the convenience and inner beauty of ROTE, it would be cumbersome to use it directly in the tests. Therefore, I decided to simplify the task by tying ROTE to the Lua language, which I love very much and know
how to write tests. This is how the
lua-rote library was born, which I want to talk about.
Installation
Linux, curses, Lua versions from 5.1 to 5.3 or LuaJIT, the luarocks package manager with the luaposix package and, in fact, the
ROTE library itself are
required .
ROTE is installed by a simple ./configure && make && make install. It is necessary to track it to be installed where the assembly system sees it. I use for this ./configure --prefix = / usr. In order not to trash the system with orphan files, you can make a package, for this the
checkinstall program is
suitable .
lua-rote is added to luarocks, so to install it, just type the following command:
$ sudo luarocks install lua-rote
If ROTE was installed in / usr / local, then this should be reported to luarocks via the option:
$ sudo luarocks install lua-rote ROTE_DIR=/usr/local
To install the version from GitHub, enter the following commands:
$ git clone https://github.com/starius/lua-rote.git $ cd lua-rote $ sudo luarocks make
To install packages into luarocks locally (that is, into the user's home folder, not into the system folders), add the option --local. In this case, you need to change some environment variables so that Lua sees these packages:
$ luarocks make --local $ luarocks path > paths $ echo 'PATH=$PATH:~/.luarocks/bin' >> paths $ . paths
Using
The entire lua-rote library is in the rote module, so first we will enable it:
rote = require 'rote'
The main part of the library is the RoteTerm class, which represents the terminal.
Create a terminal of 24 rows and 80 columns:
rt = rote.RoteTerm(24, 80)
To delete a terminal, you just need to delete the variable in which it lives. In Lua, a garbage collector works, which in the next pass will do all the work of removal.
Run the child process:
pid = rt:forkPty('less /some/file')
The command is run using '/ bin / sh -c'. The pid variable gets the ID of the child process. You can later figure it out using the childPid () method. If an error occurs, the method returns -1. If you try to start the wrong command, the error will not be caught at this level: the shell will try to start it and exit with status 127. To intercept such errors, you must install a SIGCHLD signal handler. To ignore the completion of child processes, you must set the SIGCHLD handler to SIG_IGN. In Lua, all this can be done using the
luaposix library:
signal = require 'posix.signal' signal.signal(signal.SIGCHLD, function(signo)
Interaction with the terminal in which the child process terminated is not an error, although it hardly makes sense. However, it is worth notifying ROTE of the completion of the child process by calling the forsakeChild () method.
Reading the contents of the terminal
The terminal has a number of methods that return its parameters and state:
- rt: rows () and rt: cols () is the number of rows and columns of the terminal
- rt: row () and rt: col () - current cursor coordinates
- rt: update () - applies changes coming from a child process; call before reading the contents of the terminal
- rt: cellChar (row, col) - cell symbol (row, col) in the form of a string of length 1
- rt: cellAttr (row, col) - cell attributes (row, col) in the form of a number (see below what to do with it)
- rt: attr () - current attributes that apply to new characters
- rt: rowText (row) - row of terminal number row, without "\ n" at the end
- rt: termText () is a string representing the entire terminal; rows are terminated by "\ n"
There is also a draw method for drawing the contents of the terminal in the curses window:
curses = require 'posix.curses'
Writing to the terminal
There are several methods that allow you to change the state of the terminal directly:
- rt: setCellChar (row, col, character) - replaces the cell symbol (row, col)
- rt: setCellAttr (row, col, attr) - replaces cell attributes (row, col)
- rt: setAttr (attr) - changes the current attributes that apply to new characters
- rt: inject (data) - enters data into the terminal
More important are methods that send data to a child process:
The key code collection for keyPress can be found in
curses . Unfortunately, these constants appear in the module only after curses initialization, which is often undesirable (for example, in the test code). In order to somehow live with this, a
rote.cursesConsts module was made that runs curses in a child process through ROTE and returns all constants.
Terminal snapshots
The rt: takeSnapshot () method returns a snapshot object, and the rt: restoreSnapshot (snapshot) method restores the state of the terminal according to the snapshot. The snapshot object is also deleted automatically by the garbage collector.
Attributes and colors
The attribute is an 8-bit number that stores the color of the letters, the background color, the bold bit (bold bit) and the blinking bit (blink bit). The order of bits is as follows:
: 7 6 5 4 3 2 1 0 : SFFFHBBB | `-,-' | `-,-' | | | | | | | `----- 3 (0 - 7) | | `--------- | `------------- 3 (0 - 7) `-----------------
There are a couple of functions for packing and unpacking an attribute value:
foreground, background, bold, blink = rote.fromAttr(attr) attr = rote.toAttr(foreground, background, bold, blink)
Color codes:
- 0 = black
- 1 = red
- 2 = green
- 3 = yellow
- 4 = blue
- 5 = purple
- 6 = blue
- 7 = white
The rote module has translation tables between color codes and color names:
rote.color2name[2]
Usage example
And I also do bioinformatics :)
I have long wanted to have a program to view alignments like
Jalview , but right in the terminal, since often the files are on the server to which I am connected via ssh. In such cases, you need something like less for fasta-files. All that I was able to find on this topic is the
tview program for viewing reads, but this is a bit wrong.
As a result, I wrote the
alnbox program, which is exactly what it does: it shows DNA alignment in curses, allows you to “walk” on it with arrows, move to the beginning and to the end. Sequence names are displayed on the left, position numbers - at the top, consensus - at the bottom. The code is written somewhat more widely, so it can be useful not only for alignments, but also for any less-like programs with headers along all 4 sides of the terminal. All program code is written in Lua, without using C.
With the help of lua-rote and
busted ,
tests for alnbox are written, in which all possible options for working with the program are played. The core of the test integration code in Travis CI is the backbone of the
lua-travis-example from
moteus .
The project is still unfinished, but you can already watch alignments. The dependencies are the same + lua-rote itself. To install, type the command luarocks make.
Another use case
Together with the ROTE library, the
demo / boxshell.c file is distributed. This is essentially a terminal in the terminal: bash is started inside ROTE, and the state of ROTE is drawn in curses using the draw () method. I
transferred this example to Lua. At the beginning of the article shows an example of work in this terminal.
A few fixes have been made to the Lua version of boxshell. First, you can run any command as a child process, not just bash. Secondly, the reading of keystrokes from the user is redone: instead of nodelay, halfdelay is used, that is, waiting for a keystroke with a timeout. Due to this, the processor load on the boxshell side is reduced from 100% to less than 1%.
Bugs
- No unicode support.
- The draw () method can be odd when launched in Travis CI. I can not reproduce this bug. I don’t know the exact reason, but I suspect that the point is the terminal’s features provided by Travis CI.
- Returns incorrect data if the terminal has few columns (example: terminal 1x2).
Report a bugThe ROTE source code was written in 2004 by Bruno T. de Oliveira (Bruno TC de Oliveira) and published under the GNU Lesser General Public License 2.1.
The source code for lua-rote is published under the same license. The author of ROTE writes that the development of the library is complete and the updates should be found in the
libvterm library, which is based on ROTE. There
is another project called libvterm, which is being developed more actively and there is a
modification for the NeoVim project. For my current goals, ROTE was enough, and it looks simpler, so for the time being I have stopped on it. Maybe then go to one of the libvterm.
Links