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.