Design and Implementation of an Advanced Text Editor

by Craig Bruce ( )


This section discusses the operations that have to do with maintaining the current secion of the document on the display and moving the cursor around within the document.


This is the essential operation that keeps the screen up to date with the docment in memory. ZED has a single function that does this operation, and it is called with the arguments: a pointer to the starting line to display, the starting screen line number to start painting at, and the ending screen line number to end painting at, plus one (lots of endings are 'plus one' since this allows me to use the BCC instruction). Some implied arguments include general information about the screen for use with the "aceWin*" kernel functions and the left margin for displaying lines.

This subroutine simply goes through the display lines one by one, displays the line contents, and advances the line pointer. To display a single line, the line is fetched into the line buffer from far memory and the number of displayable characters is calculated according to the line length and the left-hand display margin. The displayable characters (possibly zero) are written to the screen memory and if the line isn't completely full, then the remainder of the line is filled in with space characters. The displaying is completely performed by the "aceWinPut" system call. One oddity that needs to be handled is running out of document lines before filling the screen range. In this case, each remaining screen line is cleared.

Attributes (colors) are not used for this operation, since they are not needed and using them would only slow us down. They display-area color cells are initialiZED when ZED starts (as are the status-line and separator-line color cells) and don't change during operation. The colors come from the ACE palette (which the user can configure to his own liking).

This subroutine is used to both repaint the entire screen (when necessary) and to repaint only one line or a few lines (when I can get away with this). Repainting takes time, so we want to repaint only what has changed. However, repainting isn't too slow, especially when compared to serial-line speeds, since screen updates are written directly to screen memory, although the C64's soft-80 screen is significantly slower than the other screen types since so much more processor work needs to be done just to make a single character appear.

In the future, it may be useful to allow this function to abort in the middle of this operation if the user presses a key before the repainting is finished, in order to allow the user to work faster. For example, if you hold down the Page-Down keystroke, the speed that you go forward in the document is limited by how fast the screen can be repainted. If the repaint operation were abortable, then you could always go forward as fast as the key repeates, and when you get to where you are going and release the Page-Down key, the screen would repaint one final time and everything would be consistent. A flag would need to be kept to tell whether the screen is consistent or not, in order to make this work.

There is also a function that displays a message in the separator line on the screen. It also needs to store the displayed message in order to allow the user to scroll through it if the screen is not wide enough to display it in its entirety. When the message is no longer needed, it is erased by overwriting it with separator characters. And, there is also a function that updates all of the fields that have changed on the status line.


Before I start talking about the implementations of the individual commands, I should say something about the main control for the program. After ZED initializes and loads the initial document (even if you don't specify one, ZED will default to the name "noname" and try to load it), control is passed to a small main loop of simply displaying the cursor, waiting for a keystroke, undisplaying the cursor, calling the subroutine associated with the keystroke, and repeating.


Moving the current line to the top and bottom of the document is straight forward, because of the organization that was discussed in the data- structure secion above. To go to the top of the document, copy the trailer-line pointer to the current line pointer and then fetch the next pointer from the trailer line's header; this will give a pointer to the top line. Then we set the current line number to one and a couple of other variables and call the subroutine discussed above to repaint the screen.

Going to the bottom of the document would be just as easy as going to the top, except that we want the last line (the trailer line) to be displayed on the bottom of the screen in order to present as much useful document content to the user as possible. If there are fewer lines in the file than will fill a screen, then we cannot, of course, display an entire screen.

To make this business easier, a subroutine is provided that, given a starting line pointer and a count, will scan until it either hits the count-th line previous to the given one or it hits the top line of the document. It returns the number of lines that were actually scanned upwards (possibly zero), the pointer to the line that it stopped scanning at, and a flag indicating whether it stopped because it hit the top of the document or not. This subroutine is quite generally useful. There is a similar subroutine that scans downward.

So, after locating the bottom line of the document and setting the line number, the scan-upwards subroutine is called to scan upwards the number of displayable lines on the screen. The screen is then repainted in its entirety from the line that was scanned up to, and the new cursor-display location is computed from the count of the lines that were scanned over. This works equally well for a long document, a document shorter than the height of the screen, and an empty document.

The End-Left and End-Right commands are very simple in that they don't even have to change the current line pointer, but they do have to check if the cursor has moved off either the left or right edge (margin) of the screen. The visible columns go from the column number of the left margin, up to that plus the width of the screen. If the cursor goes off an edge of the screen, then the new left margin will have to be computed and the entire display will need to be repainted.

This repainting effectively achieves horizontal scrolling. Lines in ZED, of course, can be up to 240 characters across (241 if you count the carriage- return character), but the widest screen that ACE supports is 80 columns. Arguably, the horizontal scrolling could be done more efficiently by moving the contents of the display left or right by the requred number of columns and then filling in the opened spaces with the data from the correct columns of the display lines in memory. However, the additional complexity is non-trivial and the speedup may not be all that great except for the soft-80 screen of the C64. A better approach might be to go with the interruptable- repainting idea that I spoke of earlier, if the current line were updated first (so that you can see what you're doing) and the rest of the lines afterwards.


All of these functions follow quite naturally from what is above. For Page- Up and Down, the scan-up or scan-down subroutines already described are called to find the new current line for the cursor and then the entire screen is repainted, effectively paging up or down.

For cursor up and down, we just go up or down to the next line in the document and adjust adjust the cursor location on the screen. If the cursor goes off the top or bottom of the screen, then we scroll the screen up or down as appropriate (ACE can scroll the screen up or down and will eventually be able to scroll it left and right (although this will be a bit painful for the soft-80 screen since it may mean scrolling left and right nybbles)). Then, we display the current line at either the top or bottom of the screen to fill in the blank line that we just opened up. Because we use scrolling and painting only a single line, we can scroll the screen fairly quickly, easily keeping up with the cursor repeat rate, except on the soft-80 screen.

For cursor left and right, we advance the cursor one position on the line and see if it has gone over the edge. If not, then we are done; nothing needs to be redisplayed. If we have gone off the edge, then we call the cursor-up or cursor-down routines to go to the previous/next line and we position the cursor to either the end or start of the new line.

The word left/right functions are similar to the cursor left/right functions, except that we keep scanning until we run into the start of the next word. For word left, this is defined as running into a non-whitespace character that is preceeded by a whitespace character. A whitespace character is defined as either a space, a TAB, a hard return, or the beginning or ending of the document. For word right, the start of the next word is defined as a non-whitespace character that is preceeded by a whitespace character, where we start searching from one position to the right of the current cursor position. If we run into the beginning or end of the document, then we stop there.

BTW, all of these moving-around functions check the cursor position against the display bounds and "scroll" the display left or right if the cursor has gone off the screen. Well, actually, there is one exception to this rule. If the current line is the target-length number of characters long, and the target length equals the screen width, and carriage returns are selected not to be displayed, and the left margin of the display is column one (external), and the cursor is in the target-length-plus-one position of the line, then the screen is NOT scrolled right. Instead, the cursor is displayed on the last position of the line and is made to blink fast (an ACE feature). This is done to avoid the annoyance of having the screen scroll right when you are editing a text file on, say, an 80-column screen that has up to 80-character lines in it. The standalone ACE does this too, when you logically hit the 81st column.

On to Chapter 4.

Last Updated: 1995-12-06 Rev A