From Bitchin100 DocGarden
Jump to: navigation, search

From: PORTABLE 100, March 1989, pgs 8–13.

Get WordStar cursor control for TEXT.

By Stan Wong

Republication, bugfixes, and extensions by John R. Hogerhuis

I'm a WordStar addict. There is nothing flashy about the program. It's not even sexy. But I love WordStar and its arcane cursor movement and control commands. It was, and still is, one of the few word processing programs supported across many different machines and operating systems. I use it on a DEC Rainbow and a 386 PC at work. I use it on my IBM PC/XT. And I bought an NEC 8500 CP/M laptop because it had WordStar built in.

My fingers get cramps every time I use my trusty Model 100, and trying to remember the different cursor movement and editing commands drives me batty.

Well, no more!

A Diamond in the Rough

The Model 100 TEXT program already implements the famous WordStar cursor control-character "diamond" – ^S, ^E, ^D, ^X, where the caret (^) signifies holding down the CTRL key, as WordStar users know – but it doesn't go far enough.

So I've polished up the diamond a bit. I've written a small machine language program called TextStar (or Text* to its friends), which implements most of the WordStar cursor movement commands as well as some text deletion and other miscellaneous commands.

TextStar is not a complete program by itself. Instead, it enhances the operation of TEXT. Although TextStar's cursor control looks and feels like WordStar, it still retains a TEXT-like flavor. See the sidebar "Inside TextStar" for technical information on how it performs its magic.

TextStar extends the operation of TEXT by redefining the meaning of the control keys, where possible, to match those of WordStar (See Table 1). Even multiple-keystroke commands such as ^QD, are supported. Control keys that have not been redefined still retain their TEXT meanings. For instance, ^B still moves the cursor to the bottom of the screen. ^T will not, however move you to the top of the screen but will delete a word instead.

A simple change to TextStar could have made it ignore the TEXT control keys, but I felt it would be better to leave them in. The program owes its small size (381 bytes) to its method; it traps keyboard input and translates only WordStar control codes into their TEXT equivalents, passing all other keystrokes directly to TEXT for normal processing.

Cursor Movement
^D Right one character
^F Right one word
^QD Right to end of current line
^S Left one character
^A Left one word
^QS Left to beginning of current line
^E Up one line
^QE Top of screen, column 1
^QR Top of file
Text Deletion
^G Delete character under the cursor
^T Delete word
^BKSP Delete prior word (NEW feature -- Jhoger)
^Y Delete line
^QY Delete to end of line
^KB Mark start of block (same as F7)
^KY Delete block (same as F6)
^KD Exit TextStar to menu (same as F8)
^QY Delete to end of line

Vive La Difference

However, TextStar does not perform exactly like WordStar. Because it enhances the TEXT program, it is bound by certain TEXT conventions.

For instance, when moving up and down a line, WordStar moves the cursor to the end of a line if the destination line is shorter. But TextStar, like TEXT, leaves the cursor in the same column regardless of the destination line's length.

As a bonus, the text deletion commands ^T and ^Y store the deleted text in the paste buffer. So if you make a mistake you can recover the text by pressing the PASTE key. This gives you a simple UNDO facility.

A Star Is Born

Listing 1 is a relocating BASIC loader. It creates a machine language program, TXTSTR.CO, to run anywhere you specify in high memory, or it will default to loading just under the current HIMEM. It can thus coexist with your other .CO programs. (Thanks to Phil Wheeler for his BASBLD.PW3 program, which I used to create the loader.)

Listing 2 is the assembly language source code for TextStar. I used the ADSM assembler by James Yi, which you can find on the CompuServe Model 100 Forum. Minor syntax changes to the source code will also make it compatible with the ROM2 assembler from Traveling Software, which I used to do the initial program development.

You have no need to use the assembly language listing, however, other than to understand what TextStar is doing and how it does it.

A Rising Star

To run TextStar, place the widebar cursor over TXTSTR.CO on the main menu and press ENTER. You should then see TEXT's familiar File to edit? prompt. Specify the .DO file to edit, and you're on your way.

If the program doesn't start, go into BASIC and type CLEAR 0, nnnm (where nnnm is the load point) and then try again. If you don't know the load point, type LOADM "TXTSTR" and use the Top address returned by the LOADM command as the load point (nnnm) for the CLEAR statement above.

Where To Get TextStar

TextStar is also available on the P100-To-Go disk and on the Portable BBS (603-924-9770) as TXTSTR.100. If you need the program and listings on a cassette tape, send $6 to me at P.O. Box 28181, Santa Ana, CA 92799. You can also contact me via CompuServe user ID 70346,1267. I support the program on the M100SIG of CompuServe.

Those cramps in my fingers are finally starting to disappear. Happy typing!


The key to TextStar's operation is its ability to intercept keystrokes before TEXT sees them, by using a programming mechanism known as a RAM hook. Before I describe how TextStar uses RAM hooks, let's take a look at what TEXT does with your keystrokes.

The TEXT program is constructed as one big loop starting at ROM address 5DEEH. TEXT' waits for your keystroke, gets it, and takes the appropriate action.

It's interesting to note that near the beginning of TEXT, the program pushes the address of the top-of-the-TEXT loop onto the stack.

Normal loops end with a JMP nnnn instruction, where nnnn is the address of the top of the loop. But by pushing the top-of-the-loop address on the stack, the program can instead simply execute a RET instruction from anywhere to get back to the start.

This has two advantages. First, it saves memory for each loop return point. It needs only one byte for the RET instruction versus three for the JMP. Second, the code can be more easily relocated. If the code is, for some reason, changed and shifted in memory, then all the JMP instructions do not have to be recoded with the new top-of-the-loop address.

Give 'em the Hook, the Hook

TEXT uses a ROM routine known as CHGET (located at 12CBH) to wait for your next keystroke. The TEXT call to this "Tandy-documented" routine is at address 63F1H.

CHGET makes use of a RAM hook. What's a RAM hook? Is it related to Captain Hook? Not really.

A RAM hook is a programming mechanism by which the system designer provides a way for users to modify or replace certain system routines. RAM hooks are commonly provided for ROM routines, since code in ROM is fixed and cannot be changed.

Specifically, some ROM routines incorporate an RST 7 instruction to give users access to hooks, allowing them an opportunity to "splice in" their own code instead. It works like this.

At address FADAH is a "hook table," each table entry being a two-byte address. In effect, the RST 7 instruction in a CALL to one of these addresses. It finds the appropriate entry by adding an offset byte (which follows the RST 7 instruction in the ROM routine) to the start of the table, and then CALL's the address stored there.

For example, the CHGET routine incorporates an RST 7 instruction at the beginning of the routine. The value of the offset byte is 4. This means that the RAM hook at FADEH (FADAH + 4) is used for CHGET.

Since most entries in the hook table point to a RET instruction in ROM (at 7FF3H), the RST 7 normally CALL's 7FF3H, which simply returns back to the ROM routine, chewing up a few machine cycles and nothing more.

But since the hook table is in RAM, the user may change an entry to point to his own routine, in which case the RST 7 performs a CALL to the user's routine.

Once RST 7 locates the two-byte address in the table, it executes the routine located at that stored address. For routines like TextStar that use these hooks, RST 7 effectively CALL's TextStar, which does its business on a keystroke and then returns to CHGET.

Thus, a hook gives us a chance to sneak in and peek at a keystroke before it is processed and returned to TEXT.

Can You Direct Me To... ?

Okay, so now that we know what a RAM hook is, how do we use it? The first thing to do is save the original contents of the RAM hook at FADEH. Typically it is just a pointer to RET (address 7FF3H), but someone else might be using it, so we'll politely save it and restore it when we're done.

Next, we put the address of our own code (we'll use the address TS1) into the hook. Now every time TEXT gets a keystroke, we get a peek at it first.

Relax and Unwind

Now that we can get a peek at a keystroke before TEXT does, what next?

Other programs use the CHGET routine too. So we have to make sure that CHGET was invoked by TEXT and not another program. TEXT calls CHGET at address 5FEDH. (Actually it's called at 63F1H, from within a routine called by 5FEDH.) This means that there will be a RETurn to 5FF0H somewhere on the stack.

When we get control via the FADEH RAM hook, the return address to TEXT, if it was called by TEXT, is 10 levels (20 bytes) down in the stack.

So we have to unwind the stack 10 levels and see if the address 5FF0H is there. If it is, then CHGET was called from TEXT and this keystroke is for us. If not, then we must put everything back the way it was.

This discussion is included for hysterical reasons; the program as it currently exists no longer unwinds the stack.

The problem with the approach in the original TextStar was that it "dropped" bytes from the stack and added them back in. This is a no-no. Anything below the stack pointer is free space. Increasing the pointer value (since the stack grows downward in RAM) effectively "deallocates" the memory below the new pointer location. If a call is made, or an interrupt occurs during this time, the free space will likely be overwritten. So, it is not safe to assume that the data will be there when you lower the stack pointer position to include them again.

I believe Stan was assuming that his DI (disable interrupts) instruction would be sufficient to prevent the temporarily dropped bytes from being corrupted; however, the TRAP non-maskable interrupt is not prevented by DI. The result, I believe, was that power-off was causing the stack to get corrupted.

The unwind code has been replaced with logic that leverages the DESP instruction to observe and modify the 5FF0 pointer on the stack without the dangerous and unnecessary step of unwinding. The enable and disable interrupt instructions were unnecessary and removed. Finally, additional registers (HL and PSW) are now preserved (and restored) since it wasn't obvious to me that it was safe to destroy them.

-- John (jhoger@pobox.com)

How can a kestroke in TEXT not be for us? There are functions within TEXT, such as F1 (Find) function, that require keyboard input separate form the main TEXT processing loop.

The stack unwinding code is liberally borrowed from the TXT-OV.ASM program by James Yi and Paul Globman. The code uses an undocumented 8085 instruction that subtracts BC from HL and stores the result in HL.

HL ← HL - BC

The mnemonic for this instruction is HLMBC in the ROM2 assembler syntax and DSUB in the ADSM assembler. If defined instead as DB 8, as in Listing 1, all assemblers will support it.

Sixteen-bit arithmetic on the 8085 is quite limited. This, and several other undocumented instructions, can save many instructions and make for easier programming.

What A Character!

At label WSTXT, we examine the keystroke. If the character is a control character, then we have to look for "our"" control characters. Otherwise, we pass control to ROM location 6005H for control characters and location 608AH for "normal" characters and process the keystrokes as if we had never been there.

When we do find our control character, we simulate the commands that will implement the WordStar function. Look at the code for the ^Y function (delete line) as an example.

Multiple keystroke sequences, such as ^KD, pose a more difficult problem. When a ^K is typed, we can't act until we know what the next keystroke is. The solution is to set a flag indicating that the ^K has been typed. On every kestroke we have to check the flag to see if a ^K is in effect.

On a ^KD ( or F8) exit command, we must restore RAM hooks to their original values and exit to the main menu by JMPing to location 5797H. Failure to do so may result in a machine that feels very cool to the touch (i.e., COLD START!).