Meanwhile, users have continued to demand better and better interfaces. With stagnation in the operating system, the responsibility for improvement has fallen to individual programs. The result is that some programs (the ones that do all the work themselves) have very good interfaces, while others (the ones that depend upon operating system services) have very bad ones.
It would be very difficult to make the kernel include all the editing features from Emacs or vi. These editors are large and complicated programs, and a reasonably complete imitation of either of them must also be large and complicated. Attempting to make the same kernel code work on different versions of Unix makes the complexity even worse.
But a complete clone of Emacs is more than most programs really need. As Kernighan and Plauger put it, ``most users of a tool are willing to meet you halfway; if you do ninety percent of the job, they will be ecstatic.'' The standard kernel provides about fifty percent of what user testing shows that people want out of a command line editor. A few select enhancements will raise this figure to more than ninety nine percent.
Most importantly, it turns out, people want to be able to delete things: the previous character, the next character, the previous word, the whole line, and to the end of the line. They want to be able to move up and down a history list. They also want to be able to move left and right within the line they are editing, a character or a word at a time, and to the start or end of the line. Finally, they want to be able to complete partially-typed filenames and to clear the screen.
Other, more elaborate features are occasionally useful, but most users will never notice that anything is missing if they can do the things listed above. Selecting this small set of features to implement also means that portability need not be a major concern, since the ideas can easily be applied to a different kernel implementation even if the code itself cannot be.
One way to work around this limitation without introducing an entirely different representation is to use two queues for the current input line. The raw queue still holds the characters are to the left of the cursor, and a new queue, the editing queue, holds the ones to the right of it, if there are any. As the cursor is moved left, characters are removed from the raw queue and placed into the editing queue. When moving the other direction, the opposite happens.
So, for example, if someone had typed the line
sort file | uniq -c | sort -rn
and moved the cursor back over the i as shown, the raw queue would contain
sort file | un
and the contents of the editing queue would be
nr- tros | c- qi
The characters in the editing queue are in reverse order because it is really being used as a stack, not a queue. When the characters are moved back, one at a time, into the raw queue, they will again be in the correct order.
Since the characters after the cursor are kept in their own queue, inserting and deleting characters before the cursor can be done in exactly the same way as with the standard driver. As a result, most of the code never needs to know that the editing queue exists at all. Most of the parts that do know about it only use the two functions that move the cursor left and right.
The functions that move the cursor left and right update the structures in memory and the image on the terminal at the same time. The only control character that the updating routine uses is Backspace, so it should work with nearly any terminal.
This is lucky, because a minimal version of Emacs is much easier to write than a minimal version of vi. A vi editing mode would be a good thing to add eventually, but I have given up on it for now because vi's compound command structure and the awkward access to data in BSD queues made it too hard to do a good job.
Most basic Emacs commands, on the other hand, are very straightforward to implement. Control-B and Control-F, which move the cursor left and right, respectively, simply call the primitive function that performs this task. Control-A and Control-E, which move to the start and end of the line, do the same, but keep calling the function until it fails, which means that the end of the line has been reached.
The deletion commands are only slightly more complicated. Control-K, which deletes up to the end of the line, first checks to see how many characters there are after the cursor, moves forward that many characters, and then deletes backward the same number of times. Control-D, which deletes a single character, is cursed by being the same character that Unix uses for end-of-file. If there are any characters after the cursor, it deletes one; otherwise, it falls through to the normal input processing which interprets it as end-of-file.
These Emacs commands, incidentally, are only interpreted when the L_EMACS bit is set in the terminal control flags and the terminal is in canonical mode. This allows anyone who only wants to use the standard terminal facilities to disable the Emacs commands with stty -emacs. There is room for an L_VI flag for when a more ambitious version adds support for the vi command set.
If the next character that arrives is also ESC, it falls through into the normal processing, so a csh-style filename completion facility can still be used by typing ESC ESC. If the character is b or f, then the sequence is the Emacs backward- or forward-word command and the cursor is moved backward or forward until a word boundary or the end of a line is reached.
If the character after ESC is [ or O, then it is assumed to be part of the sequence for a VT100-style arrow key, and another bit is set to indicate this. When the following character arrives, if it is A, B, C, or D, then the appropriate arrow key action is taken.
It is a shame to hardwire these sequences into the program, but they are used by nearly every terminal, they are specified by an ANSI standard, and the code to support them is much less complicated than a more configurable version would be. The same features are still available on non-ANSI terminals by using equivalent Emacs commands rather than arrows.
In the current version, the real work of maintaining the history list is done by a daemon, ttyd, which runs as a user process and makes ioctl calls to listen for instructions from the terminal driver. The driver can post requests for the previous or next item from a terminal's history list or to add a new line to the list. The daemon satisfies these by using additional ioctls to query and set the contents of a terminal's current input buffer.
Each process on each terminal has a separate history list so programs do not interfere with each other. Typed input is added to the history list only when the terminal is in canonical mode and only when echoing is turned on, so there should be no risk of passwords ending up in the history by mistake.
Like the Emacs features, the history list can be enabled or disabled for a particular terminal by setting or resetting the L_HISTORY bit using stty.
The new ioctls that were added to support the history features also turn out to have more general uses. The header-editing feature in Berkeley mail, for instance, stuffs each header into the editing buffer by faking a series of keystrokes. When rewritten using one of the new ioctls, it is simpler, shorter, and less prone to mysterious bugs than the current version.
As mentioned above, csh-style completion still works with the modified terminal driver. The csh feature works by making the system return a partially typed line and then faking keystrokes to get the completed version back into the editing buffer. As the manual notes, this approach is ``ugly and expensive,'' and it requires each program that needs a completion feature to do all the work itself.
A variation on this theme gives control back to the user program by sending it a signal when the completion character is typed. The signal handler then uses one of the new ioctls mentioned above to retrieve the contents of the current input line. It completes the line and uses another ioctl to put the modified line back into the terminal driver's queue. This approach still requires the cooperation of each program, and also requires the addition of a new signal to the system. The current versions of NetBSD, OpenBSD, FreeBSD, and Linux each have only one slot left for a new signal, and it doesn't seem very nice to take the last one.
A third approach gives the responsibility for completion to the daemon that is already providing history services. This eliminates the need to include completion code in every program, but it also means that programs cannot tailor the completion routines to meet their specific needs. This is also difficult to implement on a BSD system, because there is no easy way for a BSD user program to find out the working directory of another process, and filenames cannot be completed without knowing this.
Because of these problems, I am still experimenting with other ways the operating system might be able to provide a generalized completion facility.
In addition, the modified terminal driver can automatically set the erase character appropriately whenever either Backspace or Delete is typed in canonical mode, so raw mode programs are also informed which key is correct. This special treatment for Backspace and Delete can be enabled or disabled by setting or resetting the L_SETERASE bit with stty. People who prefer to use Delete as their interrupt character will obviously choose to disable it.
Finally, as mentioned earlier, a surprisingly popular feature of tcsh is its ability to clear the screen when a user types Control-L. Since the terminal driver does not have access to the termcap or terminfo database, it does not know what control sequence (if any) will clear the screen. It does, however, usually know how many lines tall the screen is, and outputting that number of blank lines is not a bad substitute.