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.
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.
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).
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.
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.
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:
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.
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:
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:
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.
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.
Example MYSTUFF.E
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.
MYSTUFF.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
MAINEXIT.E:
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.
Summary
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.