ETI program examples

The editor program

This program illustrates how to use curses routines to write a screen editor. For simplicity, editor keeps the buffer in stdscr; obviously, a real screen editor would have a separate data structure for the buffer. This program has many other simplifications: no provision is made for files of any length other than the size of the screen, for lines longer than the width of the screen, or for control characters in the file.

Several points about this program are worth making. First, it uses the move, mvaddstr, flash, wnoutrefresh, and clrtoeol routines that are all discussed in this document.

Second, it also uses some curses routines that are not discussed in this document. For example, the function to write out a file uses the mvinch routine, which returns a character in a window at a given position. The data structure used to write out a file does not keep track of the number of characters in a line or the number of lines in the file, so trailing blanks are eliminated when the file is written. The program also uses the insch, delch, insertln, and deleteln routines. These functions insert and delete a character or line. See the curses(3ocurses) manual pages for more information about these routines.

Third, the editor command interpreter accepts special keys, as well as ASCII characters. On one hand, new users find an editor that handles special keys easier to learn about. For example, it's easier for new users to use the arrow keys to move a cursor than it is to memorize that the letter h means left, j means down, k means up, and l means right. On the other hand, experienced users usually like having the ASCII characters to avoid moving their hands from the home row position to use special keys.

NOTE: Because not all terminals have arrow keys, your curses programs will work on more terminals if there is an ASCII character associated with each special key.

Fourth, the CTRL-L command illustrates a feature most programs using curses routines should have. Often some program beyond the control of the routines writes something to the screen (for instance, a broadcast message) or some line noise affects the screen so much that the routines cannot keep track of it. A user invoking editor can type CTRL-L, causing the screen to be cleared and redrawn with a call to wrefresh(curscr).

Finally, another important point is that the input command is terminated by CTRL-D, not the escape key. It is very tempting to use escape as a command, since escape is one of the few special keys available on every keyboard. (Return and break are the only others.) However, using escape as a separate key introduces an ambiguity. Most terminals use sequences of characters beginning with escape (that is, escape sequences) to control the terminal and have special keys that send escape sequences to the computer. If a computer receives an escape from a terminal, it cannot tell whether the user depressed the escape key or whether a special key was pressed.

editor and other curses programs handle the ambiguity by setting a timer. If another character is received during this time, and if that character might be the beginning of a special key, the program reads more input until either a full special key is read, the time out is reached, or a character is received that could not have been generated by a special key. While this strategy works most of the time, it is not foolproof. It is possible for the user to press escape, then to type another key quickly, which causes the curses program to think a special key has been pressed. Also, a pause occurs until the escape can be passed to the user program, resulting in a slower response to the escape key.

Many existing programs use escape as a fundamental command, which cannot be changed without infuriating a large class of users. These programs cannot make use of special keys without dealing with this ambiguity, and at best must resort to a time-out solution. The moral is clear: when designing your curses programs, avoid the escape key.

 /* editor: A screen-oriented editor.  The user
  * interface is similar to a subset of vi.
  * The buffer is kept in stdscr to simplify
  * the program.

#include <stdio.h> #include <ocurses.h>

#define CTRL(c) ((c) & 037)

main(argc, argv) int argc; char **argv; { extern void perror(), exit(); int i, n, l; int c; int line = 0; FILE *fd;

if (argc != 2) { fprintf(stderr, "Usage: %s file\n", argv[0]); exit(1); }

fd = fopen(argv[1], "r"); if (fd == NULL) { perror(argv[1]); exit(2); }

initscr(); cbreak(); nonl(); noecho(); idlok(stdscr, TRUE); keypad(stdscr, TRUE);

/* Read in the file */ while ((c = getc(fd)) != EOF) { if (c == '\n') line++; if (line > LINES - 2) break; addch(c); } fclose(fd);

move(0,0); refresh(); edit();

/* Write out the file */ fd = fopen(argv[1], "w"); for (l = 0; l < LINES - 1; l++) { n = len(l); for (i = 0; i < n; i++) putc(mvinch(l, i) & A_CHARTEXT, fd); putc('\n', fd); } fclose(fd);

endwin(); exit(0); }

len(lineno) int lineno; { int linelen = COLS - 1;

while (linelen >= 0 && mvinch(lineno, linelen) == ' ') linelen--; return linelen + 1; }

/* Global value of current cursor position */ int row, col;

edit() { int c;

for (;;) { move(row, col); refresh(); c = getch();

/* Editor commands */ switch (c) {

/* hjkl and arrow keys: move cursor * in direction indicated */ case 'h': case KEY_LEFT: if (col > 0) col--; else flash(); break;

case 'j': case KEY_DOWN: if (row < LINES - 1) row++; else flash(); break;

case 'k': case KEY_UP: if (row > 0) row--; else flash(); break;

case 'l': case KEY_RIGHT: if (col < COLS - 1) col++; else flash(); break; /* i: enter input mode */ case KEY_IC: case 'i': input(); break;

/* x: delete current character */ case KEY_DC: case 'x': delch(); break;

/* o: open up a new line and enter input mode */ case KEY_IL: case 'o': move(++row, col = 0); insertln(); input(); break;

/* d: delete current line */ case KEY_DL: case 'd': deleteln(); break; /* ^L: redraw screen */ case KEY_CLEAR: case CTRL('L'): wrefresh(curscr); break;

/* w: write and quit */ case 'w': return; /* q: quit without writing */ case 'q': endwin(); exit(2); default: flash(); break; } } }

/* * Insert mode: accept characters and insert them. * End with ^D or EIC */ input() { int c;

standout(); mvaddstr(LINES - 1, COLS - 20, "INPUT MODE"); standend(); move(row, col); refresh(); for (;;) { c = getch(); if (c == CTRL('D') || c == KEY_EIC) break; insch(c); move(row, ++col); refresh(); } move(LINES - 1, COLS - 20); clrtoeol(); move(row, col); refresh(); }

Next topic: The highlight program
Previous topic: ETI program examples

© 2004 The SCO Group, Inc. All rights reserved.
UnixWare 7 Release 7.1.4 - 27 April 2004