In part 1, I talked about how to get FIG-Forth for the IBM PC running on PCjs. FIG-Forth was a popular and very compact, public-domain version of the medium speed Forth systems programming language and environment during the early 1980s. Then part 2 covered how to implement a very rudimentary disk-based line editor as a precursor to an interactive full-screen editor; while part 3 dives into machine code routines and a PC BIOS interface, because there was no real screen cursor control via the existing commands.
Let's Edit!
Now, at least we can implement a screen editor. One of the constraints I'll impose will be to keep the editor to within 1kB of source code. At first that'll be easy, because all I want to support is normal characters, cursor control, return and escape to update. However, I know that I'll probably want to add the ability to copy text from a marker point using <ctrl-c>. But even a simple decision like this raises possible issues. Consider this, if I type VLIST I find I can press <ctrl-c> to stop the listing:
However, if I type this definition:
: T1 BEGIN KEY DUP . 27 = UNTIL ;
I find that <ctrl-c> doesn't break out of T1, instead it simply displays 3 and I really do have to press <esc> to quit the routine. But it could be that Forth still checks it automatically, just not with the above sequence of commands in T1. No, from the FIG-Forth source we can see, the breakout of VLIST is simply due to it executing ?TERMINAL and exiting if any key has been pressed. So, that potential problem is sorted!
VLIST Source Code:
DB 85H DB 'VLIS' DB 'T'+80H DW UDOT-5 VLIST DW DOCOL DW LIT,80H DW OUTT DW STORE DW CONT DW AT DW AT VLIS1 DW OUTT ;BEGIN |
DW AT DW CSLL DW GREAT DW ZBRAN ;IF DW OFFSET VLIS2-$ DW CR DW ZERO DW OUTT DW STORE ;ENDIF VLIS2 DW DUPE DW IDDOT DW SPACE DW SPACE |
DW PFA DW LFA DW AT DW DUPE DW ZEQU DW QTERM DW ORR DW ZBRAN ;UNTIL DW OFFSET VLIS1-$ DW DROP DW SEMIS |
The Editor Itself
The first goal in the editor is to convert y x coordinates in the current screen to a memory location in a buffer. This is a minor change from the initial part of the code in EDL:
: EDYX& ( Y X -- ADDR)
>R 15 AND 8 /MOD SCR @ B/SCR * +
BLOCK SWAP C/L * + R> +
;
We want to be able to constrain Y and X coordinates to within the bounds of the screen (in this case with wrap around):
: 1- 1 - ; ( oddly enough missing from FIG-Forth, but I use it quite a bit in the editor)
: EDLIM ( Y X -- Y' X')
DUP 0< IF
DROP 1- 0
THEN
DUP C/L 1- > IF
DROP 1+ 0
THEN
SWAP 15 AND SWAP
OVER 2+ OVER 4 + AT
;
The key part of a screen editor is to be able to process characters, I've picked the vi cursor keys:
: DOKEY ( R C K )
>R
R 8 = IF ( CTRL-H, Left)
1-
THEN
R 12 = IF ( CTRL-L, Right)
1+
THEN
SWAP R 11 = IF ( CTRL-K, Up )
1-
THEN
R 10 = IF ( CTRL-J, Down)
1+
THEN
SWAP R 13 = IF ( CR or CTRL-M)
64 +
THEN
EDLIM
R 31 > R 128 < AND IF ( PRINTABLE)
2DUP EDYX& R SWAP C!
R EMIT 1+ UPDATE EDLIM
THEN
R>
;
Finally we want to put it all together in a top-level function:
: ED ( scr -- )
CLS LIST 0 0 EDLIM
BEGIN
KEY DOKEY
27 = UNTIL
DROP DROP
;
Interestingly, once I'd cleared up an initial bug where the cursor wasn't advanced when I typed a character, and another where I'd missed an AND when checking for printable characters, I was able to use the editor itself to edit improvements ( namely, putting the initial cursor at the right location instead of (0,0)).
Finally, although Forth isn't always as compact as its proponents like me often claim, in fact this editor in itself uses a mere 306 bytes, probably the most compact interactive editor I've seen and there's still over half the screen left for improvements. For example, there's no support for delete ( left, space, left); for inserting a line; copying text, nor blocks. But for the moment, it's easily far more enjoyable than the line editor it replaces.
Exercise For The Reader
The biggest user-interface problem I've found with extensions to the editor has been to decide which control character to use to mark the text location for copying. To explain: many archaic screen editors, including some early word processors used a mark, edit sequence for text manipulation. The user would move the cursor to where they wanted to perform an 'advanced' edit operation; mark the initial location; then move the cursor to either where they wanted the edit operation to finish; mark the end of that edit text; then finally, possibly move the cursor to some other location and complete the edit. For example, on the Quill word processor for the Sinclair QL, you'd Type F3, 'E' (for Erase), move the cursor to where you wanted to erase a block; press enter; move to where the erase should finish (and it would highlight the text as you went along); press enter; confirm you wanted to erase it and then it would. Or in the Turbo C editor you'd Mark an initial starting location; then Mark again an ending location and then perform an operation like 'Copy' to duplicate the text, or 'Delete' or 'Move' to move the text.
Or on a BBC Micro, the BASIC editor was essentially a line editor which supported two cursor positions (!!) If you needed to manipulate a line instead of just retyping it, you'd list the line (if it wasn't on the screen), then move the cursor keys and a second cursor would appear, moving to where you wanted on the screen; while the cursor at your editing position would remain. You'd then hit COPY and it would copy from the second cursor to your editing position, advancing both cursors.
So, in my system, which is similar to how editing works on FIGnition, I'd want to Mark the position where I wanted to copy / erase from; move to where I wanted to paste or finish a delete to and then COPY / MOVE a character at a time from the source to the destination (or Erase the text).
However, the most obvious control character to use, <ctrl-m> is already used for Return, and everything else seems rather contrived. Then I thought, what happens if I use <ctrl-symbol> instead? Do they produce interesting control codes? I found out quite a number produce 0s, but some actually generate the control codes in the range 0..31 that you can't generate from <ctrl-a> to <ctrl-z>.
This is what I found out:
Ctrl+ | Code | Ctrl+ | Code |
\ | 28 | ] | 29 |
6 | 30 | - | 31 |
So, what I'd like to know is whether this is just an artefact of the simulator being used on a Mac or whether it's common to other emulators and an actual IBM PC?
Conclusion
Once I'd implemented some earlier, critical definitions it turned out to be quite easy and satisfying to write a full-screen editor. The biggest challenges were in making sure certain key presses wouldn't collide with any system behaviour and finally thinking about some user-interface decisions for some future enhancements.
The editor also nicely illustrates some key Forth aspirations: the editor turns out to be very compact (though of course it's very rudimentary too); and I was able to use it to debug and improve itself once I'd reached some critical level of functionality. It was so easy and tiny, I wonder why it wasn't a standard part of FIG-FORTH, given that it was designed in an era when cursor addressable VDUs were already the norm.