24.1 The Input Editor

  • 24.1.1 The Input Editing Stream Protocol
  • 24.1.2 Suggestions for Input Editing Commands
  • An input editing stream "encapsulates" an interactive stream, that is, most operations are handled by the encapsulated interactive stream, but some operations are handled directly by the input editing stream itself. (See Appendix C for a discussion of encapsulating streams.) [annotate]

    An input editing stream will have the following components: [annotate]

    The input editing stream may also have other components to store internal state, such as a slot to accumulate a numeric argument or remember the most recently used presentation history, and so forth. These other components are explicitly left unspecified. [annotate]

    The high level description of the operation of the input editor is that it reads either "real" gestures from the user (such as characters from the keyboard or pointer button events) or input editing commands. The input editing commands can modify the state of the input buffer. When such modifications take place, it is necessary to "rescan" the input buffer, that is, reset the scan pointer SP to its original state and reparse the contents of the input editor buffer before reading any other gestures from the user. While this rescanning operation is taking place, the "rescan in progress" flag is set to true. The relationship SP≤IP≤FP always holds. [annotate]

    Note: Instead of SP≤IP≤FP, let's use a more fuzzy definition that says "SP will never move beyond IP during rescan". That solves the problem this equation is meant to handle, without having to trigger rescans during IP motion. [edit]-- Troels Henriksen 2007-01-30 09:54Z

    This relationship is probably a braino, the scan pointer scans whole buffer up to the FP. so it should be

    SP <= FP and IP <= FP

    [edit]-- DK 2022-08-04 09:41Z

    The overall control structure of the input editor is: [annotate]

    (catch 'rescan                  ;thrown to when a rescan is invoked
      (reset-scan-pointer stream)   ;sets STREAM-RESCANNING-P to T
         (funcall continuation stream)))

    where stream is the input editing stream and continuation is the code supplied by the programmer, and typically contains calls to such functions as accept and read-token (which will eventually call stream-read-gesture). When a rescan operation is invoked, it has the effect of throwing to the rescan tag in the example above. The loop is terminated when an activation gesture is seen, and at that point the values produced by continuation are returned as values from the input editor. [annotate]


    This 'overall control structure' is correct. In McCLIM it was interpreted as a typo, because it was implemented (more or less) as:

    (loop ;; external loop (catch 'rescan (reset-scan-pointer stream) (return (funcall continuation stream)))

    and it was expected that the continuation incorporates the loop. That expectation is correct, but we call the continuation in a loop regardless, because we want to allow editing the input line even when parsing is successful, until the activation gesture is 'seen'.

    [edit]-- DK 2022-08-04 09:37Z

    The important point is that functions such as accept, read-gesture, and unread-gesture read (or restore) the next gesture object from the buffer at the position pointed to by the scan pointer SP. However, insertion and input editing commands take place at the position pointed to by IP. The purpose of the rescanning operation is to eventually ensure that all the input gestures issued by the user (typed characters, pointer button presses, and so forth) have been read by CLIM. During input editing, the input editor should maintain some sort of visible cursor to remind the user of the position of IP. [annotate]

    The overall structure of stream-read-gesture on an input editing stream is: [annotate]

      (rescan-if-necessary stream)
        ;; If SP is less than FP
        ;;   Then get the next gesture from the input editor buffer at SP
        ;;   and increment SP
        ;;   Else read the next gesture from the encapsulated stream
        ;;   and insert it into the buffer at IP
        ;; Set the "rescan in progress" flag to false
        ;; Call STREAM-PROCESS-GESTURE on the gesture
        ;;   If it was a "real" gesture
        ;;     Then exit with the gesture as the result
        ;;     Else it was an input editing command (which has already been
        ;;     processed), so continue looping

    When a new gesture object is inserted into the input editor buffer, it is inserted at the insertion pointer IP. If IP = FP, this is accomplished by a vector-push-extend-like operation on the input buffer and FP, and then incrementing IP. If IP<FP, CLIM must first "make room" for the new gesture in the input buffer, then insert the gesture at IP, then increment both IP and FP. [annotate]

    When the user requests an input editor motion command, only the insertion pointer IP is affected. Motion commands do not need to request a rescan operation. [annotate]

    Note: Although it is true that pure motion does not need to trigger rescan, just adjusting IP can violate the SP<=IP relationship. So make up your mind: Either loosen the SP<=IP assertion, or indicate that editor motion can indeed trigger rescan. [edit]-- Gilbert Baumann 2006-03-01 10:51Z

    When the user requests an input editor deletion command, the sequence of gesture objects at IP are removed, and IP and FP must be modified to reflect the new state of the input buffer. Deletion commands (and other commands that modify the input buffer) must arrange for a rescan to occur when they are done modifying the buffer, either by calling queue-rescan or immediate-rescan. [annotate]

    CLIM implementations are free to put special objects in the input editor buffer, such as "noise strings" and "accept results". A "noise string" is used to represent some sort of in-line prompt and is never seen as input; the prompt-for-accept method may insert a noise string into the input buffer. An "accept result" is an object in the input buffer that is used to represent some object that was inserted into the input buffer (typically via a pointer gesture) that has no readable representation (in the Lisp sense); presentation-replace-input may create accept results. Noise strings are skipped over by input editing commands, and accept results are treated as a single gesture. [annotate]

    interactive-stream-p  object [Protocol Predicate]

    Returns true if object is an interactive stream, that is, a bidrectional stream intended for user interactions. Otherwise it returns false. This is exactly the same function as in X3J13 Common Lisp, except that in CLIM it is a generic function. [annotate]

    The input editor need only be fully implemented for interactive streams. [annotate]


    input-editing-stream   [Protocol Class]

    The protocol class that corresponds to an input editing stream. If you want to create a new class that behaves like an input editing stream, it should be a subclass of input-editing-stream. All instantiable subclasses of input-editing-stream must obey the input editing stream protocol. [annotate]


    input-editing-stream-p  object [Protocol Predicate]

    Returns true if object is an input editing stream (that is, a stream of the sort created by a call to with-input-editing), otherwise returns false. [annotate]


    standard-input-editing-stream   [Class]

    The instantiable class that implements CLIM's standard input editor. This is the class of stream created by calling with-input-editing. [annotate]

    Members of this class are mutable. [annotate]


    with-input-editing  (&optional stream &key input-sensitizer initial-contents class) &body body [Macro]

    Establishes a context in which the user can edit the input typed in on the interactive stream stream. body is then executed in this context, and the values returned by body are returned as the values of with-input-editing. body may have zero or more declarations as its first forms. [annotate]

    The stream argument is not evaluated, and must be a symbol that is bound to an input stream. If stream is t (the default), *standard-input* is used. If stream is a stream that is not an interactive stream, then with-input-editing is equivalent to progn. [annotate]

    input-sensitizer, if supplied, is a function of two arguments, a stream and a continuation function; the function has dynamic extent. The continuation, supplied by CLIM, is responsible for displaying output corresponding to the user's input on the stream. The input-sensitizer function will typically call with-output-as-presentation in order to make the output produced by the continuation sensitive. [annotate]

    If initial-contents is supplied, it must be either a string or a list of two elements, an object and a presentation type. If it is a string, the string will be inserted into the input buffer using replace-input. If it is a list, the printed representation of the object will be inserted into the input buffer using presentation-replace-input. [annotate]


    with-input-editor-typeout  (&optional stream &key erase) &body body [Macro]

    Establishes a context inside of with-input-editing in which output can be done by body to the input editing stream stream. If erase is true, the area underneath the typeout will be erased before the typeout is done. with-input-editor-typeout should call fresh-line before and after evaluating the body. body may have zero or more declarations as its first forms. [annotate]

    The stream argument is not evaluated, and must be a symbol that is bound to a stream. If stream is t (the default), *standard-input* is used. If stream is a stream that is not an input editing stream, then with-input-editor-typeout is equivalent to calling fresh-line, evaluating the body, and then calling fresh-line again. [annotate]


    Note: What's the point of this? Should it modify the input buffer? Insert noise strings? What is "the area underneath the typeout"? [edit]-- Troels "Athas" Henriksen 2006-11-07 19:22Z

    input-editor-format  stream format-string &rest format-args [Generic Function]

    This function is like format, except that it is intended to be called on input editing streams. It arranges to insert "noise strings" in the input editor's input buffer. Programmers can use this to display in-line prompts in accept methods. [annotate]

    If stream is a stream that is not an input editing stream, then input-editor-format is equivalent to format. [annotate]


    24.1.1 The Input Editing Stream Protocol

    Input editing streams obey both the extended input and extended output stream protocols, and must support the generic functions that comprise those protocols. For the most part, this will simply entail "trampolining" those operations to the encapsulated interactive stream. However, some generic functions as stream-read-gesture and stream-unread-gesture will need methods that observe the use of the input editor's scan pointer. [annotate]

    Input editing streams will typically also implement methods for prompt-for-accept (in order to provide in-line prompting that interacts correctly with input editing) and stream-accept (in order to cause accept to obey the scan pointer). [annotate]

    The following generic functions comprise the remainder of the input editing protocol, and must be implemented for all classes that inherit from input-editing-stream. [annotate]

    stream-input-buffer  (stream input-editing-stream) [Method]

    Returns the input buffer (that is, the string being edited) associated with the input editing stream stream. This must be an unspecialized vector with a fill pointer. The fill pointer of the vector points past the last gesture object in the buffer. During input editing, this buffer is side-effected. The consequences of modifying the input buffer by means other than the specified API (such as replace-input) are unspecified. [annotate]

    Note: Instead of specifying that the buffer is a vector, make it an opaque object Optionally, add the fill pointer accessor stream-fill-pointer. [edit]-- DK 2022-07-20 11:53Z


    stream-insertion-pointer  stream [Generic Function]

    Returns an integer corresponding to the current input position in the input editing stream stream's buffer, that is, the point in the buffer at which the next user input gesture will be inserted. The insertion pointer will always be less than (fill-pointer (stream-input-buffer stream)). The insertion pointer can also be thought of as an editing cursor. [annotate]


    (setf stream-insertion-pointer)  pointer stream [Generic Function]

    Changes the input position of the input editing stream stream to pointer. pointer is an integer, and must be less than (fill-pointer (stream-input-buffer stream)). [annotate]


    stream-scan-pointer  stream [Generic Function]

    Returns an integer corresponding to the current scan pointer in the input editing stream stream's buffer, that is, the point in the buffer at which calls to accept have stopped parsing input. The scan pointer will always be less than or equal to (stream-insertion-pointer stream). [annotate]


    (setf stream-scan-pointer)  pointer stream [Generic Function]

    Changes the scan pointer of the input editing stream stream to pointer. pointer is an integer, and must be less than or equal to (stream-insertion-pointer stream). [annotate]


    stream-rescanning-p  stream [Generic Function]

    Returns the state of the input editing stream stream's "rescan in progress" flag, which is true if stream is performing a rescan operation, otherwise it is false. All extended input streams must implement a method for this, but non-input editing streams will always returns false. [annotate]


    reset-scan-pointer  stream &optional (scan-pointer 0) [Generic Function]

    Sets the input editing stream stream's scan pointer to scan-pointer, and sets the state of stream-rescanning-p to true. [annotate]


    immediate-rescan  stream [Generic Function]

    Invokes a rescan operation immediately by "throwing" out to the most recent invocation of with-input-editing. [annotate]


    queue-rescan  stream [Generic Function]

    Indicates that a rescan operation on the input editing stream stream should take place after the next non-input editing gesture is read by setting the "rescan queued" flag to true. [annotate]


    rescan-if-necessary  stream &optional inhibit-activation [Generic Function]

    Invokes a rescan operation on the input editing stream stream if queue-rescan was called on the same stream and no intervening rescan operation has taken place. Resets the state of the "rescan queued" flag to false. [annotate]

    If inhibit-activation is false, the input line will not be activated even if there is an activation character in it. [annotate]


    erase-input-buffer  stream &optional (start-position 0) [Generic Function]

    Erases the part of the display that corresponds to the input editor's buffer starting at the position start-position. [annotate]


    redraw-input-buffer  stream &optional (start-position 0) [Generic Function]

    Displays the input editor's buffer starting at the position start-position on the interactive stream that is encapsulated by the input editing stream stream. [annotate]


    stream-process-gesture  stream gesture type [Generic Function]

    If gesture is an input editing command, stream-process-gesture performs the input editing operation on the input editing stream stream and returns nil. Otherwise, it returns the two values gesture and type. [annotate]


    stream-read-gesture  (stream standard-input-editing-stream) &key  [Method]

    Reads and returns a gesture from the user on the input editing stream stream. [annotate]

    The stream-read-gesture method must call stream-process-gesture, which will either return a "real" gesture (such as a typed character, a pointer gesture, or a timeout) or will return nil (indicating that some sort of input editing operation was performed). stream-read-gesture must only return when a real gesture was been read; if an input editing operation was performed, stream-read-gesture will loop until a "real" gesture is typed by the user. [annotate]


    stream-unread-gesture  (stream standard-input-editing-stream) gesture [Method]

    24.1.2 Suggestions for Input Editing Commands

    An implementation of the input editor should provide a set of generally useful input editing commands. The exact set of these commands is unspecified, and the key bindings for these commands may vary from platform to platform. The following is a suggested minimum set of input editing commands and key bindings, taken roughly from EMACS. [annotate]

    Input editor commandkey binding
    Forward character control-F
    Forward word meta-F
    Backward character control-B
    Backward word meta-B
    Beginning of line control-A
    End of line control-E
    Next line control-N
    Previous line control-P
    Beginning of buffer meta-<
    End of buffer meta-<
    Delete next character control-D
    Delete next word meta-D
    Delete previous character Rubout
    Delete previous word m-Rubout
    Kill to end of line control-K
    Clear input buffer varies
    Insert new line control-O
    Transpose adjacent characters control-T
    Transpose adjacent words meta-T
    Yank from kill ring control-Y
    Yank from presentation history control-meta-Y
    Yank next item meta-Y
    Scroll output history forward control-V
    Scroll output history backward meta-V

    An implementation of the input may also support "numeric arguments" (such as control-0, control-1, meta-0, and so forth) that modify the behavior of the input editing commands. For instance, the motion and deletion commands should be repeated as many times as specified by the numeric argument. Furthermore, the accumulated numeric argument should be passed to the command processor in such a way that substitute-numeric-argument-marker can be used to insert the numeric argument into a command that was read via a keystroke accelerator. [annotate]

    add-input-editor-command  gestures function [Function]

    Adds an input editing command that causes function to be executed when the specified gesture(s) are typed by the user. gestures is either a single gesture name, or a list of gesture names. When gestures is a sequence of gesture names, the function is executed only after all of the gestures are typed in order with no intervening gestures. (This is used to implement "prefixed" commands, such as the control-X control-F command one might fix in EMACS.) [annotate]



    What is the signature of /function/?

    Why isn't this tied to a command table or a frame class or anything? How is it prevented that the input editor commands of one application do not clobber the editing commands of another?

    [edit]-- Gilbert Baumann 2005-11-27 01:08Z

    I think function does not take any arguments.

    I also think that these input-editor commands are global, and that this facility is not meant to be a way to create application-specific input-editor commands. If this is true, however, then this function is truly useless, as CLIM does not really specify an interface to the input-editor buffer, so you can't do anything portably anyway.

    [edit]-- Troels "Athas" Henriksen 2006-10-28 16:55Z

    To answer my own question.

    Section 17.5, Examples of extending the input editor, of the CLIM 2.2 User Guide (Franz' edition) has examples on how to define your own input editor commands.

    The signature of the command functions seems to be /stream/ /input-buffer/ /gesture/ /numeric-argument/.

    [edit]-- Gilbert Baumann 2008-08-17 22:43Z