Overview
An edited input stream provides simple interactive editing of an underlying input stream. The source of input for an edited stream is almost always a user typing on a keyboard. Each character read from the keyboard stream is echoed to an output stream, which is almost always a display. (In what follows we shall refer to the keyboard and display streams, with the understanding that it is ok to use other input and output streams.)
The edited input stream contains a buffer of characters that have been read and echoed, but not made available to the client of the edited stream. A client-supplied procedure of type DeliverWhenProc examines each input character and decides whether or not to activate the buffer, i.e. move it to a separate area for delivery to the client. Buffered characters may be changed (this is the whole idea); the changes are specified by special characters in the input stream. Activating the buffer empties it, so only characters read and buffered after the activation may be edited.
The following characters in the input stream are interpreted as editing commands:
^A (or BS): erase the last character in the buffer; noop if buffer empty.
^W: erase the last word in the buffer (where a word is a run of consecutive letters and numbers); noop if buffer empty.
^Q: erase the buffer back to the last CR (erase entire buffer if no CR); noop if buffer empty.
ESC: if buffer is empty, insert the contents of the buffer at the last activation; noop if buffer non-empty.
DEL: the effect is client-defined; the stream raises EditedStream.Rubout (and all buffered characters are discarded from the edited stream, but not erased from the display, when this exception is unwound). By convention, DEL escapes from an application's current input, perhaps retrying it from the beginning.
Control of the edited stream works as follows: a DeliverWhenProc is plugged into the stream when it is created, and may be changed by calling SetDeliverWhen. The first GetChar on the edited stream enters a loop, calling GetChar on the underlying keyboard stream and echoing to the display stream until the DeliverWhenProc returns activate: TRUE (or end of stream is reached on the keyboard). Then the current buffer contents are activated and the first character of the buffer is returned. Succeding GetChars return succeeding characters until the previous buffer contents are exhausted, at which point the underlying keyboard stream is called again. This means that echoing does not begin until GetChar is called, and can lag behind typing.
A DeliverWhenProc can implement context-dependent editing features, such as command completion. The edited stream passes four parameters to the DeliverWhenProc on each call: the next character of input, the current buffer contents, the stream itself, and a REF ANY "context" specified by the client when the DeliverWhenProc was passed to the stream The "context" can be used to maintain state, such as a table of keywords to be recognized, across calls to a DeliverWhenProc. The DeliverWhenProc may examine the buffer, but does not modify it directly (to keep echoing in sync). Instead it calls AppendBufferChars to add new characters to the buffer, and UnAppendBufferChars to remove trailing characters from the buffer. If a DeliverWhenProc completely processes a character in this way, it can prevent the edited stream from performing its usual processing on the character by returning appendChar: FALSE. In this way, even the behavior of "built-in" editing characters such as ESC and DEL can be specified in the DeliverWhenProc.
The SetMode procedure supports several user interface requirements. SetMode allows an application to "stuff" a default value as if the user had typed it, and make this value subject to editing by the user. The value may be loaded in "pending delete" mode: if the user types a non-editing character, the preloaded value is erased before the character is interpreted. SetMode can cause all printing characters to echo as '* so that, for instance, a password does not appear on the display.
Edited stream creation
DIRECTORY
IO USING [STREAM, StreamProcs], Rope USING [ROPE];
EditedStream:
CEDAR
DEFINITIONS =
BEGIN
STREAM: TYPE = IO.STREAM; ROPE: TYPE = Rope.ROPE;
Create:
PROC [in:
STREAM, echoTo:
STREAM, deliverWhen: DeliverWhenProc ← IsACR, context:
REF ANY ← NIL]
RETURNS [STREAM];
Rubout: ERROR [stream: STREAM];
DeliverWhenProc:
TYPE =
PROC [char:
CHAR, buffer:
REF
TEXT, stream:
STREAM, context:
REF ANY]
RETURNS [appendChar: BOOL, activate: BOOL];
IsACR: DeliverWhenProc;
A simple and useful DeliverWhenProc: returns [appendChar: TRUE, activate: char = CR]
Edited stream operations
GetDeliverWhen: PROC [self: STREAM] RETURNS [proc: DeliverWhenProc, context: REF ANY];
SetDeliverWhen: PROC [self: STREAM, proc: DeliverWhenProc, context: REF ANY ← NIL];
AppendBufferChars:
PROC [stream:
STREAM, chars:
ROPE];
Append chars to stream's buffer (and echo to the echo stream). Called from a DeliverWhenProc.
UnAppendBufferChars:
PROC [stream:
STREAM, nChars:
NAT];
Erase the last nChars characters from stream's buffer (and from the echo stream). Called from a DeliverWhenProc.
SetMode:
PROC [stream:
STREAM, stuff:
ROPE ←
NIL, pendingDelete:
BOOL ←
TRUE,
echoAsterisks: BOOL ← FALSE];
Discard any activated input and clear the buffer. Append stuff to the buffer as if AppendBufferChars had been called.
pendingDelete means "if the first call to the DeliverWhenProc returns appendChar: TRUE and activate: FALSE and is not one of the standard editing characters (ControlA, BS, ControlW, ControlQ, DEL), then erase stuff (as if UnAppendBufferChars had been called)".
echoAsterisks means "echo any character whose Ascii code is greater than Ascii.SP as '*". This applies to stuff and to all characters read from the underlying input stream, and is turned off by activation.
Generic stream operations
GetEcho and SetEcho, though generic, are implemented directly by only a few stream classes (including edited streams).
SetEcho:
PROC [self:
STREAM, echoTo:
STREAM];
Causes every character read from self to be echoed to echoTo if non-NIL. If not implemented in a class-specific manner (as for edited streams), implemented by modifying the stream "self" so that each get performs the specified echoing.
GetEcho:
PROC [self:
STREAM]
RETURNS [oldEcho:
STREAM];
Returns the current echo stream.
Implementing a stream class
AddStreamProcs is analogous to IO.CreateStreamProcs. It is convenience for use in stream class implementations that implement echoing.
AddStreamProcs:
PROC [
to: REF IO.StreamProcs,
setEcho: PROC [self: STREAM, echoTo: STREAM] ← NIL,
getEcho: PROC [self: STREAM] RETURNS [oldEcho: STREAM] ← NIL
]
RETURNS [REF IO.StreamProcs]; -- result = to; to^ is modified by call
END.