How to customize NMODE
Alan Snyder
24 September 1982
-------------------------------------------------------------------------------

This memo explains how to customize NMODE by redefining the effect of input
keystrokes.  NMODE is customized by executing Lisp forms.  These forms may be
executed directly within NMODE (using Lisp-E), or may be stored in an INIT
file, which is read by NMODE when it first starts up.  The name of the INIT
file read by NMODE is "NMODE.INIT" in the user's home directory.

There are three concepts that must be understood to customize NMODE: Commands,
Functions, and Modes.

1) Commands.  The effect of given keystroke or sequence of keystrokes in
NMODE is based on a mapping between "commands" and "functions".
A "command" may be either a single "extended character" or a sequence
of characters.  An extended character is a 9-bit character with
distinct "Control" and "Meta" bits.  Thus "C-M-A" is a single "extended
character", even though on many terminals you have to use two keystrokes
to enter it.  Extended characters are specified using the macro X-CHAR,
for example:

  (x-char A)		the letter "A" (upper case)
  (x-char C-F)		Control-F
  (x-char C-M-Z)	Control-Meta-Z
  (x-char CR)		Carriage-Return
  (x-char TAB)		Tab
  (x-char BACKSPACE)	Backspace
  (x-char NEWLINE)	Newline
  (x-char RUBOUT)	Rubout
  (x-char C-M-RUBOUT)	Control-Meta-Rubout

(The macros described in this section are defined in the load module
EXTENDED-CHAR.)  It is important to note that on most terminals, some Ascii
control characters are mapped to extended "Control" characters and some aren't.
Those that aren't are: Backspace, CR, Newline, Tab, and Escape.  Even if you
type "CNTL-I" on the keyboard, you will get "Tab" and not "Control-I".  The
remaining Ascii control characters are mapped to extended "Control" characters,
thus typing "CNTL-A" on the keyboard gives "Control-A".

As mentioned above, a command can be a sequence of characters.  There are two
forms: Prefix commands and Extended commands.

Prefix commands: A prefix command consists of two characters, the first of
which is a defined "prefix character".  In NMODE, there are 3 predefined prefix
characters: C-X, ESC, and C-].  Prefix commands are specified using the X-CHARS
macro, for example:

  (x-chars C-X C-F)
  (x-chars ESC A)
  (x-chars C-] E)

Extended commands: An extended command consists of the character M-X and a
string.  Extended commands are defined using the M-X macro, for example:

  (M-X "Lisp Mode")
  (M-X "Revert File")

The case of the letters in the string is irrelevant, except to specify how the
command name will be displayed when "completion" is used by the user.  By
convention, the first letter of each word in an extended command name is
capitalized.

2) Functions.  NMODE commands are implemented by PSL functions.  By convention,
most (but not all) PSL functions that implement NMODE commands have names
ending with "-COMMAND", for example, MOVE-FORWARD-CHARACTER-COMMAND.

An NMODE command function should take no arguments.  The function can perform
its task using a large number of existing support functions; see PN:BUFFER.SL
and PN:MOVE-COMMANDS.SL for examples.  A command function can determine the
command argument (given by C-U) by inspecting global variables:

  nmode-command-argument: the numeric value (default: 1)
  nmode-command-argument-given: T if the user specified an argument
  nmode-command-number-given: T if the user typed digits in the argument

See the files PN:MOVE-COMMANDS.SL, PN:LISP-COMMANDS.SL, and PN:COMMANDS.SL for
many examples of NMODE command functions.

3) Modes.  The mapping between commands and functions is dependent on the
current "mode".  Examples of existing modes are "Text Mode", which is the basic
mode for text editing, "Lisp Mode", which is an extension of "Text Mode" for
editing and executing Lisp code, and "Dired Mode", which is a specialized mode
for the Directory Editor Subsystem.

A mode is defined by a list of Lisp forms which are evaluated to determine the
state of a Dispatch Table.  The Dispatch Table is what is actually used to map
from commands to functions.  Every time the user selects a new buffer, the
Dispatch Table is cleared and the Lisp forms defining the mode for the new
buffer are evaluated to fill the Dispatch Table.  The forms are evaluated in
reverse order, so that the first form is evaluated last.  Thus, any command
definitions made by one form supercede those made by forms appearing after it
in the list.

Two functions are commonly invoked by mode-defining forms: NMODE-ESTABLISH-MODE
and NMODE-DEFINE-COMMANDS.  NMODE-ESTABLISH-MODE takes one argument, a list of
mode defining forms, and evaluates those forms.  Thus, NMODE-ESTABLISH-MODE can
be used to define one mode in terms of (as an extension of or a modification
to) another mode.

NMODE-DEFINE-COMMANDS takes one argument, a list of pairs, where each pair
consists of a COMMAND and a FUNCTION.  This form of list is called a "command
list".  Command lists are not used directly to map from commands to functions.
Instead, NMODE-DEFINE-COMMANDS reads the command list it is given and for each
COMMAND-FUNCTION pair in the command list (in order), it alters the Dispatch
Table to map the specified COMMAND to the corresponding FUNCTION.

Note that as a convenience, whenever you define an "upper case" command, the
corresponding "lower case" command is also defined to map to the same function.
Thus, if you define C-M-A, you automatically define C-M-a to map to the same
function.  If you want the lower case command to map to a different function,
you must define the lower case command "after" defining the upper case command.

The usual technique for modifying one or more existing modes is to modify one
of the command lists given to NMODE-DEFINE-COMMANDS.  The file PN:MODE-DEFS.SL
contains the definition of most predefined NMODE command lists, as well as the
definition of most predefined modes.  To modify a mode or modes, you must alter
one or more command lists by adding (or perhaps removing) entries.  Command
lists are manipulated using two functions:

  (add-to-command-list list-name command func)
  (remove-from-command-list list-name command)

Here are some examples:

(add-to-command-list
 'text-command-list (x-char BACKSPACE) 'delete-backward-character-command)

(add-to-command-list
 'lisp-command-list (x-char BACKSPACE) 'delete-backward-hacking-tabs-command)

(remove-from-command-list
 'read-only-text-command-list (x-char BACKSPACE))

  [The above forms change BACKSPACE from being the same as C-B to being
   the same as RUBOUT.]

(add-to-command-list
 'read-only-text-command-list (x-char M-@) 'set-mark-command)
 
  [The above form makes M-@ set the mark.]

(add-to-command-list
 'read-only-terminal-command-list (x-chars ESC Y) 'print-buffer-names-command)
 
  [The above form makes Esc-Y print a list of all buffer names.  Esc-Y is
   sent by HP264X terminals when the "Display Functions" key is hit.]

Note that these functions change only the command lists, not the Dispatch Table
which is actually used to map from commands to functions.  To cause the
Dispatch Table to be updated to reflect any changes in the command lists, you
must invoke the function NMODE-ESTABLISH-CURRENT-MODE.
