  netrik hacker's manual
>========================<

[This file contains a description of the viewer module. See hacking.txt or
hacking.html for an overview of the manual.]

_display()_

The viewer basically consists of a keyboard dispatcher that loops until some
command was given that requires some action by main(). These include the quit
command, link following, history commands, or entering the command prompt.

display() tells main() what action is required, by the return value which is an
"enum Pager_ret".

After main() has done it's work, display() will be called again; it will then
continue just where it stopped, which is possible because the important pager
state information (postion of the currently visible page part, currently
selected link) are stored in the page structure, which is described under
_Page List Handling_ in hacking-page.*. Thus the way over main() is quite
seemless; to the user it usually looks as if he was in the pager all the time.

To allow for this, display() needs to do some work to restore the state
upon startup.

First, the page is scrolled to the previous position, using _scroll_to()_
. "old_line" is set to PAGE_INVALID (meaning the pager presently displays
nothing) before scrolling, so that the screen contents always will be drawn
completely anew.

Afterwards, if there is a selected link, it is (re)activated with
_activate_link()_ (described in hacking-links.*).

Initialization is slightly different if an anchor was activated, which is
indicated by "page->active_anchor" being set. In this case, instead of callig
scroll_to() directly, _activate_anchor()_ (also described in hacking-links.*)
is used. This one also scrolls (but to the anchor position, not the previous
pager position), and additionally draws the anchor marks.

After completing these initalizations, the keyboard input loop is entered,
which reads one character in each iteration (note that the mvgetch() fuction
used for that also updates the curses screen), and then dispatches to the
requested command in a big switch.

The commands supported presently include vertical scrolling, link selection
and following, and history operations.

_Scrolling Commands_

The scrolling commands are all implemented by simple calls to scroll_to()
with different parameters. All relative movement commands can simply add or
subtract a constant number of lines to move, as scroll_to() checks for the
page boundaries.

_scroll_to()_

This function scrolls the visible page area to the desired position. It moves
(or deletes) the screen content, and repaints newly visible areas using render()
(see hacking-layout.*).

It is called with the desired new position as argument. This position is
described by the line number, of that line of the output page which is
displayed in the first screen line.

First the desired new position is checked to be in the valid range.

If it is greater than the page length minus one screenwidth, it is set to that
value. (The last page line can't move above the bottom screen line this way.)

If smaller then zero, it is set to zero. (The page top can't move below the
screen top.) Note that this will undo the above check, if the output page
is smaller than one screenfull -- in this case, the page end is always above
the screen end.

Now that we know the real destination position, the difference to the present
position (stored in the static "old_line") is calculated.

If the absolute value of that difference is not more than a screenfull,
scrolling is used. This is done by the insdelln() curses function, called with
the cursor being in the first screen line. (Called with a negative value it
deletes lines, causing the screen contents to scroll up; called with a positive
argument, it inserts lines, causing the screen contents to scroll down.)

After scrolling, the new lines revealed at the top or bottom of the screen
need to be rendered. After scrolling down (negative "scroll_lines"), the first
"scroll_lines" of the screen have to be repainted. This is done by calling
render() with "scroll_lines" as the height, 0 as the screen position (the
first lines of the screen are repainted), and "new_line" as page position.
("new_line" contains the page position of the first screen line.)

After scrolling up, the last screen lines have to be repainted. This is
done similar. The hight is "-scroll_lines" ("scroll_lines" is negative when
scrolling up), the screen position is the screen end minus "-sroll_lines", and
the page position is the page position of the first screen line ("new_line")
plus the screen position of the rendered area.

If the difference is too big for scrolling, the whole screen is erased and
repainted.

The first time this function is called, "old_line" has the value PAGE_INVALID
(defined as the biggest possible positive int value), indicating that the
screen contains nothing valid yet. There is no special handling necessary for
this case -- "scroll_lines" gets a very big value, and thus the whole screen
is always repainted.

scroll_to() returns the adjusted "new_line", so the caller knows what is on
the screen, and can use it as the base for any successive commands.

_Link Selection Commands_

As explained under _display()_ above, the pager keeps track of the current
active link by the "active_link" variable in the "page" struct. (-1 means no
link is active.)

All link selection commands are implemented using a generic link search
function to find out which link to activate, and then just activating it with
_activate_link()_ (see hacking-links.*).

_find_link()_

Finding the link to activate is done with the find_link() function. This
function is flexible enough to implement almost all link selection commands
with only one invocation.

What link to look for is determined by a number of criteria: It is possible to
specify at which link number the target range starts, at which link number it
ends, at which line number (page coordinates) the range starts, and at which
line it ends. (Like in all places dealing with ranges, the lower bound tells
the first link or line inside the range, while the upper bound specifies the
first one *outside*, i.e. after the last one inside.)

Not all of these criteria need to be specified -- usually, only some make
sense in a certain situation. Thus it is possible to pass -1 for any of those
parameters, meaning that there is no bound to the respective parameter, i.e.
the range extends to the end of the page or link list in that direction.
find_link handles all possible combinations in a reasonable manner.

As multiple links can (and often will) match the criteria, a further parameter
is necessary: the "search_type" enum (flag) can be either FIND_FIRST or
FIND_LAST, meaning to return the first link that is inside both ranges or the
last one, respectively. (Here, "first" always means the one with the smaller
link number.)

In spite of it's great flexibility, find_link() is actually quite simple in
its implementation. The whole search is done in a single loop which scans
all links in the link number range, unless it is prior terminated becase the
result is already known.

Searching is done forwards, except when an end link is given, in which case
we search backwards -- the latter case normally means going back starting from
some specific link (typically the current active link in backwards-moving link
selection commands), so it's usually more efficient than starting scanning at
the beginning of the list, while we look for a link only a few positions back.

As only one loop is used for scanning both forward and backward, the loop
start value, end value, and increment have to be set before entering the loop,
depending on the direction. When searching forward the increment is positive,
the start value is the start of the link number range, and the end value is the
end of the link number range; searching backward needs exactly the opposite.
Note that the end value has to be matched by equality, not the smaller/greater
conditions typically used in for loops, as we can approach the end value from
both directions. (Depending on the search direction.) This makes calculating
the end value somewhat less elegant.

The links inside the link number range are cheked to be inside the line number
range one after the other in this loop. (The check for the upper bound is done
unsigned, so -1 (i.e. no bound) for the "end_line" will be treated as a very
big positive number, ensuring no link will fail this condition.) Note that
the lower bound is compared against the link's start line, while the upper
bound is compared against the end line, so multilined links will be accepted
only if they are inside the range completely.

If "search_type" is FIND_FIRST, on the first link meeting all criteria the
search is terminated. In FIND_LAST mode however the matching link is also
memorized, but we continue the scan for possible further matching links. Only
when we are already behind the "end_line" when scanning forward or before the
"start_line" when scanning backward, we know we won't enter the range anymore
and can stop scanning. (Thus keeping the last matching link encountered.)

If no link matching the criteria was found, find_link() returns -1.

_active_start() and active_end()_

Cheking whether a link is (or would be) inside the valid screen area for active
links, primarily during link selection but also in a few other places, is done
using the active_start() and active_end() functions. These simply return the
bounds of the valid area (in page coordinates, not screen coordinates!).

Normally, the area starts in the line that shows up at the screen top (i.e.
"pager_pos") plus "cfg.link_margin" -- this is one line below the screen
top with the default setting. The end is the last line on screen minus the
link margin, accordingly. (But note that active_end returns the number of
the *first invalid* line, i.e. *after* the end of the valid area, as all end
coordinates are specfied this way!)

An exception is the pager being at the top (for active_start()) or bottom
(for active_end()) of the page -- in this case the valid area includes the
first or last page lines, as these can never move into the normal valid
area. (The page top can't ever move below the screen top; same for bottom.)

The "pos" parameter allows specifying some other pager position (relative to
the current "page->pager_pos") to check; 0 means using the current position.

_Command Implementations_

The 'J' command is very simple: A single invocation of find_link() is enough
to find the new active link. The line number range spans from the first
line of the current active area to the last line of the active area after
scrolling one line. The "start_link" is the one after the current active link,
and we look for the first match, so we will always get the following link,
if any. If no link is active (page->active_link is -1), 0 will be passed
(meaning to start scanning with the first link on page), which is ok --
the first link inside the line number range (i.e. the first link on screen)
will be returned. Thus we do not need any special handling for this case.

Knowing which link to activate, we only need to call _activate_link()_ with
the new link number to activate it. (Scrolling will be done automatically in
activate_link(), if necessary.)

When there is no link to activate we just scroll one line with _scroll_to()_ .

The 'K' command works quite similar. Except for some reversed parameters
("end_link" given as current link to get the previous one, no "end_link",
"start_line" calculated for scrolling one line up), there is a major difference
however: The case of no link being active needs special handling here due
to the other behaviour -- we do not take the last link inside the active
screen area in this case, but only the last link in the new line(s) becoming
active by scrolling. (Which is the difference between the active_start()
after scrolling and the one before scrolling.)

Another difference is that due to the seperation, in the case of already
having a link we do not need to specify an "end_line" at all, as we search
from the current link backwards anyways, which is always inside the active area.

The 'H' command is quite simple: We just need to activate the first link
inside the current active screen area.

There is a special case however when some link is already active: In this case
we start scanning at this link, so we do not need to scan the whole list. Note
that here we start the scanning with the current active link itself, not the
one before as in many other commands, as the current link might as well be
already the first one on the screen.

'L' is even simpler, as we do not need the special case -- we simply always
search forwards starting with the current link; if no link is active,
"active_link" is -1 and will be passed as "start_link", which is just what
we need anyways.

'M' is also similar, but somewhat more complicated, as we do not know whether
the target link is before or after the current one.

First we scan backwards starting from the current link (but not including). If
no link is active then find_link() will get -1 as "start_link", and will
simply scan from the beginning of the list.

This first scan will fail if there was some link active (so a backward search
from that link is done), but the link was in the first screen half, so the
search will never reach the second screen half; thus, no result is returned in
this case. This condition has to be catched, and we make another scan starting
from the current link, this time forward. (And including the current one.)

The <tab> command uses find_link() to look for the next link (i.e. the first
link after the current one), with the additional condition that it has to be
after the screen start. This additional condition isn't really necessary when
there is already an active link, of course; however, it is very convinient as
this will automatically get the first link on the screen or somewhere behind
it, if there is currently no active link.

'p' is similar, but slightly more complicated, as it has to treat no active
link as a special case. Normally, it just looks for the next link without any
additional conditions. (Using find_link() here is almost overkill... But not
really, as find_link() will automatically do page bound checking etc.) If no
link is active, we look for the last link before the active screen area instead.

<backspace> just jumps to the first link with activate_link(), if there are
any links on the page.

'+' is similar to 'J' (or actually more to 'K', as it has to handle no active
link as a special case). If some link is already active, we simply look
on which line it starts, and then look for the first link in the next line
having links -- which is just the next link that starts behind the current
line. The end of the search area, just as for 'J', is the last line active
after scrolling one line, i.e. scrolling one line is allowed if there is no
link in the current valid screen area.

When no link is active, we search the same area, only starting from the
beginning of the active screen area (and no start link, of course.)

Also like in 'J', we just scroll one line forward if no link meeting the
conditions was found.

'-' is a tick more complicated. The search is done in two steps: First we look
for the last link on the previous line or before, which is symmetrical to '+'.
(With the exception that if there was no link active yet we only search the
area that *additionally* becomes active if scrolling one line, just like in
'K'.) This way we find out what is the previous line having any links.

If such a link was found, in the second step we look for the first link on
that line by another invocation of find_link(); this time searching back from
the link found in the previous search, and the search area starting in the
line where that link starts.

'^' is very simple again: It only has to find out on which line the current
active link starts (i.e. the cursor line), and then searches backwards for
the fist link that starts on that line. The search includes the current link,
as it might be already the first one. Nothing is done if there was no active
link -- there is no cursor line in this case.

'0' is more tricky (but not really more complicated) because there is no
possibility to search for a link ending on or after some specific line
directly with find_link(). Instead, we simply look for the last link that
ends *before* the current line, and then activate the next one -- which is
exactly the desired one.

If there are no links ending before the current line, find_link() will return
-1 and we will activate link 0 -- which is the first link on the page, and
thus also has to be the first one on the current line. No special handling
is necessary for this case.

'' on the other hand needs that special handling, i.e. explicitely activating
the last link on the page when nothing was found. Otherwise it is symmetrical
to '0'.

_History Commands_

The history commands all work alike:

'b' goes back to the previous page by setting the current position in the
history one backwards (decrementing). The page is then (re)loaded by quitting
the pager with "RET_HISTORY" -- main() then invokes the necessary actions for
loading the page, before restarting display().  (load_page() and init_load()
take care for correct loading of pages from history.)

'f' goes forward in the same manner (incrementing "pos").

'B' searches the history backwards, until it finds some page that is followed
either by one with the "absolute" flag set or by an internal page (the
internal page is treated like a new site). If nothing is found the search
stops at the first entry in the history, and that one is taken.

'F' searches forwards, also looking for a page followed by an absoulte URL. The
last entry is taken if nothing was found.

'^r' causes the current page to be reloaded, by returning RET_HISTORY, but
not changing the position in the page list. Thus it is not really a history
command from the user's point of view -- but technically, it is.

'r' searches backwards for a page with "mark" set. It also takes the first
page if nothing was found; 'R' does the same forwards.

The marks are set by the 's' command. This one simply sets the "mark" flag
of the current "page" structure.

_Link Following_

When a link is followed by typing <return>, "RET_LINK" is returned, and the
main program follows the current "active_link". This process is described
under _main()_ in hacking-links.*.

_URL commands_

The 'u' command causes display() to return "RET_LINK_URL"; main() then shows
the URL of the currently active link.

'c' returns "RET_URL", and main() shows the current page URL.

'U' returns "RET_ABSOLUTE_URL", causing main() to print the absolute link
target URL which is used when the link is actually followed.

_Other Commands_

When 'q' is typed the pager simply returns with "RET_QUIT", indicating that
the main program should quit also.

When ':' is typed the pager quits too, but returns "RET_COMMAND"; this means
that the main program should enter command mode.

