Enhancing the VAXTPU EDT Keypad Emulator by Michael L. Penix and Richard D. Piccard Kalamazoo College Kalamazoo, Michigan December 1, 1985 Enhancing the VAXTPU EDT Keypad Emulator ________ Abstract We report the development of customizations to the VAXTPU EDT Keypad emulator that closely parallel those reported in the past for EDT itself: word processing enhancements, multi-buffer operations, etc. In addition, the reported customizations include dual-window operation and overstrike mode text entry. We discuss the choices made in coding, some basic techniques for debugging with TPU, and several portions of the code itself. This article, the code, and a companion article describing the customized enviromonment are being submitted for inclusion in the VAX SIG Symposium tape for the December, 1985 DECUS Symposium. ____________ Introduction VMS Version 4.2 includes the new VAXTPU Text Processing Utility, together with two user interfaces, EVE (the Extensible VAX Editor) and the EDT Keypad Emulator. For those users who have been working with "plain-vanilla" EDT, the latter is likely to prove quite adequate, requires very little re-learning, and will therefore be chosen on the basis of its far superior performance: in one benchmark it finished in half the elapsed time while consuming CPU time at one quarter the rate that "real EDT" consumed it. For those of us, however, who have customized EDT with extensive EDTINI.EDT files, and especially for managers who have provided standard EDTINI.EDT files to new users, the choice is not immediately obvious. Our experience at Kalamazoo College should prove quite encouraging to others, since we made the transition in about a week, for over 95% of our usage, and we had extensive EDTINI.EDT files in place for all of our users. This paper discusses the choices made in coding the enhancement files, the techniques that proved effective for us in making the transition, and several portions of the TPU code itself. Listing 1 is the EDTINI.EDT file we started with. As can be seen, we have a variety of commands, one making EDT even more pleasant to work with for programmers by selecting alternate delimiters for "WORD" motion and deletion commands, and many enhancing EDT for word processing (defining keys for sentence and paragraph motions, for example). This file had evolved from one ___ ___ ____________ presented several years ago in The DEC Professional. _______ Choices Choices Page 2 TPU provides two layers of enhancement, "section" and "command" files. Section files are pre-compiled, and therefore should be chosen for the stable bulk of the enhancements. The DIGITAL-supplied section file for the EDT Keypad Emulator is SYS$LIBRARY:EDTSECINI. (The filename extension is .TPU for the source code and .GBL for the binary code; the default when used by TPU as a section file is .GBL and when used as a command file is .TPU.) TPU looks for a section file in SYS$LIBRARY: named TPUSECINI.GBL unless the command line includes a /NOSECTION or a /SECTION=filename option. Command files are read-in and compiled at edit session startup, and should therefore be kept short and used for the volatile portion of the enhancements. TPU looks for a command file named TPUINI.TPU in the current default directory, unless the command line includes a /COMMAND=filename option, or a /NOCOMMAND option. (If you specify /NOSECTION/NOCOMMAND, then the only action possible is to exit by CTRL/Y.) A simple method to put a section file to work is to define a DCL symbol by a line such as the following at some point in a login command file: $ ED*IT :== EDIT/TPU/SECTION=filename Since no /COMMAND option is specified, it will search the current default directory, as mentioned above, for TPUINI.TPU. Although our ultimate goal was of course the creation of a custom section file, we worked for quite a while with a (rapidly growing!) command file. The time to turn the nascent section file into a real one is when the start-up delay becomes irritating, or when you are far enough along that you want others to start trying to use the product of your efforts. We will discuss below the modifications needed to change a working command file into a working section file. The most reasonable items to leave in the command file are the two items most likely to be modified by individuals: the word wrap margin and the first tab size. These should be initialized in the section file, also, so that new converts need not have a copy of TPUINI.TPU in their directories. In order to assist ambitious users by providing examples, a few stable routines and key definitions should also be placed in the version of TPUINI.TPU that is made available for users to copy. __________ Techniques Probably the key technique to rapid implementation is the prompt switching over to the partly customized editor as soon as possible. As you work with it to implement further changes or to refine those already in place, the lack of a customization you are used to in real EDT will act as a goad to keep working. Techniques Page 3 It will also ensure that you implement the most important (i.e., heavily used) features first. ______ _____ We have followed the method discribed in the VAXTPU Users _____ Guide for creating new section files layered on top of existing ones. In particular, our file "KAZSECINI" is layered on their "EDTSECINI". During the early development phase, then, we dealt with our code as a command file and used theirs as the section file. Later on, the "old" portion of our code, together with their code, served as section file and the "new" portion of our code served as command file or was the file being edited. The advantages of layering deserve some discussion: we can use their code when it serves our purpose and replace it if we want to. The design of TPU as a programming language includes named procedures as well as local and global variables, and DIGITAL's code in EDTSECINI.TPU is careful to start the names of all procedures and global variables with "edt$", so by observing reasonable naming conventions we can keep track of which portions of our code are vulnerable to changes in theirs. We chose to name all of our procedures and global variables with names starting "KAZ$". During the first development phase, when all customizations are in the form of a command file, the structure is as shown in Fig. 1: procedure declarations and code, followed by key definitions to implement those procedures, and finally assignment statements initializing the global variables. During this phase the file is used as the command file, either by naming it TPUINI.TPU or by defining a symbol for DCL that includes specific designation of the command file. In the later phase, when the initial customizations are in the form of a section file, the structure is as shown in Fig. 2: first, a procedure named TPU$LOCAL_INIT (see below), second, all the other procedure declarations and code, third, a procedure (in our case named KAZ$DEFINE_KEYS) that, as you might suspect from its name, includes all of the key definitions, and finally, the commands to SAVE the current context in a named file and to QUIT. The filename given should be different, either in directory or extension or both, from the current production section file. The DIGITAL file EDTSECINI ends with a call to TPU$LOCAL_INIT, as well as including an empty definition of that procedure. By including a procedure with that name in our file, we replace their empty one. This procedure includes initialization of our global variables, re-initialization of some of the EDT$ global variables (changing the default first tab size and word wrapping margin, for example), and a call to our procedure KAZ$DEFINE_KEYS. Although the following sequence may seem cumbersome in writing, it works well in practice. The main problem is the obscurity of the error messages that TPU provides, a traditional problem with the first release of any compiler which is Techniques Page 6 compounded here by the limited size of the message buffer and the consequent default terseness of those messages. It is therefore ___________ essential to maintain efficiency by incremental enhancements, checking each new procedure and key definition as you go along. As few changes as possible should be made at one time! For the sake of discussion, assume that these logical names have been defined: WORK: for the device and directory containing the evolving section file, and GOOD: for the device and directory containing the section file and standard command file most recently released for public use. Several DCL symbols are useful, as well; the following lines were included in the author's LOGIN.COM: $ NEWTPU :== EDIT/TPU/SECTION=SYS$LIBRARY:EDTSECINI- /COMMAND=WORK:KAZSECINI $! $ NED :== EDIT/TPU/SECTION=SYS$LOGIN:KAZSECINI.NEW- /COMMAND=GOOD:TPUINI $! $ WED :== EDIT/TPU/SECTION=SYS$LOGIN:KAZSECINI.GOOD- /COMMAND=GOOD:TPUINI These definitions were used with a SAVE command that specified the new binary file as SYS$LOGIN:KAZSECINI.NEW. Thus, each time we thought the code was correct, we gave the command NEWTPU and watched to see if any error messages were displayed. If all seemed well, then we used the command NED to use the new editor and tried out the modified or newly introduced routines. If all was well, we RENAMED the .NEW file to .GOOD, thereby setting aside a working enhancement. At the same time a copy of the source code was set aside with a suitably distinctive name in WORK:. From then on routine editing was done with the command WED, using those working enhancements. As each round of enhancement is completed, WORK:KAZSECINI.TPU is copied to GOOD:* and SYSLOGIN:KAZSECINI.GOOD is copied to GOOD:*.NEW. Then the protection of GOOD:KAZSECINI.NEW is set correctly and finally GOOD:KAZSECINI.NEW is RENAMED to *.GBL, thereby replacing the one in use. If there is a mistake in the code (even as simple as a missing ";" termination for a statement), the only message usually visible is "compilation aborted at line 1." In order to debug at all efficiently, we took the advice offered us by the Customer Support Center and used the following command sequence from the "TPU Command: " prompt elicited by + : SET (INFORMATIONAL,ON) COMPILE(CURRENT_BUFFER) and then, if errors are reported, type CTRL/Z and at the "*" prompt, "= message", to inspect the message buffer (our code includes a less clumsy way to switch buffers, + B). If the Techniques Page 7 buffer being compiled includes routines with names the same as any already in place (from DIGITAL's EDTSECINI, or the local customizations, or previous compilations during the same edit session), an informational message that the old procedure was replaced will be generated. Since the message buffer is limited in size, the unit being compiled should ordinarily be limited to as few procedures as possible. For enhancements, our routine is to write the new procedure and the key-binding command in a separate file, compiling and re-compiling as we go. When it seems logically complete and generates no compiler errors, we cut it out of that buffer and paste it into the growing section in the main buffer, placing it at the end of the regular procedure declarations and finally cutting and pasting the key-binding statement into the procedure KAZ$DEFINE_KEYS. In the case of a flawed procedure that we seek to correct, once the full file is in place, we create a separate file with the suspect procedure as its only contents. Then we edit that file. After modifying it we give the TPU command "COMPILE(MAIN_BUFFER)", and then (if no compiler errors were generated) select an alternate buffer and include text from a file known to elicit the misbehavior whose correction we seek. Because the revised procedure has the standard name, the compilation of the main buffer re-defined the procedure that will be executed upon typing of the standard key sequence bound to that procedure. ___ ____ The Code Search Patterns A search pattern may be as simple as the string being sought itself, or it may include TPU builtins and operators to ________ describe that string. In search patterns the symbol '&' is the CONCATENATION operator. Thus the pattern defined by 'a' & 'b' & 'c' is identical to that defined by 'abc'. The symbol '|' is the OR or ALTERNATION operator. Thus the pattern defined by 'a' | 'A' will be matched by any a, regardless of the case. These two symbols may be used to form more complex search patterns. The pattern Code Page 8 ('a'|'A') & ('b'|'B') will match any sequence 'ab' regardless of the case of either letter. The VAXTPU Reference Manual, section 2.8.4, "Modes of ___________ ____ Searching", discusses incremental and seek searching. The latter is the default because it is more efficient if it will produce the desired result. They differ on the handling of alternate parts of the pattern. This will matter only if there may be more than one location in the buffer that matches the pattern. Seek searching essentially "multiplies out" the pattern, like the distributive law of algebra, producing a set of pure concatenation possibilities. A search is then performed, through to the end of the buffer for each of those possibilities in turn. The overall search terminates successfully upon encountering of the first instance of any of these possibilities. Thus, an earlier instance of a later possibility will be skipped over by a seek mode search if there is any instance of the earlier possibility. Incremental mode searching, on the other hand, will terminate with a successful match at the first instance of a ___ string matching any of the possiblities. Any alternation that is enclosed in parentheses and preceded by a concatenation will be ___________ searched using the incremental mode. This is why we have on occasion started our patterns with the string ''&. Bug Work-arounds With help from William White of the Colorado Customer Support Center, we have implemented a work-around for a bug in the FILL command. The problem manifests itself whenever the beginning of ____ the SELECTED region is at the start of or within a word that ends beyond the margin for wrapping. The result of executing the FILL command under these circumstances is that a line break is inserted at the start of the select region, whether it is beyond the margin for wrapping or not, and whether it is in the middle of a word or not! The concept of the work-around is simply to ensure that the beginning of the SELECT region is moved to the start of the line before executing the FILL command. The work-around is implemented by copying DIGITAL's procedure EDT$PRESERVE_BLANKS(flag), creating a procedure named KAZ$FIND_BEG_OF_LINE(b_mark), and then modifying EDT$PRESERVE_BLANKS, by adding a call to KAZ$FIND_BEG_OF_LINE, at the third line of the main sequence of the code, just prior to the call to EDT$SKIP_LEADING_SPACES(b_mark). KAZ$FIND_BEG_OF_LINE moves b_mark from its original Code Page 9 location to the beginning of the line that it had been in. Replacement Customizations The following procedures are among those that implemented equivalent functions to those that we already had in place through EDTINI.EDT. Word Delimiter Changes PROCEDURE KAZ$SWAP_DELIM The procedure KAZ$SWAP_DELIM allows the user to toggle back and forth between two sets of characters to be taken as the delimiters between words. The two sets are optimized for word processing and for programming. The word delimiters in word processing are a space, a tab, a form-feed, a line-feed, a carriage return, and a vertical tab. The word delimiters for programming include all the above as well as the characters "/<>[]{},.:*&!;+-=()\|'" which constitute most of the "algebraic punctuation" that is used in FORTRAN, DCL, and PASCAL programs. DIGITAL's code includes a global variable EDT$X_WORD that is the set of characters used to identify word breaks. We maintain a variable called KAZ$WORD_DELIM with the value of either 'text' or 'program' to indicate the current editing context: word processing or programming. Procedure KAZ$SWAP_DELIM uses an if-then-else statement to determine the current editing context from the variable KAZ$WORD_DELIM and then toggles the two variables, EDT$X_WORD and KAZ$WORD_DELIM. For instance, if upon entering this procedure KAZ$WORD_DELIM has the value 'text', then the old context is word processing. The programming delimiters are assigned to EDT$X_WORD and the value 'program' is assigned to KAZ$WORD_DELIM, indicating the new editing context. Word Processing Conveniences DEFINE_KEY ('READ_FILE (''STANDARD.RNO'')', KEY_NAME('R',SHIFT_KEY), "Insert a copy of the file STANDARD.RNO."); This defines + R to include a file named 'STANDARD.RNO' on the lines preceding the line containing the current position of the cursor. This filename results from the routine usage of Code Page 10 this command to include a user's standard RUNOFF initializations. Obviously, any sort of contents could be maintained in that file. This definition can be easily modified to include any frequently used file into the current editing buffer by substituting the appropriate name for STANDARD.RNO. Logical name assignments would provide yet another mechanism for generalizing the use of this command. PROCEDURE KAZ$GET_OUT This procedure eliminates the following command sequence: "CTRL/Z + EXIT + ". This command sequence does a normal exit out of TPU, saves the current buffer under the filename that was used at the starting of the editing session and deletes the journal file. We have chosen + CTRL/Z to cause the execution of this procedure. The first line, "write_file(main_buffer)", writes out the main buffer. The second line, "set(no_write,main_buffer,on)", indicates that upon the termination of the editing session the main_buffer is not to be written out, since we just performed that function in the previous line. The last line, "exit", simply tells TPU that the editing session is to be terminated and the journal file is to be deleted. The TPU command "quit" would have worked equally well here because we took care of writing out the buffer ourselves. KAZ$FILL_PARAG This permits an entire paragraph to be re-filled at once. We take real EDT's default definition of a paragraph: a block of text bounded by a line containing no characters, or by the beginning or end of the buffer. The right margin used is the same as the current EDT "wrap" emulation. The cursor is returned to the original location in the buffer. The entire operation is performed without ongoing screen updating. Our procedure to move to the beginning of the current paragraph is used to establish the start of the range to be filled. Then a search is conducted forwards for the end of the paragraph. If it fails, the paragraph ends at the end of the buffer. If it succeeds, we move back one character to avoid swallowing the blank line between paragraphs. The word separators used for the fill operation are the current word delimiters, EDT$X_WORD. KAZ$END_SENT Code Page 11 First a short discussion on the search command is warranted. Search uses three parameters: the first parameter is the search pattern. The second parameter is the direction of the search. The third parameter is a qualifier on the search. In this proceedure the direction is always FORWARD and the qualifier is always EXACT. The search command, as used in this procedure, returns the range of the first exact pattern match that is forward of the cursor position. The returned range will be 0 if the pattern was not found. In KAZ$END_SENT, the search command is used three times. The first search looks for the end-of-sentence delimiters that are defined by the search pattern KAZ$SENT_DELIM. One character string that will match that pattern is ". ", a period followed by a space character. The initialization of KAZ$END_SENT will be described later. In this procedure, if the pattern is not found, i.e., the search range returned to end_sent_range was 0, then the procedure is exited. If the pattern is found, then the cursor is positioned to the beginning of the delimiter. The second search looks for either a space character or the end of the line. The cursor is then positioned to the beginning of that pattern if it was found (thereby skipping over the printing characters of the sentence delimiter) or the procedure is exited. The third search looks for either line_begin or the first non-space character (thereby finding the beginning of the next sentence). If the next sentence is found, the cursor is positioned there, otherwise the procedure is exited. KAZ$SENT_DELIM is initialized in procedure TPU$LOCAL_INIT. The search pattern consists of the concatenation of the empty string (to force incremental searching) with two alternations: first, the punctuation, '.', '?', or '!', and second the conclusion which may include a "concealing" character such as ']', '}', ')', or '"', and terminates with a space or line_end. The second alternation is rather extended, since the various combinations of concealment and termination are all explicitly given. This implementation is significantly more versatile than true EDT's +SEN and -SEN commands, since those do not recognize any "concealed" sentences. New and Wonderful Options KAZ$WINDOWS This procedure permits selection of single- or double-window editing. After interactively specifying the number of windows, the user is asked for the name of the second buffer. If the buffer is new, the user is asked for the name of the file to edit and whether that file should be written to disk upon exit. The windows and buffers are established and mapped, and keys are suitably re-defined for moving between buffers. Global variables Code Page 12 are established containing the names of the first and second windows and buffers. In normal usage, before executing KAZ$WINDOWS, we define PF1 + M to execute a procedure that moves the cursor to the main buffer and map that buffer to the main window. That procedure was created by copying the support routine for the EDT line mode "= buffer" command from page 22 of EDTSECINI, and then specializing it to go for the main buffer. In normal usage, before executing KAZ$WINDOWS, we define PF1 + B to execute a procedure (even more closely modeled on the "= buffer" support from EDTSECINI) that prompts for the name of the buffer, moves the cursor to it, and maps it to the main window. Thus, those keys are routinely used to return to the main buffer or to shift to a named buffer. KAZ$WINDOWS restores these definitions whenever single-window editing is specified. When dual-window operation is to commence, PF 1 + M is defined to move to the first (upper) window, and PF1 + B is defined to move to the second (lower) window. We have defined PF1 + to jump back a screen, and PF1 + to jump forward a screen. This is implemented using a global variable, KAZ$WINDOWSIZE, whose value is initialized to 21 for single-window editing and is set to 10 for dual-window editing. The action of the screen jump command is thereby adjusted to avoid either overlap or skipped lines as one moves through a buffer. The sizes of the windows are adjusted appropriately to allow for status lines at the bottom of each window, clarifying the boundary between them. The scrolling limits are set so that a reasonable amount of context is presented before and after the cursor, while still permitting some range of motion without stimulating excessive output to the terminal. KAZ$OVERSTRIKE This procedure is used to swap between the overstrike and insert modes using one key definition rather than using a key definition for each mode. The procedure KAZ$OVERSTRIKE uses a global variable called KAZ$ENTRY_MODE to indicate the current editing context. This allows the defined key to take on a dual function depending on which 'state' the variable KAZ$ENTRY_MODE is in. For example: if KAZ$ENTRY_MODE is 'insert', the current editing context is insert mode and the procedure should switch into overstrike mode. In the procedure an if statement implements the above logic; if the mode is not 'insert' then the else clause is executed to switch to 'insert' mode. The commands SET(OVERSTRIKE, CURRENT_BUFFER); and SET(INSERT, CURRENT_BUFFER); Code Page 13 switch the current editing context of the current buffer to overstrike and insert respectively. After the editing context is set, then the variable KAZ$ENTRY_MODE is updated to 'overstrike' or 'insert', respectively. __________ Conclusion The VAX Text Processing Utility has certainly demonstrated its utility! It provides virtually the full functionality of the EDT Keypad environment, and can be readily enhanced both in ways that EDT can and in ways that EDT cannot. While providing this superb editing capability, it consumes significantly less CPU time than EDT itself to do the same job. Thus users see a more friendly system that responds faster, too. _______________ Acknowledgement We wish to express our thanks to the staff of the Customer Support Center in Colorado Springs, several of whom have provided quite useful suggestions. The file EDTSECINI.TPU provided with VMS 4.2 contained many useful examples. In fact, some portions of the code we used were simply copied from there and modified ______ _____ slightly. A variety of useful examples from the VAXTPU Users _____ Guide have also found their way into our code. The users of Kalamazoo College's Educational Computing VAX system tolerated being forced from EDT onto an early version of the customizations reported here, and have survived the various stages of refinement or brutalization it has gone through since, so to them we owe especial thanks. Enhancing VAXTPU EDT M.L. Penix and R.D. Piccard _____________________________________ | | | Procedure | | . | | . | | . | | Endprocedure | _____________________________________ _____________________________________ | | | Key-definitions | _____________________________________ _____________________________________ | | | Assignment statements | _____________________________________ Figure 1: TPU Command File Organization Enhancing VAXTPU EDT M.L. Penix and R.D. Piccard _____________________________________ | | | Procedure TPU$LOCAL_INIT | _____________________________________ _____________________________________ | | | other procedures | _____________________________________ _____________________________________ | | | Procedure KAZ$DEFINE_KEYS | _____________________________________ _____________________________________ | | | SAVE SYS$LOGIN:filename.NEW | | QUIT | _____________________________________ Figure 2: TPU Section File Organization