Version: 1:0
charterm: Character-cell Terminal Interface in Racket
(require (planet neil/charterm:1:0)) |
1 Introduction
The
charterm package provides a Racket interface for character-cell video
display terminals on Unix-like systems – both terminal emulators like
xterm, and some older hardware terminals (even the venerable
DEC VT100). Currently, it implements a subset of
xterm’s features.
This package could be built upon to implement a status/management
console for a Racket-based server process (perhaps run from an SSH session, or
perhaps in
screen), a lightweight user interface for a systems tool, a command-line
REPL, a text editor, and, most importantly, a
Rogue-like application.
The charterm package does not include any native code in the Racket process,
such as through the Racket FFI or C extensions. It is implemented in pure
Racket code except for briefly calling out to /bin/stty at startup time and shutdown time.
Fun fact: “charterm” is short for “Character Terminal,” not for
“Chart? Erm...” For doing charts, see the PLoT library by Neil Toronto.
1.1 Demo
For a demonstration, the following command, run from a terminal, should install the charterm package (if not already installed), and run the demo:
racket -p neil/charterm -l racket -e "(charterm-demo)"
Note: Although charterm-demo includes an editable text field, as proof of concept, the current
version of charterm does not provide editable text fields as reusable functionality.
1.2 Simple Example
Here’s your first charterm program:
#lang racket/base |
|
(require (planet neil/charterm:1)) |
|
(with-charterm |
(charterm-clear-screen) |
(charterm-cursor 10 5) |
(charterm-display "Hello, ") |
(charterm-bold) |
(charterm-display "you") |
(charterm-normal) |
(charterm-display ".") |
(charterm-cursor 1 1) |
(charterm-display "Press a key...") |
(let ((key (charterm-read-key))) |
(charterm-cursor 1 1) |
(charterm-clear-line) |
(printf "You pressed: ~S\r\n" key))) |
Now you’re living the dream of the ’70s.
2 Interface
2.1 charterm Object
(charterm? x) → boolean? |
x : any/c |
Predicate for whether or not x is a charterm.
2.2 Opening and Closing
(current-charterm) → (or/c #f charterm?) |
(current-charterm ct) → void? |
ct : (or/c #f charterm?) |
This parameter provides the default charterm for most of the other procedures. It is usually set automatically by call-with-charterm, with-charterm, open-charterm, and close-charterm.
(open-charterm | [ | #:tty tty | | | | | | | #:current? current?]) | | → | | charterm? |
|
tty : (or/c #f path-string?) = #f |
current? : boolean? = #t |
Returns an open charterm object, by opening I/O ports on the terminal device at tty (or, if #f, file "/dev/tty"), and setting raw mode and disabling echo (via "/bin/stty"). If current? is true, the current-charterm parameter is also set to this object.
(close-charterm [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
Closes ct by closing the I/O ports, and undoing open-charterm’s changes via "/bin/stty". If current-charterm is set to ct, then that parameter will be changed to #f for good measure. You might wish to use with-charterm instead of worrying about calling close-charterm directly.
Note: If you exit your Racket process without properly closing the charterm, your terminal may be left in a crazy state. You can fix it with
the command:
stty sane
If only ex-lovers could be fixed as easily as ex-processes.
(with-charterm expr? ...) |
Opens a charterm and evaluates the body expressions in sequence with current-charterm set appropriately. When control jumps out of the body, in a
manner of speaking, the charterm is closed.
2.3 Information
(charterm-screen-size [#:charterm ct]) |
| → | | (or/c #f exact-nonnegative-integer?) | (or/c #f exact-nonnegative-integer?) |
|
|
ct : charterm? = (current-charterm) |
Attempts to get the screen size, in character columns and rows.
If unable to get a value, then #f is returned for the value.
If you find this returning (#f, #f), then (80, 24) might be a good fallback.
2.4 Video
2.4.1 Cursor
(charterm-cursor x y [#:charterm ct]) → void? |
x : exact-positive-integer? |
y : exact-positive-integer? |
ct : charterm? = (current-charterm) |
Positions the cursor at column x, row y, with the upper-left character cell being (1, 1).
(charterm-newline [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
Sends a newline to the terminal. This is typically a CR-LF
sequence.
2.4.2 Displaying
(charterm-display | [ | #:charterm ct | | | | | | | #:width width | | | | | | | #:pad pad | | | | | | | #:truncate truncate] | | | | | | | arg ...) | | → | | void? |
|
ct : charterm? = (current-charterm) |
width : (or/c #f exact-positive-integer?) = #f |
pad : (or/c 'width boolean?) = 'width |
truncate : (or/c 'width boolean?) = 'width |
arg : any/c |
Displays each arg on the terminal, as if formatted by display, with the exception that unprintable or non-ASCII characters might not be displayed. (The exact behavior of what is permitted is expected to change in a later version of charterm, so avoid trying to send your own control sequences or using newlines, making assumptions about non-ASCII, etc.)
If width is a number, then pad and truncate specify whether or not to pad with spaces or truncate the output, respectively, to width characters. When pad or width is 'width, that is a convenience meaning “true if, and only if, width is not #f.”
2.4.3 Video Attributes
(charterm-normal [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
(charterm-inverse [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
(charterm-bold [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
(charterm-underline [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
(charterm-blink [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
Sets the
video attributes for subsequent writes to the terminal. In this version of
charterm, each is mutually-exclusive, so, for example, setting
bold clears
inverse. Note that not all terminals support all of these.
2.4.4 Clearing
(charterm-clear-screen [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
Clears the screen, including first setting the video attributes to
normal, and positioning the cursor at (1, 1).
(charterm-clear-line [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
(charterm-clear-line-left [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
(charterm-clear-line-right [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
Clears text from the line with the cursor, or part of the line with the cursor.
2.4.5 Line Insert and Delete
(charterm-insert-line [count #:charterm ct]) → void? |
count : exact-positive-integer? = 1 |
ct : charterm? = (current-charterm) |
Inserts count blank lines at cursor. Note that not all terminals support
this.
(charterm-delete-line [count #:charterm ct]) → void? |
count : exact-positive-integer? = 1 |
ct : charterm? = (current-charterm) |
Deletes count blank lines at cursor. Note that not all terminals support
this.
(charterm-bell [#:charterm ct]) → void? |
ct : charterm? = (current-charterm) |
Rings the terminal bell. This bell ringing might manifest as a
beep, a flash of the screen, or nothing.
2.5 Keyboard
(charterm-byte-ready? [#:charterm ct]) → boolean? |
ct : charterm? = (current-charterm) |
Returns true/false for whether at least one byte is ready for
reading (either in a buffer or on the port) from ct. Note that, since some keys are encoded as multiple bytes, just
because this procedure returns true doesn’t mean that charterm-read-key won’t block temporarily because it sees part of a potential
multiple-byte key encoding.
(charterm-read-key | [ | #:charterm ct | | | | | | | #:timeout timeout]) | | → | | (or #f char? symbol?) |
|
ct : charterm? = (current-charterm) |
timeout : (or/c #f positive?) = #f |
Reads a key from ct, blocking indefinitely or until sometime after timeout seconds has been reached, if timeout is non-#f. If timeout is reached, #f is returned.
Many keys are returned as characters, especially ones that
correspond to printable characters. For example, the unshifted “q” key is
returned as character #\q. Other keys are returned as symbols, such as 'return, 'esc, 'f1, 'shift-f12, 'right, and many others.
Since some keys are sent as ambiguous sequences, charterm-read-key employs separate timeouts internally, such as to disambuate
the Esc key (byte sequence 27) from what on some terminals would be
the F10 key (bytes sequence 27, 91, 50, 49, 126).
3 Misc.
(charterm-demo [#:tty tty]) → void? |
tty : (or/c #f path-string?) = #f |
This procedure runs a demonstration program using charterm. Specifically, it reports what keys you pressed, while letting
you edit a text field, and while displaying a clock. The clock is updated
roughly once per second, and is not updated during heavy keyboard input, such
as when typing fast. The demo responds to changing terminal sizes, such as
when an xterm is window is resized. It also displays the determined terminal
size, and some small tests of the #:width argument to charterm-display. Exit the demo by pressing the Esc key.
4 Known Issues
Currently only implemented to work on Unix-like systems like
GNU/Linux.
Only supports ASCII characters. UTF-8, for terminal emulators
that support it, would be nice.
More controls for terminal features can be added.
Add other ways to detect terminal size.
Need to look more at low-level TTY issues and buffering,
especially since screen is noticeably sluggish on key responses with charterm-demo.
Expose the character-decoding mini-language as a configurable
option. Perhaps wait until we implement timeout-based disambiguation at
arbitrary points in the the DFA rather than just at the top. Also, might be
better to resolve multi-byte characters first, in case that affects the
mini-language.
Possibly make a charterm object usable as a Racket event.
Implement text input controls, either as part of this library or
another, using charterm-demo as a starting point.
5 History
6 Legal
Copyright 2012 Neil Van Dyke. This program is Free Software; you can
redistribute it and/or modify it under the terms of the GNU Lesser General
Public License as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version. This program is
distributed in the hope that it will be useful, but without any warranty;
without even the implied warranty of merchantability or fitness for a
particular purpose. See http://www.gnu.org/licenses/ for details. For other
licenses and consulting, please contact the author.