Rexx Center features a built-in macro language, so that you can automate various tasks. For example, maybe you'd like to make a macro that strips all of the REXX comments out of some editor window. Or perhaps you'd like to make a macro that inserts some pre-written REXX instructions into an editor window, or opens up a new window and creates a "template script" for you. Or, maybe you'd like to make a macro that jumps to every line with an error indicator (ie, a red arrow) and writes out a log file containing those lines. Or, maybe you'd like to write a macro that saves the contents of an editor to disk and invokes some other program on that file.

You can do all of these tasks, and many, many more things with Rexx Center's macro language. Mostly, anything you can do manually to an editor window, you can make a macro to do.

It just so happens that Rexx Center's macro language is REXX. Therefore, you write REXX scripts that, when run by Rexx Center, can read the contents of open editor windows, write new contents to an editor window, delete text from an editor window, open new editor windows, load and save the contents to disk, set/clear/toggle bookmarks/breakpoints/error indicators, copy and paste between the clipboard and an editor window, etc.

And your scripts can use REXX GUI to create a user interface if you need to interact with the person using Rexx Center. So, for example, you can present a window to get some information from the user, and then open an editor window and write REXX instructions to it.

When a script runs inside of Rexx Center, it has several, special functions that it may call. Each of these functions starts with the four letters Edit, such as EditOpenDoc(). These functions control Rexx Center itself. For example, your script can call EditOpenDoc() to load a REXX script (from disk) into an editor window, or select one of the already-opened windows, or create a new, empty window. Your script can call EditSelect() to delete text from an editor, or write text into an editor. And there are more functions to do things like set bookmarks upon lines, or read lines from the editor window, etc.

In fact, you can run Rexx Center's debugger on your script, so that you can debug the script even as it is controlling Rexx Center. In this way, you can easily edit and test your "macros" right from Rexx Center. Once you've ensured that your macro is doing exactly what you want it to do, then you can add it to Rexx Center's Macro menu, using the {\uldb Macro -> Add a Macro}{\v menu_macro} menu command. The name of the script will appear underneath the menu. The script will be automatically loaded each time that Rexx Center runs, and you can also bind it to any keyboard shortcut using the {\uldb Edit -> Keyboard shortcuts}{\v HID_SHOW_KEYMAPS} menu so that you can quickly run it whenever you desire.

Note: One caveat with Rexx Center's macro language is that you can't simultaneously run any REXX script while your macro is running. But otherwise, there are no other limits, and writing a Rexx Center macro is the same as writing any other REXX script, except that you have a few special functions you can call.


Your first macro

Let's get started trying our hand at writing a macro.

First, let's write a macro that creates a new, empty editor window. To do this, you call the special function named EditOpenDoc. If this successfully opens a new window, it will return an empty string. If there is a problem, it will return an error message. Here then is a macro that opens a new window, and checks for an error. Cut and paste the following REXX instructions to an editor window (just as if you were writing any other REXX script using Rexx Center). Then select Rexx Center's "Run -> Execute script" menu item to run this script. (You'll be prompted to save it to disk. Choose any name you desire).

/* Open a new editor window */
error = EditOpenDoc()
/* Check for an error */
IF error \== "" THEN DO
   SAY error
   RETURN
END
You should see a new editor window pop open. It will have a default name If you wish to know what the name of the window is, you can call the special function EditGetDoc, and pass no arguments. Here, we open up a new editor, and then SAY its name.
/* Open a new editor window */
error = EditOpenDoc()
/* Check for an error */
IF error \== "" THEN DO
   SAY error
   RETURN
END
SAY EditGetDoc()
All of the other special Rexx Center functions you call (in your one macro) will operate upon this window until you call EditOpenDoc() or EditGetDoc() to reference some other window.


Writing to a window

Let's do something with this window. For example, let's write 3 lines of text to the window. The lines will be "Line 1", "Line 2", and "Line 3". To write lines to a window, you use the EditSelect function. Before calling this function, you set up some compound variable to the desired lines, and also set a count of how many lines you have. For example, if we use the stem variable 'MyVar', then we would set MyVar.0 to how many lines of text we want to write, and MyVar.1 would be the first line, MyVar.2 would be the second line, etc. So here is how we initialize our variable:

MyVar.0 = 3 /* I want to write 3 lines */
MyVar.1 = "Line 1"
MyVar.2 = "Line 2"
MyVar.3 = "Line 3"
Then, you pass this variable name as the third arg to EditSelect(). For now, we won't worry about the other args.

If successful, EditSelect() returns the text cursor's new position. (We'll get to that in a moment). If a problem, an empty string is returned.

Here we open a new window, and write 3 lines to it. Copy and paste the following instructions to an editor window, and then select "Run -> Execute script".

/* Open a new editor window */
error = EditOpenDoc()
IF error \== "" THEN DO
   SAY error
   RETURN
END

/* Write 3 lines */
MyVar.0 = 3
MyVar.1 = "Line 1"
MyVar.2 = "Line 2"
MyVar.3 = "Line 3"
position = EditSelect(, , 'MyVar')
IF position == "" THEN SAY "Couldn't write the lines!"


The text cursor

The text cursor is where text is entered in the window when you type on the keyboard. You can set this position when you call EditSelect() so that you can determine where your lines are inserted. Normally, the text cursor is automatically placed at the end of the last line you insert. So, if you call EditSelect() several times, the lines will get appended.

But you can also specify a position. A position is expressed in terms of a line number, and a character upon the line. For example, the first line in a editor is line number 1. The first character upon that line is character number 1. The second line in the editor is line number two, and the first character upon that second line is character number 1. You specify the line number first, and then a space, and then the character number. For example, the sixth character upon the third line would be '3 6'. The value -1 for the character number has a special meaning, and it indicates the end of the line. For example, a position of '2 -1' means the end of the second line.

To set the cursor to a particular position, you can pass the start and end args to EditSelect(). You will specify the same position for both if you simply want to set the text cursor without any selection. Otherwise, if you wish to select some text, you can set the start position where to begin the selection, and the end position where to end the selection. The start position is the first arg passed to EditSelect(), and the end position is the second arg.

For example, here we set the cursor to the third character in line 4, with no selection:

position = EditSelect('4 3', '4 3')
IF position == "" THEN SAY "Couldn't set the position!"
Here we select all of the characters from the first character upon line 2 to the end of line 4:
position = EditSelect('2 1', '4 -1')
IF position == "" THEN SAY "Couldn't set the selection!"
The cursor position is actually set to the end position of the selection.

Therefore, to insert text so that it overwrites other, existing text, you specify a selection. For example, here our three lines overwrite the first line in the file. (ie, We select all the characters in the first line by specifying a start of '1 1' and an end of '1 -1').

/* Write 3 lines that overwrite any existing, first line */
MyVar.0 = 3
MyVar.1 = "Line 1"
MyVar.2 = "Line 2"
MyVar.3 = "Line 3"
position = EditSelect('1 1', '1 -1', 'MyVar')
IF position == "" THEN SAY "Couldn't overwrite!"
To select all of the lines and characters in an editor window, you set the start position to '1 1', and the end position to '-1 -1'. (ie, A line number of -1 has a special meaning, and indicates the last line in the editor window). Here, we overwrite the entire contents of the window, and replace it with a blank line:
MyVar.0 = 1
MyVar.1 = ""
position = EditSelect('1 1', '-1 -1', 'MyVar')
IF position == "" THEN SAY "Couldn't erase the contents!"
You can optionally pass the text you wish to insert directly to EditSelect(), in place of the variable name. You indicate this by passing a fourth arg to EditSelect() of 'TEXT', and directly passing the text in place of the variable name.

Normally, EditSelect() inserts entire lines. But by passing the text directly to EditSelect(), you can insert/overwrite part of an existing line, prepend to the beginning of an existing line, or append text to the end of a line.

For example, here we append a comment to the end of any existing line 3:

position = EditSelect('3 -1', '3 -1', '/* My Comment */', 'TEXT')
IF position == "" THEN SAY "Couldn't append!"
Here we insert a label called MyLabel: at the start of line 2:
position = EditSelect('2 1', '2 1', 'MyLabel: ', 'TEXT')
IF position == "" THEN SAY "Couldn't prepend the label!"
Here we overwrite the first 4 characters on line 1 with the text "This is my replacement". We set a selection by specifying a start position of '1 1' and an end position of '1 4'. The selection will be overwritten.
position = EditSelect('1 1', '1 4', 'This is my replacement', 'TEXT')
IF position == "" THEN SAY "Couldn't overwrite part of line 1!"
It is possible to embed line breaks in text that you pass directly. You use a '0D'X (or '0D0A'X). For example, here we insert some text with a line break when overwriting those 4 characters:
position = EditSelect('1 1', '1 4', 'Line 1' || '0D'X || 'Line 2', 'TEXT')
IF position == "" THEN SAY "Couldn't overwrite part of line 1!"
If you wish to simply delete text without inserting any replacement text, then set a selection, and pass an empty string. You do not need to specify the 'TEXT' option, although you can. For example, here we delete the first 3 lines in the editor by setting the start position to '1 1' (ie, beginning of the editor), and an end position of '3 -1' (end of line 3), and passing an empty string:
position = EditSelect('1 1', '3 -1', "")
IF position == "" THEN SAY "Couldn't delete lines!"
You can optionally paste text from the windows clipboard (instead of supplying text directly, or from a compound variable). You pass a fourth arg of 'CLIP'. For example, here we paste the contents of the clipboard at the end of the editor:
position = EditSelect('-1 -1', '-1 -1', "", 'CLIP')
IF position == "" THEN SAY "Couldn't paste clipboard!"
Incidentally, EditSelect() always returns the final position of the text cursor, and inserting or deleting text may cause its position to change.

If you wish merely to find out what the text position currently is, without deleting nor inserting any text (nor altering the selection), then pass no arguments to EditSelect():

position = EditSelect()
IF position == "" THEN SAY "Couldn't get position!"
ELSE SAY "Text cursor is at" position


Reading text from a window

If you wish to read text from an editor window, you'll use the special function EditGetText. The first arg passed is the name of a compound variable where Rexx Center will store the text. The second and third args are the start and end positions of where to begin and end. If successful, EditGetText() returns an empty string. If a problem, an error message is returned.

For example, here we read from the first character on line 2 (ie, '2 1') to the third character on line 4 (ie, '4 3'). We tell Rexx Center to use the stem variable 'MyVar', so Rexx Center stores those as three lines in MyVar.1, MyVar.2, and MyVar.3 (and sets MyVar.0 to a count of 3):

error = EditGetText('MyVar', '2 1', '4 3')
IF error \== "" THEN SAY error
Here we read all of line 3:
error = EditGetText('MyVar', '3 1', '3 -1')
IF error \== "" THEN SAY error
If you omit both the start and end positions, then Rexx Center returns whatever text is currently selected in the editor. For example, here we get whatever text is selected. (If no text is selected, EditGetText() returns an error message of "No text").
error = EditGetText('MyVar')
IF error \== "" THEN SAY error
ELSE
   /* SAY all the selected lines */
   DO i = 1 TO MyVar.0
      SAY MyVar.i
   END
You can copy text to the windows clipboard (instead of a variable) by passing a fourth argument of 'CLIP'. Here we copy the current selection to the clipboard:
error = EditGetText(, , , 'CLIP')


Searching for text

If you wish to search for certain text in an editor, you could use EditGetText() to retrieve the text and scan it yourself, counting lines and characters positions in order to determine where the text is located. But Rexx Center had a special {\uldb EditFind()}{\v EditFind} command that does the searching for you.

The first arg you pass to EditFind() is the text you wish to find. You cannot have any line breaks in the text. EditFind() is limited to searching for text that doesn't span lines.

The second arg you pass to EditFind are your search options. They can be any of the following, each separated by a '|'.

'CASE' Match case.
'WORD' Match whole word.
'SEL' Limit search within the current selection.
'WRAP' Wrap around when reaching the head or end of the editor.
'BACK' Search backward from the current position, rather than forward.
'WARN' Show a message box if the text isn't found.

If EditFind() finds an occurence of the specified text, it returns the position where that text starts in the editor window. If no occurence is found, then EditFind() returns an empty string.

For example, here we set the position to the start of the editor, and search for the first occurence of the text "some stuff":

position = EditFind('some stuff')
IF position == "" THEN SAY "Not found!"
If the text is found, then the text cursor will be set to that position. Therefore, a subsequent call to EditFind() can find the next occurence of that text.

If you omit the text to find, then EditFind() presents the Find Dialog.


Moving the cursor

The special function EditMove can move the text cursor's position relative to where it currently is. For example, you can move down or up one line, or left or right one character, etc. This is identical to if you manually use the arrow keys or the page up, page down, home, and end key.

If successful, EditMove() returns the text cursor's new position. If a problem, an empty string is returned.


Setting attributes

The special function EditSetFlag can set, clear, or toggle a bookmark, breakpoint, or error indicator upon the current line (ie, the line that the text cursor is currently located on).

The first arg you pass tells which item you wish to change, as so:

'UNNAME' Unnamed bookmark.
'NAM1' Named bookmark 1.
'NAM2' Named bookmark 2.
'NAM3' Named bookmark 3.
'NAM4' Named bookmark 4.
'NAM5' Named bookmark 5.
'NAM6' Named bookmark 6.
'NAM7' Named bookmark 7.
'NAM8' Named bookmark 8.
'NAM9' Named bookmark 9.
'NAM0' Named bookmark 10.
'BREAK' Breakpoint.
'ERROR' Error indication.

The second arg is whether you wish to toggle, set, or clear that item. You can also choose to clear that item from all lines in the editor. The second arg is as so:

-1 Clear the item from all lines.
0 Clear the item from the current line.
1 Set the item in the current line.
2 Toggle the item in the current line.

If omitted, 1 is assumed.

EditSetFlag() returns the previous state of the item (ie, 0 if it was clear, or 1 if it was set). If there is a problem, EditSetFlag() returns an empty string.


Querying attributes

A complementary function called EditFindFlag lets you check if the current line has any of the above items set. Optionally, it can search for another line that has any of the above items set.

It takes one arg, which is the same as EditSetFlag()'s first arg, except that you can specify more than one item, each separated by a '|'. You can also specify the following additional items:

'DOWN' Search for the next line with one of the items set.
'UP' Search for a previous line with one of the items set.
'HOME' Search from the beginning for the next matching line, with no wraparound.

EditFindFlag() returns the line number of the line that has at least one of the specified items set, or 0 if none. Otherwise, if there is an error, a -1 is returned.


Errata

Before calling any other Rexx Center functions, the best policy is for your macro to first call EditOpenDoc() or EditGetDoc() once. This establishes the document and editor window that you operate upon.

If you know the name of the document that you wish to operate upon, then call EditOpenDoc() and pass that name. If the document is already loaded, it will simply be selected as the current document. Otherwise, it will be loaded from disk.

If you wish to create a new, blank window, then call EditOpenDoc() without any specific name.

If you first wish to check whether a particular document is already loaded, then call EditGetDoc() and pass that document's name. If EditGetDoc() doesn't return an empty string, then that document is loaded and will be made the current document. You can also operate several different documents by calling EditGetDoc() when you want to switch documents. But Rexx Center functions affect only the current document.

If you want to operate upon whatever window is active when your macro first starts, then simply call EditGetDoc() without any args. It will select the currently active document, and return its name.

If you wish to test whether you're running as a Rexx Center macro, your script can use the standard RXFUNCQUERY() function to test whether one of the special Rexx Center functions is registered. If registered, then you're running as a macro. If not, then your script has been run by some other program than Rexx Center.

error = RXFUNCQUERY('EDITSELECT')
IF error \== 0 THEN SAY "I'm not a Rexx Center macro."
ELSE SAY "I'm running as a Rexx Center macro."