The question for this article is: What should be included in the displayed window for a particular document?
We have a document. We have a current point (cursor) in the document. And we have a display area (window) with certain space characteristics (width, height, DPI) and we want to display part of the document, presumably including the part around the point, making best use of the available space.
There are two issues that can guide us. The first is the need for stability in the display : a small change in the document or point should cause a small change in the display. The other is information content : the display should be as informative as possible.
These two issues can conflict, so they need to be assessed together. However both a quite substantial issues and so developing them in parallel is not practical.
In this article we explore the stability issue assuming a very simple information model. In a later article we might explore information content and then blend the two together.
The information model that we will use is that content closer to the cursor is more informative that information further away, and so the displayed content will be a contiguous region of character or lines around the current point. This is the normal model for most text editors. Displays with both line-wrapping and line-truncation-with-horizontal-scroll will be discussed.
Our question is: What information do we need beyond the content and the current-point in order to present a display that is reasonably stable, and how do we use the information to generate the display.
First we will consider some examples.
One editor that I used many years ago had a very simple display model. The current line was always displayed on the center line of the screen. Surrounding text was displayed above or below as appropriate. Thus when the cursor is moved "down", what actually happens is that the whole display is scrolled one line "up". This is simple and predictable, but not very stable - the content of the display moves around more than one would like.
EMACS provides quite a stable display if one ignores scrolling. When moving around among displayed text, the display doesn't change at all (except for the cursor) and when inserting or deleting text minimal changes are made, and where larger changes are needed (such as when inserting a line-break), text below the cursor is moved, which is fairly intuitive.
EMACS achieves this by storing, as well as the cursor, a point in the text which is at the top of the window. This point is only changed when the cursor would no-longer appear in the display. In that case various scrolling rules come into play.
By contrast, the editor that I am currently using (textarea in Firefox 0.8) does not provide quite the same stability. If I am added text at the end of the display and delete everything on the last line (maybe removing a word I just type because I wanted to change it), the whole display scrolls down one line so that the end of the current document aligns with the end of the display. It would appear that this editor does not like showing any blank lines after the document unless the document is smaller than the displayed area. This leads to a lack of stability that I find quite unpleasant.
I find that emacs loses it's nice stability when scrolling or window geometry changes start to happen.
The default behaviour of emacs when the cursor moves off the bottom of the display is to scroll the display by half its height, leaving the cursor in the middle of the display. This is quite a large change in display for a fairly small change in cursor position.
This behaviour is probably due to a focus on working well over slow links to simpel terminals. This half-scroll-at-a-time policy means that most of the time the display doesn't scroll which is good for slow links. However for fast links, it is better to scroll a little bit often which is the normal behaviour for "vi".
This scrolling behaviour in emacs is (very) configurable so I can get emacs to behave how I like, but the average user should need to worry about configuration. It should work nicely by default, and I think stability is very nice.
EMACS also has horizontal scrolling that happens automatically when long lines are not being truncated. This scrolling always scrolls half the width of the display, and doesn't seem to be configurable. This does not present good stabilitiy.
Another area where EMACS' display doesn't work so well is when a window is split or resized.
On testing EMACS a bit, it does mostly do what you would want. However if the cursor is in the middle of the display, then a vertical split means that the display has to be scrolled to make the cursor visible (as the middle line gets used as a status bar for the top window). When this happens, emacs scrolls both the top and bottom by half their height leaving the cursor in the middle of both rather than near where it was to start with (i.e. bottom of top window, and top of bottom window). While it is possibly a very subjective area, I would prefer the display to jump-around as little as possible.
One more example: In this firebird textarea editor with word-wrap enabled, a word jumps to the next line if it becomes too long to fit on the current line. This is OK, as better than the emacs approach of only wrapping when space is typed (though emacs works OK if the wrap margin is sufficiently less than the visible margin). However if you delete the character the caused the word to flow to the next line, the word immediately jumps back to the previous line, which can be very disturbing. This is another case were stability is needed - the should only jump back when it has been finished with - when a space is typed, or when the cursor otherwise leaves the word.
Principles
It is useful to list some basic prinicples for stability.
- Small changes in content or location should result in small changes in appearance.
- Stability in the immediate vicinity of the cursor is essential and overrides the desire for a completely "accurate" display
- The cursor must always be visible, but if possible, a small change should be used to achieve visibility.
State
What state do we need to store in order to be able to reproduce a display that is close to the previous display?It would seem that a few locations in the document are needed. Some possibilities might be:
1. The cursor position (obviously)
2. The position of the start of the displayed portion of the document (emacs uses this)
3. The position in the cursor-line where the display line starts.
Other possibilities might include the end of the display, and the end of the line as well.
However just these points to not make it easy to maintain horiztonal stability when moving vertically in the document. For that, we probably need a length being the distance that the image has been shifted to the left.
It is worth observing that shifting the whole displayed image horizontally often gives a fairly unpleasant effect. A lot of relevant context is at the beginning of neighbouring lines and this is lost when a horizontal scroll happens. It might be more effective to just scroll the cursor line, and maybe the nearyby lines if they are also suitably long. However that is a topic for the next section.
For now, we will assume that the 'state' is the cursor position, the position of the start of the displayed portion of the document (which might not be the start of a line if it is a very long line that is wrapped), the position of the end of the displayed portion of the document, and a length of horizontal shift.
Algorithm
We need an algorithm that takes this state and generates a displayed image. It would be best if the algorithm doesn't depend too heavily on the particular choice of state, as other document structures might use other state. It would also be good if the algorithm performmed in a single pass over the document, rather than iteratively trying different possibilities until the cursor appeared properly.
It seems most general to start at the cursor position and render outwards. We generate the cursor line first, and then generate the display one line at a time moving alternately backwards and forwards.
While both display endpoints are inside the region that was previously displayed, we extend equally in both directions.
While neither display endpoint is inside the previous region it is also reasonable to extend equally in both directions as either side could give useful context, and there is no reason to suppose one direction rather than the other will encourage stability.
If one endpoint is inside the region, and the other is outside, it becomes sensible to only extend that one endpoint (the one inside), as doing so will improve stability of the display.
With this approach, if the cursor moves two lines beyond the end of the display, then the display will scroll 3 or 4 lines which is probably a good choice.
If the cursor moves a long way from the display, the result will have the cursor in the center of the display, which is also probably a good idea.
It is worth noting that with this scheme, a "move-forward-one-page" command cannot be effected by simply moving the cursor without reference to where in the page the cursor is. It would seem simplest to implement such a command by adjusting the current display state, but that does not translate well into other structures which might have very different state. Possibly each display type would need it's own 'move forward a page' implementation.
When dealing with a display mode that truncates long lines, the first step would need to generate a display for the cursor line, and adjust the horizontal shift to make sure the cursor is properly visible.
When dealing with a display mode which wrapped long lines, we would need to find the start of the cursor line and generate display from there to make sure that wrapping happened at the correct places. The result of this display generation would be cache, and individual display lines added according to the above algorithm.
