Customizing the Enhanced Editor
Written by Jörg Schwieder
If you talk with OS/2 developers, you will soon get the impression that most of them are desperately looking for a good programming editor even though OS/2 includes EPM, a very capable and flexible editor. One of the reasons for the bad reputation EPM seems to have may be its standard configuration which makes it resemble the system editor, OS/2's version of the Windows Notepad. Another reason may be that - though it is very flexible - it shares the problem of a lot of software that was 'born to use': that is it was written to be used not to be sold or that evolved from a line of more simple product improved over the time. Its user-interface clearly shows some of the author's preferences and remainders from former stages of development. For example, this means that EPM is almost completely incapable of using 'hard' tabs (which doesn't matter if you don't like them but if you're used to using such features it is quite a drawback).
So if you want to use EPM you may need to customize it. To do this you need the complete EPM toolkit available on various ftp sites such as cdrom.com. It includes an updated (and reconfigured) version of EPM along with a better documentation and a compiler for the E macro language (ETPM). This article will cover custom configuration of your editor using this toolkit and - through examples - provide you with some nice additional features such as file autoloading and positioning and keyboard undo.
The EPM Programming Model
There are two ways of programming EPM: using REXX and using the E macro language. Using REXX you may write EPM macros that don't need to be compiled and you don't have to learn the E macro language. This is quite powerful and lets you use most of EPM's features including custem menus, etc. and so it may be great for user macros but you cannot change the default configuration using REXX and since its interpreted and not compiled its quite slow so we will use the macro language here.
What is EPM?
EPM is not a "program" as such. The key element of the editor is the Enhanced Multi-Line Edit control (E-MLE) that is defined if the E-Toolkit (ETK) DLL's. It may also be used in custom programs (see the ETK documentation). Most of the basic functionality as well as macro language and REXX support are provided by the control. It can be used to edit multiple files in a "ring" which may be as large as system memory permits. EPM itself is just a small program that manages E-MLEs and the user interface (e.g. dialogs).
The Macro Language
Most of the features that make EPM were added using the E macro language and therefore may be rewritten or changed as well as used within other programs. For example, it would be possible to write applications using E-MLEs for their own editing purpose but incorporating the user's individual EPM setup. Given its capability to use the REXX language this makes for a great shot in the way of finding an individual standard user interface (a paradox within conventional application design).
Okay, let's get back to EPM. Macros that were written using the macro language (*.E) are compiled with the macro compiler ETPM and become ETPM modules (*.EX). An E-MLE has one main module (EPM.EX for EPM) which is executed upon creation and which contains startup and close-down code as well as basic functions and commands, menus, key-definitions etc. There may be additional modules that define external commands, they can be executed at runtime or linked into a running system meaning they're loaded permanently.
The macro language itself closely resembles REXX with some compiler specific differences especially regarding variable handling. See the EPM Technical Reference included in the complete EPM distribution.
Changing Your Configuration
The EPM main module is called EPM.E. (Well, in fact EPM.E currently does nothing else than including E.E which is the real main module but in the documentation the main module is said to be EPM.E so we call it that.) It includes the other EPM modules and is compiled to EPM.EX. Changes to the default configuration have to be recognized by EPM.E; usually this is done by changing a set of files that are included in EPM.E: MYCNF.E, MYKEYS.E, MYSTUFF.E, MYMAIN.E, MYSELECT.E and MYKEYSET.E.
MYCNF.E is a configuration file you should use to set configuration switches whereas the other ones are thought to include code. You may recompile your standard configuration by just recompiling EPM.E.
Changing The Default Configuration via MYCNF.E
EPM's standard configuration is stored in a file calles STDCNF.E. Every definition primitive is given a default value here. You should not change this file since it may be changed with the next release of EPM and you had to rewrite all your changes. Instead you should write a file called MYCNF.E which is included before STDCNF.E andso settings you specify here override settings in STDCNF.E.
Configuration constants are constants so you can set them using the const statement:
const FOO = 'VALUE'There are, well, quite a lot of configuration constants within EPM so we will cover only a subset which I think are the more important ones. See the EPM User's Manual for the complete set of constants. Along with the constants the default values are given:
EPM includes the file COLORS.E which defines a set of color values to be used within EPM macros. The available constants are:
These constants may be used to set the display color variables. Note that these constants may only be used if COLORS.E was included. This may not be the case for external commands that include MYCNF.E. Use the COMPILE IF statement to include them if the color constants are defined. Also note that the color variables are not constants and therefore should be define'd.
compile if defined(BLACK) define TEXTCOLOR = BLACK + WHITEB MARKCOLOR = BLUE + GREYB STATUSCOLOR = BLACK + WHITEB MESSAGECOLOR = LIGHT_RED + WHITEB DRAGCOLOR = YELLOW + MAGENTAB HIGHLIGHT_COLOR = compile endif
Here's an example MYCNF.E file.
const NLS_LANGUAGE = 'ENGLISH' AUTOSAVE_PATH = '\OS2\EPM\AUTO\' BACKUP_PATH = '\OS2\EPM\BACK\' TEMP_PATH = '\OS2\EPM\TEMP\' EPATH = 'EPMPATH' C_TABS = '4' E_TABS = '4' REXX_TABS = '4' P_TABS = '4' SYNTAX_INDENT = '4' USE_APPEND = 1 SETSTAY = '?' TRASH_TEMP_FILES = 1 WANT_ALL = 1 WANT_RETRIEVE = 1 WANT_KEYWORD_HELP = 1 WANT_LONGNAMES = 'SWITCH' WANT_STACK_CMDS = 'SWITCH' WANT_CUA_MARKING = 'SWITCH' WANT_STREAM_MODE = 'SWITCH' WANT_DYNAMIC_PROMPTS = 1 WANT_PROFILE = 'SWITCH' MY_DEFAULT_EDIT_OPTIONS = '/t' SUPPORT_TECHREF = 1 SUPPORT_USERS_GUIDE = 1 SUPPORT_USER_EXITS = 1 EXTRA_EX = 1 DEFAULT_PASTE = '' TOGGLE_ESCAPE = 1 TOGGLE_TAB = 1 DRAG_ALWAYS_MARKS = 1 MENU_LIMIT = 25 SMARTFILE = 1 compile if defined(BLACK) define HIGHLIGHT_COLOR = GREEN + GREENB DRAGCOLOR = LIGHT_GREY + DARK_GREYB compile endif
Adding your own key definitions via MYKEYS.E
The file MYKEYS.E should be used for your own key definitions and redefinitions. It is included into E.E after most of the definition stuff and directly before MYSTUFF.E. Currently it does not matter which of those you use to store your key DEFs but that may change.
Key definitions start with a DEF statement and give the key name with an additional S_ for Shift+key, A_ for Alt+key or C_ for Ctrl+key in front of it. Alphanumeric keys are addressed by their alphanumeric value while special keys are listed below:
Following the DEF statement you simply start writing your key function.
def a_s= 'saveall'This makes ALT+S execute the SAVEALL command.
You may, of course, also include other key definition files in MYKEYS.E, e.g. REVERSE.E and GLOBFIND.E from the EPM distribution demos:
include 'reverse.e' include 'globfind.e'
The following example MYKEYS.E defines an undo key for ALT+U. It uses EPM's undoaction command to take back changes one by one. So you don't have to use the UNDO-dialog if you just want to take back a few changes. The one thing making this a little bit complicated is the fact that the undo itself is regarded as a change and therefore is stored by undoaction. So if you did undo twice you would restore the state before the undo. However the intention of the undo key is to be able to take back more than one step. The solution is to store information about which steps in the undo-chain are UNDOs itself and to skip them.
/* This is an example MYKEYS.E file defining an undo key <C> 1993 Joerg Schwieder, Berlin, Germany */ -- define ALT-U def a_u= -- define an universal variable to store UNDOs taken universal undostates -- get the first and the last actions stored in the UNDO-tree undoaction 6, states -- parse the 2 values into sfirst and slast parse value states with sfirst slast -- copy our undo-states undoakt = undostates -- save the actual state to our new UNDO-state list newstate = slast -- last entry we could UNDO to slast = slast - 1 -- loop through the UNDO-tree while slast >= sfirst do -- look at our last undo parse value undoakt with thisone undoakt if (thisone < slast) or (not thisone) or (thisone < sfirst) then -- if its older than the last tree entry or if -- we never undid - leave, it's OK! leave endif -- add our last UNDO to our new list if thisone then newstate = newstate' 'thisone; endif -- if the actual tree entry is an old UNDO if thisone = slast then slast = slast - 1; endif -- look at the one before endwhile -- if a possible tree-entry was found, if slast >= sfirst then -- add it to the start of the new UNDO-list newstate = newstate' 'slast endif -- add the remaining old UNDO list to the new one while undoakt and (thisone >= sfirst) do -- but strip entries no longer in the UNDO tree newstate = newstate' 'thisone parse value undoakt with thisone undoakt endwhile -- set undostates to the new list undostates = newstate -- if an entry was fond if slast >= sfirst then -- UNDO undoaction 7, slast else -- else remove the actual entry from the list parse value undostates with . undostates endif /* This is the sample ALT-S definition for SAVEALL */ def a_s= 'saveall' /* Include REVERSE and GLOBFIND keys from the EPM distribution demos if present. */ tryinclude 'reverse.e' tryinclude 'globfind.e'Select this to go to the next section
Your own commands and procedures via MYSTUFF.E
This is the place to put your own commands and procedures. Usually you won't put them into the file directly but include other files. Don't be concerned if these files contain key definitions as well. EPM includes MYSTUFF.E directly after MYKEYS.E. The distinction is for organizational purposes. You might want to put everything in MYSTUFF.E, especially if you write large extensions part of which are key definitions and if you don't want to split them up into several files.
include 'saveall.e'This would put the SAVEALL command into the default configuration.
Commands are defined using the DEFC statement followed by the command's name, arguments are passed through arg(1) which returns a parameter string:
defc gocol .col = arg(1)This defines a GOCOL command that moves the cursor to the column passed as parameter.
The following sample MYSTUFF.E file uses another EPM feature - user exits. Several hooks are spread in the editor code which give user procedures notice of certain events, e.g. a file is being saved. User exits are procedures that are called by EPM if they are defined. We will use them to write an autostart feature that "reminds" of its closedown state. If EPM is closed with open files, these filenames are stored in EPM.INI; should EPM be started later without parameters, it will reload these files. Additionally the cursor position in each open file is stored in extended attributes upon shutdown; reloading this file later repositions the cursor right where it was.
This feature is quite useful when you are editing several large files. If you want to leave EPM but continue the next day just close down EPM and if you restart it the next day without parameters; you are right back where you were without having to load all your files and having to find your way through your code back to where you left.
There is just one drawback: Since defmain_exit has to be called from MAIN.E it has to be included before it and therefore has to be included into MYCNF.E. We include this section as a file called MAINEXIT.E into MYCNF.E.
include 'saveall.e' defc gocol .col = arg(1) /* For the autoload feature we maintain a list of the currently open files in a profile entry named STARTUP_FILES. The first one is the file actually edited to make shure it comes up on restart */ /* This one makes shure that the file that was on top on shutdown will reappear there on startup. Defselects are called whenever a file is selected */ defselect /* The application name and INI-handle are stored in these global variables */ universal appname, app_hini -- Save cursor position psave_pos(spos) -- get actual filelist filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES') -- find actual file if wordpos(.filename, filelist) then -- and remove it filelist = delword(filelist, wordpos(.filename,filelist),1) endif if (not wordpos(.filename, filelist)) and (substr(.filename, lastpos('\', .filename) + 1, 1) <> '.') then -- add the actual filename at the beginning filelist = .filename' 'filelist endif -- write the new list to the INI-file setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist ) -- Restore cursor position prestore_pos(spos) -- called just after file was saved defproc postsave_exit (fname) /* oldfile is the old filename if file was renamed */ universal appname, app_hini, oldfile -- Save cursor position psave_pos(spos) -- get the old file list filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES') -- if old filename is in the list if wordpos(oldfile, filelist) then -- delete it filelist = delword(filelist, wordpos(oldfile,filelist),1) endif if (not wordpos(fname, filelist)) and (substr(fname, lastpos('\', fname) + 1,1) <> '.') then -- Add actual filename if its not -- a tempfile ('.---') filelist = fname' 'filelist -- remove the 'and (substr...' section if -- you want to restart temp files endif -- store the new list setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist ) -- Restore cursor position prestore_pos(spos) -- called if file was renamed defproc rename_exit (oldfile, fname) universal appname, app_hini -- Save cursor position psave_pos(spos) -- same as for postsave_exit filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES') if wordpos(oldfile, filelist) then filelist = delword(filelist, wordpos(oldfile,filelist),1) endif if (not wordpos(fname, filelist)) and (substr(fname, lastpos('\', fname) + 1, 1) <> '.') then filelist = fname' 'filelist endif setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist ) -- Restore cursor position prestore_pos(spos) -- called if file is closed defproc quit_exit (fname) universal appname, app_hini -- just delete entry filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES') if wordpos(fname, filelist) then filelist = delword(filelist, wordpos(fname,filelist),1) endif setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist ) -- Defload functions are called whenever a file was loaded defload /* same as for namefile and savefile, just that there are no old names to delete */ universal appname, app_hini filelist = queryprofile( $HINI_PARM appname, 'STARTUP_FILES') if (not wordpos(.filename, filelist)) and (substr(.filename, lastpos('\', .filename) + 1, 1) <> '.') then filelist = .filename' 'filelist endif setprofile( $HINI_PARM appname, 'STARTUP_FILES', filelist ) /* Here comes the Cursor position feature */ -- issued just before a file is saved defproc presave_exit -- used to store the filename for postsave_exit universal oldfile -- delete old 'EPM.POS' entry in the EAs delete_ea('EPM.POS') -- get cursor and screen position,... psave_pos(screenpos) -- and store it in the EA 'addea EPM.POS' screenpos -- this one's needed for the restart feature oldfile = .filename -- This one's for setting the Cursor and window --position for the loaded file defload if find_ea('EPM.POS', ea_seg, ea_ofs, ea_ptr1, ea_ptr2, ea_len, ea_entrylen, ea_valuelen) then -- if there's an EPM.POS EA... -- that's the actual file getfileid myid 'postme restore_ORG_pos 'myid get_EAT_ASCII_value('EPM.POS') /* restore postions. have to post it 'cause the screen may not yet be painted. Also I had to make this the last of my defloads to make it work correctly */ endif -- This sets the cursor and screen-positions. -- It does the same as defc restore_ORG_pos -- prestore_pos except for being passed -- the fileid as first parameter parse value arg(1) with myid fline fcol cx cy myid.cursorx = cx myid.cursory = cy myid.line = fline myid.col = fcol
define -- 5.20 adds a HINI to the *Profile calls. compile if EVERSION >= '5.20' HINI_PARM = 'app_hini,' compile else HINI_PARM = ' ' compile endif -- called just before files are loaded upon startup defproc defmain_exit (var cmdline) universal appname, app_hini -- this is the command line + 'e '. -- If no parameters are specified ... if cmdline = 'e ' then -- ...add the ones from 'STARTUP_FILES' cmdline ='e 'queryprofile( $HINI_PARM appname, 'STARTUP_FILES' ) endif
Editor startup code via MYMAIN.E
MYMAIN.E is included and executed directly after the editors own startup code so you can put code here that has to be executed once after the editor was initialized e.g. recognizing messages for other applications (sorry, could not think of anything else). With the exception of the different include position handling of MYMAIN.E is the same as for MYSTUFF.E
File initializations and focus changes via MYSELECT.E
MYSELECT.E is another file that does not differ much from MYSTUFF.E. It is included after MYMAIN.E but before MYSTUFF.E. Theoretically, it should contain DEFSELECTs, code that is executed whenever a file is selected, but this does not necessarily have to be the case so we put this code into MYSTUFF.E also. See the MYSTUFF.E example.
Typical tasks for DEFSELECTs are cursor positioning, file-type specific changes to the keyset or the messageline or adding of file-type specific menus etc. Using DEFSELECTs you can change the behavior of the editor depending on the file-type.
We have seen how the EPM toolkit can be used to change your EPM configuration and how commands that you write can be integrated into the system. A little thought can yield a good editor for your uses at a great price. Also, support for EPM-related topics can already be found in comp.os.os2.programmer.misc and is provided by Larry "Mr. Macro" Margolis, who wrote most of the standard macros provided by EPM and is currently a member of the EPM development group within IBM.