Programming curses in Rust

In the previous episode, we developed a minimal ls command implemented in Rust. In this episode, we are starting to work on a Rust implementation of the Linux command watch:

NAME
       watch - execute a program periodically, showing output fullscreen

SYNOPSIS
       watch [options] command

DESCRIPTION
       watch  runs  command  repeatedly, displaying its output and errors (the
       first screenfull).  This allows you to watch the program output  change
       over  time.   By default, command is run every 2 seconds and watch will
       run until interrupted.

For example, the command

watch -n 5 ls -l

will run the command "ls -l" every 5 seconds and display its output, until interrupted. Its output looks like this:

Every 5.0s: ls -l                               juliet: Sun Oct 22 10:47:35 2023

total 8
drwxrwxr-x 4 aleks aleks 4096 Oct 22 10:37 hello-curses
-rw-rw-r-- 1 aleks aleks  530 Oct 22 10:45 README.md

watch command has lots of command line flags and complex functionality, but in our minimal version, we will just implement the "-n" option that allows us to set the refresh period (the default is 2s).

In the first post, we will explore how to control the whole shell window: clear it, write text on the window and refresh the window. We will also want to restore the previous window contents upon exit.

For this functionality, the library that is commonly used is ncurses:

ncurses (new curses) is a programming library providing an application programming interface
(API) that allows the programmer to write text-based user interfaces (TUI) in a
terminal-independent manner. It is a toolkit for developing "GUI-like" application software
that runs under a terminal emulator. It also optimizes screen changes, in order to reduce
the latency experienced when using remote shells.

There are several Rust crates that can be used for working with ncurses. I have chosen pancurses.

This is a minimal program that uses pancurses:

use pancurses::{initscr, endwin};

fn main() {
    let window = initscr();
    window.printw("Hello pancurses");
    window.refresh();
    window.getch();
    endwin();
}

initscr initializes the curses system and returns an instance of the Window struct. We use that object to print text by calling the method printw. Remember that we have to call refresh after writing to the window.

After refreshing the window, we call getch, which waits for the user to click on any key. After the user has clicked on a key, we ignore that input, call method endwin to clean up and then exit.

In the next episode, we will look into more functionality provided by pancurses that we will need for our implementation of the watch command.

Get the code at 2023-10-22-watch/hello-curses.