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.
|