Artifact 8791f0f36c7908dd6d85905ac7643325ebabb6cca42bc2f5456ab39b129028ab:
- File
psl-1983/emode/emode.mss
— part of check-in
[eb17ceb7f6]
at
2020-04-21 19:40:01
on branch master
— Add Reduce 3.0 to the historical section of the archive, and some more
files relating to version sof PSL from the early 1980s. Thanks are due to
Paul McJones and Nelson Beebe for these, as well as to all the original
authors.git-svn-id: https://svn.code.sf.net/p/reduce-algebra/code/historical@5328 2bfe0521-f11c-4a00-b80e-6202646ff360 (user: arthurcnorman@users.sourceforge.net, size: 40318) [annotate] [blame] [check-ins using] [more...]
@use[bibliography = "<galway.scribe>biblio.bib"] @make[article] @style[references = STDalphabetic] @style[spacing 1] @style[indentation 5] @modify[enumerate, numbered=<@a. @,@i. >, spread 0, above 1, below 1] @modify[itemize,spread 0, above 1, below 1] @modify[example, above 1, below 1] @modify[description, spread 1, above 1, below 1] @modify[appendix, numbered=<APPENDIX @A: >] @pageheading[Left "Utah Symbolic Computation Group", Right "June 1982", Line "Operating Note No. 69" ] @set[page=1] @newpage[] @begin[titlepage] @begin[titlebox] @begin[center] @b[A Guide to EMODE] by William F. Galway and Martin L. Griss Department of Computer Science University of Utah Salt Lake City, Utah 84112 Last Revision: @value[date] @end[center] @end[titlebox] @begin[abstract] EMODE is a LISP-based EMACS-like editor that runs on the PSL system. This document is meant to serve as a guide to using EMODE--but will only be roughly up to date, since the system is in a state of transition. @end[abstract] @begin[Researchcredit] Work supported in part by the National Science Foundation under Grant No. MCS80-07034. @end[Researchcredit] @end[titlepage] @pageheading[Left "Guide to EMODE", Right "@value(Page)"] @set[page=1] @newpage[] @section[Introduction and Acknowledgments] @Comment{Needs more?} This paper describes the EMODE editor being developed for PSL @cite[PSL-manual]. EMODE is an interactive, EMACS like @cite[STALLMAN-ARTICLE-81], screen editor. EMODE provides multiple windows, can simultaneously support different "modes" of editing in different buffers, and supports a variety of CRT terminals such as the Teleray 1061 and the DEC VT-100. Several people have made contributions to EMODE. EMODE itself is based on an earlier editor EMID @cite[Armantrout81], written by Robert Armantrout and Martin Griss for LISP 1.6. Tony Carter has used EMODE to develop several large packages for VLSI circuitry design @cite[Carter81, Carter-THESIS]. Optimizations for the Vax version, and many useful comments, have been provided by Russ Fish. Several features have been added by Alan Snyder and Cris Perdue at Hewlett Packard Research Labs. Cris implemented the current version of "mode lists", while Alan has implemented a huge number of commands and improved the efficiency of several operations. @section[Running EMODE] EMODE is available as a "loadable" file. It can be invoked as follows: @begin[example] @@PSL:RLISP [1] load emode; [2] emode(); @end[example] Of course, you may choose to invoke RLISP (or PSL) differently, and to perform other operations before loading and running EMODE. From this point on the term "PSL" will be used to refer to this family of systems, independently of whether they use Lisp or RLISP syntax. The terminal that EMODE uses by default is determined by its LOADing the file DEFAULT-TERMINAL. At the University of Utah this is the TELERAY driver. At other sites, some other driver may be chosen as the default. To use a different terminal you must LOAD in a different "driver file" after loading EMODE. For example, to run EMODE on the Hewlett Packard 2648A terminal, you could type: @begin[example] @@PSL:RLISP [1] load emode, hp2648a; [2] emode(); @end[example] The following drivers are currently available: @begin[description,spread 0] AAA@\For the Ann Arbor Ambassador. DM1520@\For the Datamedia 1520. HP2648A@\For the Hewlett Packard 2648A and similar Hewlett Packard terminals. @Comment{Should we be this specific?} TELERAY@\For the Teleray 1061. VT52@\For the DEC VT52. VT100@\For the DEC VT100. @end[description] See section @ref[terminal-drivers] for information on creating new terminal drivers. EMODE is quite similar to EMACS @cite[EMACS-manual, STALLMAN-ARTICLE-81], although it doesn't have nearly as many commands. A detailed list of commands is given in appendix @ref[key-bindings]. This information can also be obtained by typing @w["HELP EMODE;"] to RLISP, or (equivalently) by reading the file PH:EMODE.HLP. The notation used here to describe character codes is basically the same as that used for EMACS. For example: C-Z means "control-Z", the character code produced by typing Z while holding down the control key. The ascii code for a control character is the same as the 5 low order bits of the original character--the code for Z is 132 octal, while the code for C-Z is 32 octal. M-Z means "meta-Z", the character produced by typing Z while holding down the meta key. To support those terminals without a meta key, the same result can normally be achieved by typing two characters--first the ESCAPE character, then the Z character. The ascii code for a meta character is the same as the original character with the parity bit set--the code for M-Z is 332 octal. (Some terminals use the ESCAPE character for other purposes, in which case the "META prefix" will be some other character.) Rather than using the EMACS convention, we write "control-meta" characters (such as C-M-Z) as "meta-control" characters (M-C-Z), since the latter notation better reflects the internal code (232 octal for M-C-Z). The C-Z character is used as a "meta-control" prefix, so one way to type M-C-Z is to type @w[C-Z C-Z]. (Another way to type it is to hold down the meta and control keys and type "Z".) When EMODE is started up as described above, it will immediately enter "two window mode". To enter "one window mode", you can type "C-X 1" (as in EMACS). Commands can be typed into a buffer shown in the top window. The result of evaluating a command is printed into the OUT_WINDOW buffer (shown in the bottom window). To evaluate the expression starting on the current line, type M-E. M-E will (normally) automatically enter two window mode if anything is "printed" to the OUT_WINDOW buffer. If you don't want to see things being printed to the output window, you can set the variable !*OUTWINDOW to NIL. (Or use the RLISP command "OFF OUTWINDOW;".) This prevents EMODE from automatically going into two window mode when something is printed to OUT_WINDOW. You must still use the "C-X 1" command to enter one window mode initially. Figure @ref[two-window-figure] shows EMODE in two window mode. In this mode the top window includes everything above (and including) the first line of dashes. This is followed by a single line window, showing the current prompt from PSL. Beneath this is the "output window", the window which usually shows the OUT_WINDOW buffer. This is followed by another single line window, which EMODE uses to prompt the user for values (not the same as PSL's prompt). @begin[figure] @begin[example] % Commands can be typed in the top window. % When they're executed the value is printed into % the OUT_WINDOW buffer. x := '(now is the time); y := cddr x; ----MAIN-----------------------------------------85%--- [7] ------------------------------------------------------- NIL (NOW IS THE TIME) (THE TIME) ----OUT_WINDOW-----------------------------------75%--- File for photo: s:twowindow.photo @end[example] @caption[Two window mode] @tag[two-window-figure] @end[figure] Figure @ref[one-window-figure] shows EMODE in one window mode. The "top window" takes up most of the screen, followed by EMODE's prompt line, and then by PSL's prompt line. @begin[figure] @begin[example] % Commands can be typed in the top window. % When they're executed the value is printed into % the OUT_WINDOW buffer. x := '(now is the time); y := cddr x; ----MAIN-----------------------------------------85%--- File for photo: s:onewindow.photo [7] @end[example] @caption[One window mode] @tag[one-window-figure] @end[figure] The BREAK handler has been modified by EMODE to "pop up" a "break window menu". This is illustrated in figure @ref[break-window-figure]. The commands in the menu can be executed with the M-E command, and you can also edit the BREAK buffer just like any other buffer. If you wish to move to another window, use the @w[C-X N] command. This may cause the break window to disappear as it is covered by some other window, but @w[C-X P] will find it and pop it to the "top" of the screen again. @begin[figure] @begin[example] cdr 2; +------------------------------+ |A ;% To abort | |Q ;% To quit | |T ;% To traceback | |I ;% Trace interpreted stuff | |R ;% Retry | |C ;% Continue, | | % using last value | ----MAIN-----------|? ;% For more help |- 4 lisp break> +----BREAK---------------11%---+ ---------------------------------------------------- NIL ***** An attempt was made to do CDR on `2', which is not a pair {99} Break loop ----OUT_WINDOW-----------------------------------75%--- File for photo: s:breakwindow.photo @end[example] @caption[A break window (doctored from the original)] @tag[break-window-figure] @end[figure] EMODE is not very robust in its handling of errors. Here's a summary of known problems and suggestions on how to deal with them: @begin[description] Garbage collection messages "blow up":@\Printing messages into EMODE buffers involves CONSing, so the system blows up if it tries to print a message from inside the garbage collector. EMODE sets GC OFF at load time. Always run EMODE with GC OFF. @begin[multiple] Terminal doesn't echo:@\This can be caused by abnormal exits from EMODE. If PSL is still running, you can call the routine "EchoOn" to turn echoing back on. (It's the routine "EchoOff" that turns echoing off, and starts "raw output" mode.) Otherwise, as may happen on the Vax running Unix, you will have to give shell commands to turn echoing back on. This is best done by defining the following alias in your ".login" file. @begin[example] alias rst 'reset; stty -litout intr ^C' @end[example] (That's a "control-C", not "uparrow C".) The "rst" command must be typed as "<LF>rst<LF>" because carriage-return processing is turned off. @end[multiple] "Garbled" printout:@\This is probably caused by EMODE's not running in "raw output" mode--a problem which can be caused by some other errors. A cure is to type @w[C-Z C-Z] to leave EMODE, and then to call EMODE again. This should reset the terminal mode to "raw mode" (by calling EchoOff). (The @w[C-Z C-Z] must be followed by a linefeed on the Vax, to force the @w[C-Z C-Z] to be read.) @begin[multiple] Stuck in an error:@\This is often caused by trying to evaluate an expression that lacks a closing parenthesis (or some other terminator)--producing a message something like: @begin[example] ***** Unexpected EOF while reading ... @end[example] If it's obvious that an additional parenthesis will cure the problem, you can use @w[C-X N] to select the input window and insert it. Then position the cursor to the left of the parenthesis and use @w[C-X N] to select the break window and "Quit". Otherwise you should use the "Abort" option of the break handler. Currently this resets the terminal mode (at least on the DEC-20), so you'll have to restart EMODE as described above. The BREAK window will still be present on the screen after restarting, even though you are no longer in the break loop. You can use the @w[C-X 2] or @w[C-X 1] command to get rid of the break window, and then use the @w[C-X B] command to select some buffer other than the break buffer. @end[multiple] @end[description] @section[A Guide to the Sources and Rebuilding] The "primary" sources for EMODE reside on UTAH-20: @begin[description] PES:@\Is defined locally as <GALWAY.EMODE.V2>. This directory is for the "version 2" of EMODE--being maintained now. The corresponding "logical name" on the VAX is "$pes". PE:@\Is defined as <PSL.EMODE>. Holds sources and documentation which may be generally useful to the public. It includes sources for the various terminal drivers available for EMODE. (Further described in section @ref[terminal-drivers].) The corresponding logical name on the VAX is "$pe". @end[description] The file PES:BUILD-EMODE.CTL is the command file for building EMODE on the DEC-20. Use SUBMIT or DO to run the command file, which builds EMODE in two parts on the local directory: EMODE-B-1.B and EMODE-B-2.B. PES:BUILD-EMODE.CSH (or $pes/build-emode.csh) is the build file for the VAX. It also builds the binary files on the "local directory". On both machines the ".B" files for the terminal drivers and for RAWIO.B are built separately. The PES:EMODE.TAGS file can be used with the TAGS facility provided by EMACS on the DEC-20. (Highly recommended!) @section[Terminology: Buffers, Views/Windows, and Virtual Screens] @Comment{Need to say more about NSTRUCT, refer to some manual.} "Buffers", "views", and "virtual screens" are the three major data structures in EMODE. Virtual screens correspond fairly closely to what are often called @i[windows] in other systems. They are rectangular regions on the screen, possibly overlapping, that characters can be written to. A virtual screen provides a sort of pseudo-hardware. The operations that can be performed on a virtual screen are modeled after what can be done with a real terminal. The use of a virtual screen provides these advantages: @begin[itemize] Operations on a virtual screen are machine independent. (To some extent, this will be less true if we try to support "fancier" graphics.) The "bandwidth problem" of maintaining the screen image is isolated to the virtual screen package--other programs don't have to worry about the problem. Several virtual screens can be shown on one physical screen. @end[itemize] Virtual screens are implemented as "Structs" using the "DefStruct" facility provided by the loadable file "NSTRUCT". Buffers hold the data to be edited, possibly something other than text, depending on the buffer's "data mode". Views are data structures used to display buffers on the screen, they may be made of several virtual screens. The term @i["window"] is often used instead of "view", when you see the one term it should be possible to substitute the other. Buffers and views are implemented as "environments". An environment is an association list of @w[(NAME . VALUE)] pairs. (These association lists are sometimes referred to as "descriptors".) The usual method for working with an environment is "restoring" (or "selecting") the environment by calling the procedure "RestoreEnv". This sets each variable name in the list to its associated value. The procedure "SaveEnv" does the inverse operation of updating the values of each variable name in the association list. (This is done "destructively", using RPLACD.) The names in an environment are sometimes called "per-environment" variables. Names in "buffer environments" are called "per-buffer variables", and similarly for "per-view variables". Buffers and views are just environments that follow certain conventions. These conventions are that they always include certain @w[(name . value)] pairs--i.e. that they always include certain "per-buffer" or "per-view" variables. For example, the required per-buffer variables include: @begin[description] buffers_file@\The name (a string) of a file associated with the buffer, or NIL if no file is associated with the buffer. buffers_view_creator@\A routine that creates a "view" (or "window") looking into the buffer. @end[description] In addition to the required per-buffer variables, text buffers include variables containing things like the text being edited in the buffer and the location of "point" in the buffer. The required per-view variables include: @begin[description] windows_refresher@\(Which should actually be called the "views_refresher") defines a routine to be the refresh algorithm for whatever data structure this view looks into. WindowsBufferName@\Is the name (an ID) of the buffer that the view looks into. @end[description] Views into text buffers include additional information such as a virtual screen to display the text in, and "cache" information to make refreshing faster. The choice of whether variables should be per-buffer or per-view is sometimes unclear. For example, it would seem to make better sense to have "point" be part of a view, rather than a buffer. This would allow the user to have two windows looking into different parts of the same buffer. However, it would also require the selection of a window for the many functions that insert strings into the buffer, delete strings from the buffer, etc., since these routines all work around the current "point". Somehow it seems unnatural to require the selection of a @i[view] for these @i[buffer] operations. The current decision is to make point a per-buffer variable. Further details on buffers and views for different modes are given in section @ref[creating-modes]. A list of all the buffers in EMODE is stored in the variable "BufferNames" as a list of @w[(name . environment)] pairs . These pairs are created with the routine "CreateBuffer". A list of "active" views in EMODE is stored in the variable "WindowList". This is simply a list of "environments" (association lists as described above). Unlike buffers, views are not referred to by name. Instead, specific views can be referred to by storing their environment in a variable (such as "BreakWindow"). @section[Modes and Key bindings in EMODE] @label[key-modes] There are two aspects to "modes" in EMODE. One is the choice of the data structure to be edited within a buffer. Until recently there has only been one kind of structure: "text". As discussed in section @ref[creating-modes] EMODE now provides tools for editing other, user defined, structures. @begin[Comment] Is this DISTINCTION between key bindings and the binding of other variables really VALID? @end[Comment] The other aspect of "modes", discussed in this section, is the binding of "handler" routines to terminal keys (or sequences of keys for multi-key commands). A simple version of this would associate a table of handlers (indexed by character code) with each buffer (or view). The method actually used is more complicated due to a desire to divide keyboard bindings into groups that can be combined in different ways. For example, we might have a text mode and an Rlisp mode, and an optional Word Abbreviation Mode that could be combined with either of them to cause automatic expansion of abbreviations as they are typed. Implementing optional keyboard bindings that can @i[removed] as well as @i[added] is difficult. Consider the situation with an optional "Abbreviation Mode" and an optional "Auto Fill Mode". Turning on either mode redefines the space character to act differently. In each case, the new definition for space would be something like "do some fancy stuff for this submode, and then do whatever space used to do". Imagine the difficulties involved in turning on "Abbreviation Mode" and then "Auto Fill Mode" and then turning off "Abbreviation Mode". EMODE's solution to the problem is based on the method suggested in @cite[FINSETH]. A @i[single], @i[global] "dispatch vector" is used, but is rebuilt when switching between buffers. The mode for each buffer is stored as a list of expressions to be evaluated. Evaluating each expression enters the bindings for an associated group of keys into the vector. Incremental modes can be added or deleted by adding or deleting expressions from the list. Although changing modes is fairly time consuming (more than a few microseconds), we assume that this is rare enough that the overhead is acceptable. NOTE that simply changing an entry in the dispatch vector will not work--since any switching between buffers will cause the entry to be permanently lost. The dispatch "vector" is actually implemented as a combination of a true PSL vector "MainDispatch", indexed by character code, and an association list "PrefixAssociationLists" used to implement two character commands. Currently the only two character commands start with the "prefix character" C-X, although the mechanism is more general. Prefix characters are "declared" by calling the routine "define_prefix_character" (refer to code for details). Bindings for prefix-character commands are stored in PrefixAssociationLists as an association list of association lists. The top level of the list is "indexed" by the prefix character, the next level contains @w[(character . handler)] pairs indexed by the character following the prefix character. The list of expressions for building the dispatch vector is called the "mode list", and is stored in the per-buffer variable "ModeEstablishExpressions". See the following section for more on how ModeEstablishExpressions is used in the declaration of a mode. The procedure "EstablishCurrentMode" evaluates these expressions in reverse order (the last expression in the list is evaluated first) to establish the keyboard dispatch vector used for editing the current buffer. Reverse order is used so that the @i[last] expression added to the @i[front] of the list will be evaluated last. EstablishCurrentMode must be called after changing the mode list for the current buffer and when switching to a different buffer @i[for editing from the keyboard]. The routine SelectBuffer switches to a buffer without "establishing" the buffer's mode. This saves the cost of setting up the dispatch vector when it isn't needed (which is the case for most "internal operations" on buffers). The expressions in ModeEstablishExpressions can execute @i[any] code desired. This generality is rarely needed, the usual action is to call the routine SetKeys with a list of @w[(character . handler)] pairs. For example, the mode list for text mode is defined by this Lisp code: @begin[example] (setf FundamentalTextMode '((SetKeys TextDispatchList) (SetKeys BasicDispatchList) (NormalSelfInserts))) @end[example] The RLISP mode is built "on top of" FundamentalTextMode as follows: @begin[example] (setf RlispMode (cons '(SetKeys RlispDispatchList) FundamentalTextMode)) @end[example] This section taken from the code that builds BasicDispatchList shows what a "key list" for the SetKeys routine should look like: @begin[example] (setf BasicDispatchList (list (cons (char ESC) 'EscapeAsMeta) (cons (char (cntrl U)) '$Iterate) (cons (char (cntrl Z)) 'DoControlMeta) % "C-X O" switches to "next window" (or "other % window" if in "two window mode"). (cons (CharSequence (cntrl X) O) 'next_window) (cons (CharSequence (cntrl X) (cntrl F)) 'find_file) . . . @end[example] Note that the pairs in a key list can specify character sequences like "@w[(cntrl X) O]" as well as single characters. At runtime, after they're created, key lists can be most easily modified by calling the routine AddToKeyList. For example @begin[example] (AddToKeyList 'RlispDispatchList (char (meta (cntrl Z))) 'DeleteComment) @end[example] could be executed to add a new, "delete comment" handler to RLISP mode. The routine SetTextKey is equivalent to adding to the key list TextDispatchList (see code). For example @begin[example] (SetTextKey (char (meta !$)) 'CheckSpelling) @end[example] could be executed to add a new "spelling checker" command to text mode (and other modes such as RLISP mode that incorporate text mode). SetTextKey seems to correspond most closely to EMACS's "Set Key" command. The routine "SetLispKey" is also defined for adding bindings to "Lisp mode". (There is no "SetRlispKey" routine in EMODE, although it would be easy to define for yourself if desired.) @section[Creating New Modes] @label[creating-modes] To define a new mode you must provide a "buffer creator" routine that returns a "buffer environment" with the required per-buffer variables along with any other state information needed for the type of data being edited. You need to "declare" the mode by calling the routine "declare_data_mode". It's also possible to associate the mode with a file extension by calling the routine "declare_file_mode". For example, the current EMODE declares the modes, "text" and "rlisp", as follows: @begin[example] (declare_data_mode "text" 'create_text_buffer) (declare_data_mode "rlisp" 'create_rlisp_buffer) (declare_file_mode "txt" 'create_text_buffer) (declare_file_mode "red" 'create_rlisp_buffer) @end[example] The second argument to both routines is the "buffer creator" routine for that mode. The first argument to declare_data_mode is a "name" for the mode. The first argument to declare_file_mode is a file extension associated with that mode. The conventions for "buffer environments" are that they always include certain @w[(name . value)] pairs--i.e. that they always include certain "per-buffer" variables. These variables are: @begin[description] ModeEstablishExpressions@\A list of expressions to evaluate for establishing the keyboard bindings for the buffer's mode. buffers_file@\The name (a string) of a file associated with the buffer, or NIL if no file is associated with the buffer. buffers_file_reader@\A routine to APPLY to one argument--a PSL io-channel. The routine should read the channel into the current buffer. buffers_file_writer@\A routine to APPLY to an io-channel. The routine writes the current buffer out to that channel. buffers_view_creator@\A routine to create a "view" (or "window") looking into the buffer. This is described in more detail below. @end[description] For example, the buffer creator for "text mode" is: @begin[example] (de create_text_buffer () (cons (cons 'ModeEstablishExpressions FundamentalTextMode) (create_raw_text_buffer))) @end[example] Most of the work is done by create_raw_text_buffer, which does everything but determine the keyboard bindings for the buffer. Here's the code with comments removed: @begin[example] (de create_raw_text_buffer () (list (cons 'buffers_view_creator 'create_text_view) (cons 'buffers_file_reader 'read_channel_into_text_buffer) (cons 'buffers_file_writer 'write_text_buffer_to_channel) (cons 'buffers_file NIL) (cons 'CurrentBufferText (MkVect 0)) (cons 'CurrentBufferSize 1) (cons 'CurrentLine NIL) (cons 'CurrentLineIndex 0) (cons 'point 0) (cons 'MarkLineIndex 0) (cons 'MarkPoint 0) )) @end[example] Other modes based on text can be similarly defined by consing an appropriate binding for ModeEstablishExpressions to the environment returned by create_raw_text_buffer. Of course we need some way of "viewing" buffers once they've been created. The per-buffer variable "buffers_view_creator" is responsible for creating a view into a buffer. The "view creator" is typically invoked by the routine "select_or_create_buffer". The required per-view variables are: @begin[description] @begin[group] windows_refresher@\Which should actually be called the "views_refresher", is a routine to APPLY to no arguments. This routine is the refresh algorithm for whatever data structure this view looks into. @end[group] @begin[group] WindowsBufferName@\Is the name (an ID) of the buffer that the view looks into. @end[group] @begin[group] views_cleanup_routine@\A routine that's called when a view is being deleted from the screen. Different views may require different kinds of cleaning up at this point. For example, they should "deselect" any "virtual screens" that make up the view. @end[group] @end[description] The view creator for text structures is "create_text_view". This routine typically modifies and returns the current view (which is almost certainly also looking into text in the current system) so that the current view looks into the new text buffer. Most of the real work of creating text views is done by the routine "FramedWindowDescriptor", which is typically invoked by the routines "OneWindow" and "TwoRFACEWindows". (So, although select_or_create_buffer is one way of creating views into a buffer, there's quite a bit of freedom in using other methods for creating views.) @section[Manipulating Text Buffers] The text in "text buffers" is stored as a vector of strings in the per-buffer variable "CurrentBufferText"--with the exception of a "current line" (stored in the per-buffer variable "CurrentLine"), which is a linked list of character codes. The CurrentLine is the line indexed by "CurrentLineIndex". Refer to the routine create_text_buffer for details of the contents of a text buffer. It's an easy mistake to modify CurrentLine but to forget to update the CurrentBufferText when moving to a new line. For this reason, and because the representation used for text may change in the future, you should use the utilities provided (mostly) in PES:EMODE1.RED to manipulate text. The procedure "GetLine(x)" can be used to get line x as the current line. The procedure "PutLine()" is used to store the current line back into CurrentBufferText. The procedure "SelectLine(x)" first "puts away" the current line, and then "gets" line x. It would seem natural to move forward a line in the text by doing something like @begin[example] SelectLine(CurrentLineIndex + 1); @end[example] but you should resist the temptation. For one thing, SelectLine makes little attempt to check that you stay within the limits of the buffer. Furthermore, future representations of text may not use integers to index lines. For example, some future version may use a doubly linked list of "line structures" instead of a vector of strings. So, you should use the routines "NextIndex" and "PreviousIndex" to calculate new "indices" into text, and you should also check to make sure that CurrentLineIndex is within the bounds of the buffer. You can probably just use the routines "!$ForwardLine" and "!$BackwardLine", (or "!$ForwardCharacter" and "!$BackwardCharacter"). You should also read some of the code in EMODE1.RED before attempting your own modifications. (Much of the code is rather ugly, but it does seem to work!) @section[Evaluating Expressions in EMODE Buffers] The "M-E" command for evaluating an expression in a buffer (of the appropriate mode) depends on I/O channels that read from and write to EMODE buffers. This is implemented in a fairly straightforward manner, using the general I/O hooks provided by PSL. (See the Input/Output chapter of the PSL Manual for further details.) The code for EMODE buffer I/O resides in the file RFACE.RED. The tricky part of implementing M-E is making it fit with the READ/EVAL/PRINT loop that Lisp and other front ends use. The most obvious scheme would be to have EMODE invoke one "READ/EVAL/PRINT" for each M-E typed. However, this doesn't work well when a break loop, or a user's program, unexpectedly prompts for input. Instead, the top level read functions in PSL call the "hook" function, MakeInputAvailable(), which allows the user to edit a buffer before the reader actually takes characters from the current standard input channel. Examples of top level read functions are READ (for Lisp), and XREAD (for RLISP). If you define your own read function, for example--to use with the general TopLoop mechanism, it should also call MakeInputAvailable before trying to actually read anything. When EMODE dispatches on M-E, it RETURNS to the routine that called it (e.g. READ), which then reads from the selected channel (which gets characters from an EMODE buffer). After evaluating the expression, the program then PRINTs to an output channel which inserts into another EMODE buffer. EMODE is then called again by the read routine (indirectly, via MakeInputAvailable). The fact that EMODE @i[returns to the reader] means that different buffers cannot use different readers. This can be a bit confusing when editing several buffers with different kinds of code. Simply switching to a buffer with Lisp code does not cause the system to return to READ instead of XREAD. Implementing this would require some sort of coroutine or process mechanism--neither of which are currently provided in PSL. (However, it may be possible to provide an acceptable approximation by having M-E normally invoke a READ/EVAL/PRINT operation, while preserving the MakeInputAvailable hook for exceptional situations.) @section[Customizing EMODE for New Terminals] @label[terminal-drivers] The files PE:AAA.SL, PE:DM1520.SL, PE:HP2648A.SL, PE:TELERAY.SL, PE:VT52.SL, and PE:VT100.SL define the different terminal drivers currently available. Terminal drivers define some values and functions used to emit the appropriate character strings to position the cursor, erase the screen and clear to end of line. To define a new terminal, use one of the files as a guide. A listing of TELERAY.SL follows: @begin[verbatim] % % TELERAY.SL - EMODE support for Teleray terminals % % Author: William F. Galway % Symbolic Computation Group % Computer Science Dept. % University of Utah % Date: 27 June 1982 % Copyright (c) 1982 University of Utah % % Screen starts at (0,0), and other corner is offset by (79,23) % (total dimensions are 80 wide by 24 down). (setf ScreenBase (Coords 0 0)) (setf ScreenDelta (Coords 79 23)) % Parity mask is used to clear "parity bit" for those terminals % that don't have a meta key. It should be 8#177 in that case. % Should be 8#377 for terminals with a meta key. (setf parity_mask 8#377) (DE EraseScreen () (progn (PBOUT (Char ESC)) (PBOUT (Char (lower J))))) (DE Ding () (PBOUT (Char Bell))) % Clear to end of line from current position (inclusive). (DE TerminalClearEol () (progn (PBOUT (Char ESC)) (PBOUT (Char K)))) % Move physical cursor to Column,Row (DE SetTerminalCursor (ColLoc RowLoc) (progn (PBOUT (char ESC)) (PBOUT (char Y)) (PBOUT (plus (char BLANK) RowLoc)) (PBOUT (plus (char BLANK) ColLoc)))) @end[verbatim] @Comment{Newpage???} @newpage[] @Comment{Section???} @section[Bibliography] @Bibliography[] @newpage[] @appendix[Default Keyboard Bindings for EMODE] @label[key-bindings] @include[keybindings.mss] @newpage[] @appendix[Some Important Fluid Variables] Here is an incomplete list of the fluid ("global") variables in EMODE. @begin[description] @begin[group] *outwindow@\A flag for PSL's ON/OFF mechanism. When T, means that the "output" (or OUT_WINDOW) window should be "popped up" when output occurs. @end[group] @begin[group] *EMODE@\T when EMODE is running. (Not quite the same as "runflag" described below. For example, runflag will be set NIL to cause EMODE to leave a "recursive edit", but *EMODE stays T.) @end[group] @begin[group] *RAWIO@\T when "raw I/O" is in effect. @end[group] @begin[group] BasicDispatchList@\The "key list" for "basic" operations. @end[group] @begin[group] BreakWindow@\The view for the "popup" break window. @end[group] @begin[group] BufferNames@\An association list of the @w[(name . buffer-environment)] pairs for all the buffers. @end[group] @begin[group] CurrentBufferName@\The name of the currently selected buffer. @end[group] @begin[group] CurrentBufferSize@\A per-buffer variable for text buffers, gives number of lines actually within buffer. @end[group] @begin[group] CurrentBufferText@\A per-buffer variable for text buffers. A vector of lines making up the buffer. @end[group] @begin[group] CurrentLine@\A per-buffer variable for text buffers. The contents (text) of current line--as a linked list of character codes. (Takes precedence over whatever is contained in the text vector.) @end[group] @begin[group] CurrentLineIndex@\A per-buffer variable for text buffers. Index of the "current line" within buffer. @end[group] @begin[group] CurrentVirtualScreen@\Per-view variable for text windows (views), holds the virtual screen used by the view. @end[group] @begin[group] CurrentWindowDelta@\Per-view variable for text windows, gives window dimensions as @w[(delta x . delta y)]. @end[group] @begin[group] CurrentWindowDescriptor@\The currently selected window environment. @end[group] @begin[group] declared_data_modes@\List of @w[(mode-name . buffer-creator)] pairs for all the declared modes. @end[group] @begin[group] declared_file_extensions@\List of @w[(file-extension . buffer-creator)] pairs for all modes with declared file extensions. @end[group] @begin[group] EmodeBufferChannel@\Channel used for EMODE I/O. Perhaps this should be expanded to allow different channels for different purposes (break loops, error messages, etc.) (Or, perhaps the whole model needs more thought! ) @end[group] @begin[group] FirstCall@\NIL means re-entering EMODE, T means first time. @end[group] @begin[group] FundamentalTextMode@\Mode list (list of expressions) for establishing "fundamental" text mode. @end[group] @begin[group] kill_buffer_ring@\Vector of vectors of strings--holds recently deleted text. @end[group] @begin[group] kill_opers@\list of (names of) handler routines that kill text. NEEDS MORE DOCUMENTATION! @end[group] @begin[group] kill_ring_index@\Pointer to the most recent "kill buffer". @end[group] @begin[group] last_buffername@\Name (a string) of the last buffer visited. @end[group] @begin[group] last_operation@\The "last" routine dispatched to (before the "current operation"). @end[group] @begin[group] last_search_string@\The last string searched for by a search command--used as default for next search command. @end[group] @begin[group] last_yank_point@\Vector of [buffer lineindex point], giving location where last "yank" occured. @end[group] @begin[group] LispDispatchList@\The "key list" for Lisp mode. @end[group] @begin[group] LispMode@\The mode list for Lisp mode. @end[group] @begin[group] MainDispatch@\Dispatch table (vector), an entry for each key. @end[group] @begin[group] minor_window_list@\List of windows to be ignored by the "next_window" routine. @end[group] @begin[group] ModeEstablishExpressions@\List of expressions to be evaluated. Each expression is expected to modify (add to?) the dispatch table. @end[group] @begin[group] OldErrOut@\The error output channel in effect before EMODE was started. @end[group] @begin[group] OldStdIn@\The standard input channel in effect before EMODE was started. @end[group] @begin[group] OldStdOut@\The standard output channel in effect before EMODE was started. @end[group] @begin[group] point@\A per-buffer variable for text buffers. Number of chars to the left of point within CurrentLine. @end[group] @begin[group] PrefixAssociationLists@\Additional dispatch information for prefixed characters. @end[group] @begin[group] PrefixCharacterList@\A list of the declared prefix characters. @end[group] @begin[group] pushed_back_characters@\A list of characters pushed back for EMODE's command reader. This may be used when a command isn't recognized by one dispatcher, so it can push the characters back and pass control to another dispatcher. @end[group] @begin[group] reading_from_output@\Kludge flag, T when input buffer is OUT_WINDOW buffer (for M-E). @end[group] @begin[group] RlispDispatchList@\The "key list" for RLISP mode. @end[group] @begin[group] RlispMode@\The mode list for RLISP mode. @end[group] @begin[group] runflag@\EMODE continues its READ/DISPATCH/REDISPLAY until this flag is NIL. @end[group] @begin[group] SelfInsertCharacter@\Character being dispatched upon. (Usually the last character typed.) @end[group] @begin[group] ShiftDisplayColumn@\Amount to shift things to the left by before (re)displaying lines in a text view. @end[group] @begin[group] TextDispatchList@\The "key list" for fundamental text mode. @end[group] @begin[group] Two_window_midpoint@\Gives location (roughly) of dividing line for two window mode. @end[group] @begin[group] WindowList@\List of active windows (views). @end[group] @begin[group] WindowsBufferName@\Required per-view variable giving the name of the buffer being viewed. @end[group] @begin[group] Windows_Refresher@\Required per-view variable giving the refresh algorithm to be APPLYed for this view. @end[group] @begin[group] Window_Image@\Per-view variable for text views, holding information for speeding up refresh. @end[group] @end[description]