📜 ⬆️ ⬇️

ROTE and Lua bind terminal emulation library

boxshell

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) -- do smth end) signal.signal(signal.SIGCHLD, signal.SIG_IGN) 

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:


There is also a draw method for drawing the contents of the terminal in the curses window:

 curses = require 'posix.curses' --  curses, .  demo/boxshell.lua window = ... rt = ... start_row = 0 start_col = 0 rt:draw(window, start_row, start_col) 


Writing to the terminal


There are several methods that allow you to change the state of the terminal directly:


More important are methods that send data to a child process:

 --   ':wq\n'   --    ,   . --         inject() rt:write(':wq\n') --     vim --       write() local keycode = string.byte('\n') --  rt:keyPress(keycode) 

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) -- foreground  background -  (0 - 7) -- bold  blink -   


Color codes:


The rote module has translation tables between color codes and color names:
 rote.color2name[2] --  "green" rote.name2color.green --  2 


Usage example



DNA alignment

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



Report a bug

The 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


Source: https://habr.com/ru/post/254089/


All Articles