# $Header: /home/yew/yew5/users/phelps/spine/tkman/RCS/tkman.tcl,v 1.69 1993/11/02 22:21:16 phelps Exp phelps $

#
# Bird?  Plane?  TkMan!  (TkPerson?)
#
#   by Tom Phelps (phelps@cs.Berkeley.EDU)
#      March 24-25, 1993
#
#
# 24-Mar  hard stuff working
# 25      easy stuff finished up, including documentation
# 29      wrote Makefile and otherwise prepared for distribution
# 31      changed incremental searching
#  1-Apr  posted to comp.lang.tcl
#  2      more portable version posted
#         browsers `send' to `manShowManFound', sink manual section
#  4      SEE ALSO links filter, unpacks compressed pages
#         (both inspired by Paul Raines)
#  5      manX.[zZ] directories, whatis found in text
#  6      workaround for bind bug, sunken text (both Dan Schenck)
#         "Searching for ..." message
#  7      print: now meta-click, works with compressed ones
#         fixed bind problems
#         can type in, e.g., print.3v and print.3s; distinction saved in history
#         .so links work (thanks Dave Lemke)--
#            but don't do this with compressed pages (which HPs don't)
#         various portability/system admin (suggested by model bug reporter Lars Huttar)
#  8      memory pipe replaces tmp file
# 10      Emacs-like point and mark
#         sections loaded on demand (suggested by Lars Huttar)
#         user additions preserved in ~/.tkman 
#         quick view of first n lines
# 11      >1000 lines
#         wait cursor, tkinfo shares window with TkMan
# 12      option to preload or not, instant show update moved to bs2tk
# 13      restricted search of paths in MANPATH (anthony baxter)
# 14      case sensitivity switches to searches (anthony baxter)
# 15      tbl, eqn handled (Mark McIntosh)
# 17      incremental searching made more like Emacs, flush volume cache
#         (both suggested by Robert Wilensky)
# 19      fixed handling of compressed pages, section re-viewing much faster
# 20      solution to multiple, identically-named man pages in different directories,
#            at the expense of volume load on demand
# 25      different way to deal with multiples
# 27      hack around Tk text's wordend limitation
# 29      compressed database hack around
#         v1.3.3 released
# 30      dups menu hangs around, different hack around for compressed database
#  2-May  faster startup with database (big thanks to Frank Delahoyde)
#         SuperMan, of dubious value (suggested by Larry Virden)
#  5      demo to UCB Tcl/Tk users
#  7      (none) and (n/a) shown when pulldowns empty (Steve Smoot)
# 14      manTextOpen, manTextClose, manSectButt
#         documented: scan, pipe capability of apropos
#         apropos responsive to selected directory switches (Robert Wilensky)
# 15      more generous focus hits (Steve Smoot), tab moves focus
# 18      v1.3.4 released
# 19      v1.3.5 released (fixed bug which affected first-time users)
#         default fonts changed from pixel specification to point size (Donn Cave)
# 29      man page name searching case insensitive--search time 3->4 seconds
#         (Wayne A. Christopher)
#  1-Jun  multiple instantiations
#  2      output menu for multiple instantiations
#         changed default font to New Century Schoolbook so underscores show up
#  4      switch all man paths on or off (David Taubman)
#  7      . and .. to ~ and / (John Hagerman)
# 25      support for people with only cat directories (6 characters from Mike Beasley)
#         v1.4 released
# 28      deleted focus $w.show" at very end of proc TkMan.  otherwise if start up
#             iconified, then deiconify before done with init, text window doesn't show up.
#             It's the focus problem again.  (David Svoboda)
# 29      "update, don't quit" (Gordon Lack & Lord Wodehouse)
#  1-Jul  lengthier criteria for picking which to show from list of matches
#         (Michael Moore and Kwo-Long Lai)
# 14      option not to move change bars, apropos filter in variable (both Warren Jessop)
# 18      manKeyNav's key bindings to variables (David Sibley, thanks Wayne Christopher)
#         moved Paths, added `All' to Volumes (Neal McBurnett, friend of Scott Schwartz)
#         option to save nroff-formatted version (Warren Jessop)
# 24      margins around text box (colleague of Gordon Lack)
# 26      character to indicate popup menu (too bulky and unaesthetic, though)
# 27      keep list of man pages with dots--startup takes a couple more seconds (Warren Jessop)
#  4-Aug  check to see if man page source is older than nroff (Warren Jessop)
#         C-s C-s retrieves last search pattern, as in Emacs
#  5      multiple instantiations share menu windows--sabotaged by new requirement in Tk 3.3
#  6      each instance gets own set of variables
#         `|command' syntax for arbitrary text-generating command,
#         `<file' syntax for reading raw file
#         uses SearchBox mega widget
#  8      file name completion (if had fast database could do
#             man page name completion and spelling correction)
# 11      user annotations (first cool new idea since March!)
#            (could have set of different annotations but would that be useful?)
#            (automatically jump to first highlight?)
# 17      mandesc-like directory dance hall (clamored for by Oyvind Yrke)
#         >2000 lines, 70K characters, despite kicking generally useful code into taputils.tcl
#         decision: don't integrate tkinfo (it could use the searchbox megawidget, though)
# 20      changes to tkmandesc (Oyvind Yrke)
# 21      SGI support worked out (Paul Raines)
# 13-Sep  greater control of ordering of added (pseudo) volumes (Paul Raines)
# 15      submitted to comp.sources.testers
#         >80K characters
# 20      fixed printing of .so pages (Larry Virden)
# 21      various parts of ui disabled for different text box content types
# 28      1.5 review version available via comp.sources.testers
#  7-Oct  iconposition, -nosave, tkmangeometry (Chris Siebenmann)
# 12      reviews received from comp.sources.testers
#         C-d/C-m hot keys (Kwo-Long Lai)
#         variety of small changes suggested by the anonymous reviewers
#            removed gzip requirement for H-P
#            if main window owns X selection, zap it before showing another page
# 13      taking advantage of bs2tk's new reverse compile, do this for cat page printouts
#         show hot spot on a single click (many, Robert Wilensky first)
#         more small changes suggested by reviewers
#            killing TkMan with window manager's kill removes process
# 21      >90K characters
# 24      highlights for symbolic links resolved to physical file
# ??      1.5 released for Tcl 7.0/Tk 3.3
#

set manx(boilerplate) {\
A bird?  A plane?  TkMan!  (TkPerson?)
by Tom Phelps

based on Tcl 7.0/Tk 3.3
Compatible with Hewlett-Packard HP-UX, AT&T System V, SunOS, Sun Solaris, DEC Ultrix, SGI IRIX, Linux

Copyright © 1993  T. A. Phelps
All Rights Reserved.
University of California, Berkeley
Department of Electrical Engineering and Computer Science
Computer Science Division


The latest version of TkMan is always available by anonymous FTP at \
ftp.cs.Berkeley.EDU in the /ucb/mhgs directory.


PERMISSION IS GRANTED TO DISTRIBUTE THIS SOFTWARE FREELY, BUT ONE MAY NOT \
CHARGE FOR IT OR INCLUDE IT WITH SOFTWARE WHICH IS SOLD.

If you send me bug reports and/or suggestions for new features, include the \
versions of TkMan, Tcl, Tk, X, and UNIX, your machine and X window \
manager names, and a copy of your ~/.tkman file.  First check that values \
changed in the Makefile or source code aren't being unexpectedly \
overridden in ~/.tkman.



============
Introduction
------------

A manual page browser, TkMan offers two major advantages over xman:
hypertext links to other man pages (click on a word in the text which
corresponds to a man page, and you jump there), and better navigation
within long man pages with searches (both incremental and regular
expression) and jumps to section headers.  TkMan also offers some
convenience features, like a user-configurable list of commonly used man
pages, a one-click printout, and integration of `whatis' and `apropos'.
Further, one may highlight, as if with a yellow marker, arbitrary passages
of text in man pages and subsequently jump directly to these passages by
selecting an identifying excerpt from a pulldown menu.  Finally, TkMan
gives one control over the directory-to-menu volume mapping of man pages
with a capability similar to but superior to xman's mandesc in that rather
than forcing all who share a man directory to follow a single organization,
TkMan gives control to the individual.  In fact, one may decide he has no
use for a large set of man pages--say for instance the programmer routines
in volumes 2, 3, 4, 8--and eliminate them from his personal database.

Since man page formatting follows conventions but not rigid standards, not
all man pages can be parsed fully.  However, most yield their section
titles and SEE ALSOs and their emphasized words.  TkMan also tries to
filter out the unsightly page footers and headers put in by `nroff', but
nonstandard formatting can slip by.

First I'll describe how to use TkMan, although I hope that the use of most
features is intuitive.  At the end are my address and UC's disclaimer and
licensing information.



===========
Using TkMan
-----------

= Locating a man page =

There are several ways to identify the manual page you desire.  You can
type its name into the entry box at the top of the screen and press return
or click `man'.  The name may be just the name of the command or may
include a `.n' or `(n)' at the end where `n' specifies in which section to
look.  Man pages are matched using file name globbing (as in `csh'), so you
can use `?' to match any single character, `*' to match any (zero or more)
characters, `[' .. `]' to match any single character in the enclosed class,
and `{' .. `}' to expand the enclosed strings.  If you're running TkMan
from a shell and giving it an initial man page name to load up as an
argument, use this syntax (adequately quoted for protection from the
shell), as opposed to the syntax of the standard `man' command.  Usually
TkMan searches the directories in your MANPATH environment variable for the
man page, but you may instead provide a path name for the man page by
beginning it with `~', `/', `.' or `..'; this is the way to access a man
page which isn't installed in a MANPATH man directory.  Further, other Tcl
interpreters may display a man page in TkMan by `send'ing a message to the
function `manShowMan' with the name of the desired man page, for example
`send tkman manShowMan tcl.n'.  If multiple man page names match the
specification, the first match (as searched for in MANPATH order) is shown
and a pulldown menu appears which contains a list of the other matches.

`apropos' information is available by typing the name and clicking
`apropos' or hitting meta-return (for meta information, of course).  The
output of `apropos' is piped through `sort' and `uniq' to remove
duplicates.  To pass the matches through another filter, simply give the
pipe as in a shell, e.g., `search | grep ^g' (each space character is
significant) returns all the search-related commands which begin with the
letter `g'.

The `Paths' pulldown gives you complete control over which directories of
your MANPATH are searched for man pages and apropos information.  You can
call up a listing of all man pages in a volume through the `Volumes'
pulldown menu and then select one to view by double-clicking on its name.
Typing a letter jumps to the line in the listing starting with that letter
(capital and lower case letters are distinct).  The `all' pseudo volume can
be useful when used with `Paths' to obtain a complete listing of man pages
in, say, one directory tree.

Once you have a man page name in the text display box, whether from
apropos, a volume listing or a reference within another man page, you can
double-click on it to hypertext-jump to it.

The last few man pages you looked at can be accessed directly through the
`History' pulldown menu.  `Shortcuts' lists your personal favorites and is
used just like `History', with the additional options of adding (by
clicking `+') the current man page or removing (`-') it from the list.

(Man pages specified as above are processed through an `nroff' filter.
TkMan can also read raw text from a file or from a command pipeline, which
can then be read, searched and highlighted same as a man page.  To read
from a file, make the first character in the name a `<', as in
`<~/foo.txt'.  To open a pipe, make the first character a `|' (vertical
bar), as in `|gzcat foo.txt.gz' or `|cat ../foo.txt | grep bar' (that's no
space after the first `|', a space before and after any subsequent ones).
After reading a file, the current directory is set to its directory.
Commands are not processed by a shell, but the metacharacters `.', `..',
`~' and `$' (for environment variables), are expanded nonetheless.  Typing
is eased further by file name completion, bound to the escape key.  Lone
files (i.e., not part of a pipe) are automatically uncompressed--no need to
read compressed files through a `zcat' pipe.  It is not expected that
reading raw text will be done much; it is included so the occasional
non-man page documentation may be read from the same environment.
For more sophisticated file browsing, use NBT, my Tcl/Tk-based file
browser, which available from TkMan's home FTP site, mentioned above.)


= Working within a man page =

The `whatis' information for a man page, if any, appears at the top of the
screen.

To the extent it follows convention, the man page is parsed to yield its
section and subsection titles (which are directly available from the
`Sections' pulldown) and references to other man pages from its SEE ALSO
section (`Links' pulldown).  One may jump directly to a section within a
man page or a man page referenced in the SEE ALSO section, respectively, by
selecting the corresponding entry from one of these pulldowns.  It may be
handy to tear off the `Sections' and `Links' menus (by dragging the menu
title with mouse button 2 pressed).

Within a man page or raw text file or pipe, you may added ad hoc
highlighting, as though with a yellow marker (underlining on monochrome
monitors).  Highlighted regions may then be scrolled to directly through
the `Highlights' pulldown menu.  To highlight a region, select the desired
text by clicking button 1, dragging to the far extent of the desired
region, releasing the button, then clicking on the `+' next to
`Highlights'.  To remove any highlights or portions thereof in a region,
select it as before but then click on `-'.  Highlighting information is
persistent across executions of TkMan.

You can move about the man page by using the scrollbar, or typing a number
of key combinations familiar to Emacs aficionados.  Space or C-v page down,
and delete or M-v page up.  C-n and C-p scroll up and down,
respectively, by a single line.  M-< goes to the head and M-> to the tail
of the text.  One may "scan" the page by dragging up and down with the
middle mouse button pressed.  Like Emacs, C-space will mark one's current
location, which can be returned to later with C-x, which exchanges the
then-current position with the saved mark; a second C-x swaps back.

C-s initiates a search.  Subsequently typing a few letters attempts to find
a line with that string, starting its search with the topmost line
currently visible.  A second C-s finds the next match of the string typed
so far.  (If the current search string is empty, a second C-s retrieves the
previous search pattern.) C-r is similar to C-s but searches backwards.
Escape or C-g cancels the search.  This incremental search can be used to
quickly locate a particular command-line option or a particular command in
a group (as in `csh').  At the bottom of the screen, type in a regular
expression to search for and hit return or click `Search' to begin a
search.  Hit `Next' or keep hitting return to search for the next
occurance.  [`Prev' will be added when Tk supports a `tag prevrange'
command.]
#  If there is an X selection (in any application), clicking on
#`Search' will replace the current search text with the X selection and
#initiate a search for it.  If there is no selection or if the search is
#initiated from the keyboard in the entry window, the current search text is
#observed.

The tab key moves the focus from the man page type-in line to the text view
of the man page to the search line and back around.


= Other commands =

The `Occasionals' menu holds commands and options which you probably won't
use much.  The first group in this menu is comprised of commands which you
may invoke several times in a single TkMan session.  `Help' returns to this
information screen.  Although virtually made obsolete by TkMan, `Print'
makes a copy of the current man page on dead trees, helping to starve the
planet of life-giving oxygen.  (If the troff source is not available, TkMan
asks if it should try to reverse compile the man page.  If successful, this
produces much more appealing output than a straight ASCII dump.)  By
default, incremental searching is not case sensitive, but regular
expression searching is; these settings can be toggled with the next two
menu checkboxes.  With proportional fonts giving a ragged right, any change
bars in the right margin will form an uneven line; by opting for
`Changebars on left', they will form a straight line at the left margin.
If you install new manual pages, invoking `Rebuild Database' will permit
them to show up the next time that volume list is shown and be found in the
next search without the bother of re-executing TkMan.  (If you want to add
paths to your MANPATH, or edit ~/.tkman, you will have to restart to see
them take effect, however.)  Usually TkMan saves its persistent variables,
only and always, when exited with the `Quit' button.  One may guard against
losing highlighting, shortcuts and other what-should-be persistent
information by checkpointing the current state with the "Update .tkman,
don't quit" button.  "Quit, don't update" performs the opposite operation,
obviously.

Like `xman' one may instantiate multiple viewers.  When there is more than
one viewer you may choose man pages in one viewer and have their contents
shown in another.  Use the `Output' pulldown (which appears and disappears
as relevant) to direct one viewer's output destination to another.  With
this feature one may easily compare two similar man pages for differences,
keep one man page always visible, or examine several man pages from a
particular volume listing or a SEE ALSO section.  `Output' only affects the
display destination of man pages.

You will probably only use commands in the next two clusters of
`Occasionals' once or twice and leave them set for all executions of TkMan.
Choose between seeing a man page's `whatis' information and the full path
name of the found file with the `Show Path of Found Man' switch.  Until
`wish' has an option to startup iconified, TkMan has a checkbox for that.
Subsection parsing doesn't work for all varieties of man page macros
formatting, but it can usefully augment the Sections listing for long man
pages.  At the factory, TkMan is set to format the contents of man volumes
on demand, which entails a little wait the first time that volume is shown,
as opposed to waiting for all volumes to be loaded at startup but no
waiting thereafter; the `Preformat Volumes' switch chooses between these.
If a man page has not been formatted by `nroff', TkMan must first pipe the
source text through `nroff'.  By turing on `Save on nroff', the
`nroff'-formatted text is saved to disk (if possible), thereby eliminating
this time-consuming step the next time the man page is read.

At the bottom right corner of the screen, `Mono' toggles between the
proportionally-spaced font and a monospaced one, for use in those man pages
that rely on a constant-width font to align columns [when Tk supports tabs
better, the need for this will diminish].  `Quit' exits TkMan, of course,
after saving some status information (see below).  To exit without saving
status information, select the `Quit' option from the `Occasionals'
pulldown.



=================
Customizing TkMan
-----------------

There are three levels of configuration to TkMan.

(1) Transparent.  Simply use TkMan and it will remember your window size
and placement and short cuts (if you quit out of TkMan via the `Quit'
button).


(2) Configuration file.  Most interesting persistent information, like the
command(s) used to print the man page, the fonts used, and some key
bindings, can be changed by editing one's own ~/.tkman.  Thus, a single
copy of TkMan (i.e., the executable `tkman') can be shared, but each user
can have his own customized setup.  (The file ~/.tkman is created/rewritten
every time one quits TkMan via the `Quit' button in the lower right corner.
Thus, to get a ~/.tkman to edit, first run and quit TkMan.  Do not create
one from scratch as it will not have the proper format used for saving
other persistent information, and your work will be overwritten, which is
to say lost.)

Most persistent information can be changed in the obvious way.  For
example, to change the means of highlighting from a light yellow background
to an underline (to display better on a monochrome monitor), EDIT (change
in place)
`set man(show,highlight) {-background #ffd8ffffb332}'
to
`set man(show,highlight) {-underline yes}'
#I find that dark text is easier to read:
#`set man(show,fontcolor) black'

To change colors append `option' commands to ~/.tkman.  Alternatively,
TkMan also works with standard X11 resources.  For instance, this is how I
set my foreground and background colors (X11 resource lines should be
placed in one's `.Xdefaults' file, not in ~/.tkman):

*TkMan*Foreground: SlateGray4
*TkMan*Background: beige
*TkMan*Text.foreground: black

Additional useful commands include `wm', which deals with the window
manager; and `bind', which changes keyboard and mouse bindings not related
to the text display window.  For example, to set an icon image for TkMan,
add this line to the end of ~/.tkman:

`wm iconbitmap .man @/some/directory/TkMan.xpm'


(3) Source code.  Of course, but if you make generally useful changes or
have suggestions for some, please report them back to me so I may share the
wealth with the next release.


= Command line options =

`-title <title>'
Place `<title>' in the window's title bar.

`-tkmangeometry <geometry>'
Specify the geometry for this invocation only.  To assign a persistent
geometry, start up TkMan, size and place the window as desired, then (this
is important) quit via the `Quit' button in the lower right corner.
(Unfortunately, the option name `-geometry' is eaten by `wish' before
having a chance to be processed by TkMan.)

#`-foreground' or '-fg'
#`-background' or `-bg'
#Set the foreground and background colors of all windows.  (You can also do
#this with X resources.)
#
`-iconify' and `--iconify'
Start up iconified or uniconified (the default), respectively.

`-iconname <name>'
Use `<name>' in place of the uniconified window's title for the icon name.

`-iconbitmap <bitmap-path>' and `-iconmask <bitmap-path>'
Specify the icon bitmap and its mask.

`-iconposition <geometry>'
Place the icon with the given geometry; "" cancels any such hints to the
window manager.

`-startup <filename>'
Use `<filename>' in place of ~/.tkman as the startup file; "" dictates
no startup file.

`-quit save' and `-quit nosave'
Specify that the startup file (usually ~/.tkman) should be updated (`save')
or not (`nosave') when quitting by the `Quit' button.

`-v'
Show the current version of TkMan and exit.

`-M <path-list>'
`-M+ <path-list>'
`-+M <path-list>'
As with `man', change the search path for manual pages to the given
colon-separated list of directory subtrees.  `-M+' appends and `-+M'
prepends these directories to the current list.


= Key bindings =

Key bindings related to the text display box are kept in the `sb' array in
~/.tkman (see the Tcl documentation for more information on Tcl arrays).
In editing the `sb(key,...)' keyboard bindings, modifiers MUST be listed in
the following order: `M' (for meta), `C' (control), `A' (alt), `S' (shift).
For instance, `set sb(key,MS-less) pagestart' is a valid binding, whereas
`set sb(key,SM-less)' is not.  To make a binding without a modifier key,
precede the character by `-', as in `set sb(key,-space) pagedown'.  If you
have NBT and would like to share key bindings between them, save the
keybindings in their own file, then `source' this file at the bottom of
~/.tkman.


= tkmandesc =

Like `xman', TkMan gives you directory-by-directory control over named
volume contents.  Unlike and superior to `xman', however, each individual
controls directory-to-volume placement, rather than facing a single
specification for each directory tree that must be observed by all.

By default a matrix is created by taking the product of directories in the
MANPATH crossed with volume names, with the yield of each volume containing
all the corresponding subdirectories in the MANPATH.  By adding Tcl
commands to your ~/.tkman (see above), you may add new volume names and
add, move, copy and delete directories to/from/among directories.

The interface to this functionality takes the form of Tcl commands, so you
may need to learn Tcl--particularly the sections on Tcl lists and, perhaps,
iteration (`foreach' or `for')--to use this facility to its fullest.

Directory titles and SINGLE LETTER abbrevations are kept in lists.  Letters
MUST be unique (capital letters are distinct from lower case), but need not
correspond to actual directories.  In fact, volume letters specified here
supercede the defaults in identifying a volume in man page searches.


COMMANDS

These commands are appended to the file ~/.tkman (see Customizing TkMan,
above).

To set absolutely the volume names for which all directories should be
searched, EDIT the parallel arrays on these EXISTING lines:
`set man(manList) ...'
`set man(manTitleList) ...'

To recreate a cross product of current section lists:
`manDescDefaults'

To add "pseudo" sections to default/current volume name list, at the
end of the list, in alphabetical order, or before or after a specific volume:
`manDescAddSects <list of <letter, title pairs>>'
or `manDescAddSects <list of <letter, title pairs>> sort'
or `manDescAddSects <list of <letter, title pairs>> before <sect-letter>'
or `manDescAddSects <list of <letter, title pairs>> after <sect-letter>'

To move/copy/delete/add directories:
`manDescMove <from-list> <to-list> <dir-patterns-list>'
`manDescCopy <from-list> <to-list> <dir-patterns-list>'
`manDescDelete <from-list> <dir-patterns-list>'
`manDescAdd <to-list> <dir-list>'

The `<dir-patterns-list>' uses the same meta characters as man page searching
(see above).  It is matched against MANPATH directories with volume
subdirectory appended, as in `/usr/man/man3'.  `<from-list>' and
`<to-list>' are Tcl lists of the unique single letter volume names; `*' is
an abbreviation for all volumes.

Warning: Moving directories from their natural home slightly impairs
searching speed when following a reference within a man page.  For
instance, say you've moved man pages for X Windows subroutines from their
natural home in volume 3 to their own volume called "X".  Following a
reference in `XButtonEvent' to `XAnyEvent(3X11)' first searches volume 3;
not finding it, TkMan searches all volumes and finally finds it in volume
X.  With no hint to look in volume 3 (as given by the `3X11' suffix), the
full volume search would have begun straight away.  (Had you double-clicked
in the volume listing for volume X or specified the man page as
`XButtonEvent.X', volume X would have been searched first, successfully.)

To help debug tkmandesc scripts, you may invoke `manDescShow' to dump to
stdout the current correspondence of directories to volumes names.


EXAMPLES

(1) To collect together all man pages in default volumes 2 and 3 in all
directories into a volume called "Programmer Subroutines", add these lines
to the tail of ~/.tkman:

`manDescAddSects {{p "Programmer Subroutines"}}'
`manDescMove {2 3} p *'

To place the new section at the same position in the volume pulldown list
as volumes 2 and 3:
`manDescAddSects {{p "Programmer Subroutines"}} after 2'
`manDescMove {2 3} p *'

To move only a selected set of directories:
`manDescAddSects {{p "Programmer Subroutines"}}'
`manDescMove * p {/usr/man/man2 /usr/local/man/man3}'


(2) To have a separate volume with all of your and a friend's personal man
pages, keeping with a duplicate in their default locations:

`manDescAddSects {{t "Man Pages du Tom"} {b "Betty Page(s)"}}'
`manDescCopy *phelps* t *'
`manDescCopy *page* t *'


(3) To collect the X windows man pages into two sections of their own, one
for programmer subroutines and another for the others.

`manDescAddSects {{x "X Windows"}} after 1'
`manDescAddSects {{X "X Subroutines"}} after 3'
`manDescMove * x *X11*'
`manDescMove x X *3'

We must name the X Subroutines volume "X", not "x" or "xsub" as the former
would duplicate the letter used for other X man pages and the latter is a
string of multiple letters.


(4) If you never use the programmer subroutines, why not
save time and memory by not reading them into the database?

`manDescDelete * {*[2348]}; # braces prevent Tcl from trying to execute [2348]'


Alternatively but not equivalently:
`manDescDelete {2 3 4 8} *'


= tkmandesc vs. xman and SGI =

TkMan's `tkmandesc' capability is patterned after xman's mandesc files.  By
placing a mandesc file at the root of a man page directory tree, one may
create pseudo volumes and move and copy subdirectories into them.  Silicon
Graphics has modified xman so that simply by creating a subdirectory in a
regular man subdirectory one creates a new volume.  This is evil.  It
violates the individual user's rights to arrange the directory-volume
mapping as he pleases, as the mandesc file or subdirectory that
spontaneously creates a volume is set a single place and must be observed
by all who read that directory.  By contrast, TkMan places the
directory-to-volume mapping control in an individual's own ~/.tkman file.
This gives the individual complete control and inflicts no pogrom on
others who share man page directories.  Therefore, mandesc files are not
supported in any way by TkMan.

One may still share custom setups, however, by sharing the relevant lines
of ~/.tkman.  In fact, a tkmandesc version of the standard SGI man page
directory setup is included in the `contrib' directory of the TkMan
distribution.  For assistance with SGI-specific directory manipulation,
contact Paul Raines (raines@bohr.physics.upenn.edu).



=========
Addresses
---------

Tom Phelps
University of California, Berkeley
Computer Science Division
571 Evans Hall
Berkeley, CA  94720
USA

(510) 642-8155
phelps@cs.Berkeley.EDU



==========
Disclaimer
----------

Permission to use, copy, modify, and distribute this software and its
documentation for educational, research and non-profit purposes, 
without fee, and without a written agreement is hereby granted, 
provided that the above copyright notice and the following three 
paragraphs appear in all copies.  

Permission to incorporate this software into commercial products may 
be obtained from the Office of Technology Licensing, 2150 Shattuck 
Avenue, Suite 510, Berkeley, CA  94704. 

IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY \
FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES \
ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF \
THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF \
SUCH DAMAGE.

THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, \
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF \
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE \
PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF \
CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, \
ENHANCEMENTS, OR MODIFICATIONS.


$Date: 1993/11/02 22:21:16 $
}


###
### proc's (only--no variables, proc calls)
###

wm withdraw .; update idletasks


#--------------------------------------------------
#
# TkMan -- make the gui
#
#--------------------------------------------------

proc TkMan {} {
   global man manx env winout

   # determine instance name
   if {$manx(uid)==1} {
      set dup 0
      # .man is guaranteed to exist
      set w .man
      # always aborts without saving .tkman
      bind $w <Destroy> "after 1 bind $w <Destroy> {}; after 2 exit 0"
   } else {
      set dup 1
      set w .man$manx(uid)
      toplevel $w -class TkMan
   }

   # initialize per-instance variables
   set manx(man$w) ""
   set manx(manfull$w) ""
   set manx(catfull$w) ""
   set manx(name$w) ""
   set manx(num$w) ""
   set winout(lastMessage$w.info) ""
   set manx(hv$w) [set manx(oldmode$w) [set manx(mode$w) help]]
   # everybody shares same icons
   foreach i {iconbitmap iconmask} {
      if {$manx($i)!=""} {wm $i $w @$manx($i)}
   }


   # make the gui
   wm minsize $w 200 200

   if {!$dup} {
      wm geometry $w $man(geom)
      wm geometry $w $manx(geom)
      wm title $w $manx(title)
      wm iconname $w $manx(iconname)
      if {$manx(iconify)} {
         # map window, so it exists, so can create database (too bad about the flash)
         update idletasks
         wm iconify $w
      }
   } else {
      wm title $w "$manx(title) - #$manx(uid)"
      wm iconname $w "$manx(iconname) - $manx(uid)"
      wm geometry $w [lfirst [split $man(geom) +]]
      wm geometry $w [lfirst [split $manx(geom) +]]
#      wm group $w .man
   }


   ### information bar
   label $w.info -anchor w

   ### man or section or texinfo
   frame $w.kind
   button $w.man -text "man" -command "manShowMan \$manx(typein$w) {} $w"
   bind $w.man <Shift-Button-1> "manSuperMan $w \$manx(typein$w)"
   bind $w.man <Meta-Button-1> "manSuperMan $w \$manx(typein$w)"
   button $w.apropos -text "apropos" -command "manApropos $w \$manx(typein$w)"
   entry $w.mantypein -relief sunken -textvariable manx(typein$w)
   emacsbind $w.mantypein
   bind $w.mantypein <Shift-KeyPress-Return> "manSuperMan $w \$manx(typein$w)"
   bind $w.mantypein <KeyPress-Return> "$w.man invoke"
   bind $w.mantypein <KeyPress-Escape> "
      if \[regexp {^\[<|.~/$\]} \$manx(typein$w)\] {manFilecomplete $w}
   "
   # ha! meta information
   bind $w.mantypein <Mod1-KeyPress-Return> "$w.apropos invoke"
   menubutton $w.dups -text "\337" -font $man(symbolfont) -menu $w.dups.m -relief raised
   menu $w.dups.m

   pack $w.man -in $w.kind -side left -padx 2 -anchor e
   pack $w.apropos -in $w.kind -side left -padx 4
   pack $w.mantypein -in $w.kind -side left -ipadx 5 -anchor w

   menubutton $w.paths -text "Paths$man(popupchar)" -relief raised -menu $w.paths.m
   menu $w.paths.m
   if {[llength $manx(paths)]>2} {
      $w.paths.m add command -label "All Paths On" -command {
         foreach i $manx(paths) {set man($i) 1}
         foreach i $manx(manList) {set mani($i,form) ""}
         manResetEnv
      }
      $w.paths.m add command -label "All Paths Off" -command {
         foreach i $manx(paths) {set man($i) 0}
         foreach i $manx(manList) {set mani($i,form) ""}
         manResetEnv
      }
      $w.paths.m add command -label "Save Paths Selections" -command {
         set manx(pathstat) ""
         foreach i $manx(paths) {lappend manx(pathstat) $man($i)}
      }
      $w.paths.m add command -label "Restore Paths Selections" -command {
         set ctr 0
         foreach i $manx(paths) {set man($i) [lindex $manx(pathstat) $ctr]; incr ctr}
         foreach i $manx(manList) {set mani($i,form) ""}
         manResetEnv
      }
      $w.paths.m add separator
   }
   foreach i $manx(paths) {
      $w.paths.m add checkbutton -label $i -variable man($i) -command {
         foreach i $manx(manList) {set mani($i,form) ""}
         manResetEnv
      }
   }

   menubutton $w.vols -text "Volumes$man(popupchar)" -relief raised -menu $w.vols.m
   menu $w.vols.m
   set ctr 0
   foreach i $manx(manList) {
      $w.vols.m add command -label "($i) [lindex $manx(manTitleList) $ctr]" \
         -command "manShowSection $w $i"
      incr ctr
   }
   label $w.volnow

   pack $w.paths -in $w.kind -side left -padx 4
   pack $w.vols $w.volnow -in $w.kind -side left


   ### navigation
   frame $w.nav
   menubutton $w.sections -text "Sections$man(popupchar)" -relief raised -menu $w.sections.m
   menu $w.sections.m
   menubutton $w.links -text "Links$man(popupchar)" -relief raised -menu $w.links.m
   menu $w.links.m

   frame $w.hlf
   menubutton $w.high -text "Highlights" -relief raised -menu $w.high.m
   menu $w.high.m
   button $w.hadd -text "+" -padx 4 -command "
      if \[llength \[$w.show tag nextrange sel 1.0\]\]==0 {
         winstderr $w.info {Select a range of characters to highlight.}
         return
      }
      $w.show tag add highlight sel.first sel.last
      selection clear $w.show
      manHighlights $w
   "
   button $w.hsub -text "-" -padx 4 -command "
      if \[llength \[$w.show tag nextrange sel 1.0\]\]==0 {
         winstderr $w.info {Select a range of characters to unhighlight.}
         return
      }
      $w.show tag remove highlight sel.first sel.last
      selection clear $w.show
      manHighlights $w
   "
   pack $w.high $w.hadd $w.hsub -in $w.hlf -side left


   frame $w.scf
   menubutton $w.shortcuts -text "Shortcuts$man(popupchar)" -relief raised -menu $w.shortcuts.m
   menu $w.shortcuts.m
   button $w.sadd -text "+" -padx 4 -command "manShortcuts $w add"
   button $w.ssub -text "-" -padx 4 -command "manShortcuts $w sub"
   pack $w.shortcuts $w.sadd $w.ssub -in $w.scf -side left
   manShortcuts $w init


   menubutton $w.history -text "History$man(popupchar)" -relief raised -menu $w.history.m
   set manx(history$w) ""
   menu $w.history.m
   $w.history.m add command -label "(none)"


   menubutton $w.output -text "Output$man(popupchar)" -menu $w.output.m -relief raised
   menu $w.output.m
   set manx(out$w) $w


   ### commands I don't use much
   menubutton $w.occ -text "Occasionals$man(popupchar)" -relief raised -menu $w.occ.m
   menu $w.occ.m
   $w.occ.m add command -label "Help" -command "manHelp $w"
   $w.occ.m add command -label Print -command "manPrint $w"
   $w.occ.m add checkbutton -label "Incremental Search Case Sensitive" \
      -variable man(incr,case)
   $w.occ.m add checkbutton -label "Regexp Search Case Sensitive" \
      -variable man(regexp,case)
   $w.occ.m add checkbutton -label "Changebars on Left" -variable man(changeleft) \
      -onvalue "-c" -offvalue ""
   $w.occ.m add command -label "Instantiate New View" -command {
      incr manx(uid)
      incr manx(outcnt)
      TkMan
      manOutput
   }
   $w.occ.m add command -label "Rebuild Database" -command "manReadSects $w"
   $w.occ.m add command -label "Update .tkman, don't quit" \
       -command "manSave; winstdout $w.info {[bolg $manx(startup) ~] updated}"
   $w.occ.m add command -label "Quit, don't update .tkman" -command "destroy ."
#   $w.occ.m add command -label "Quit" -accelerator M-q \
#      -command "manSave"

   $w.occ.m add separator
   $w.occ.m add checkbutton -label "Show Path of Found Man" -variable man(whatwhere)
   $w.occ.m add checkbutton -label "Startup Iconified" -variable man(iconify)
   $w.occ.m add checkbutton -label "Parse Subsections" -variable man(subsect) \
      -onvalue -b -offvalue ""
   $w.occ.m add checkbutton -label "Preformat Volumes" -variable man(preformat)
   $w.occ.m add checkbutton -label "Save on nroff" -variable man(savenroff)
   $w.occ.m add checkbutton -label "Show header & footer at bottom" -variable man(headfoot) \
      -onvalue -k -offvalue ""

   foreach i {sections hlf links history scf occ} {pack $w.$i -in $w.nav -side left}
   foreach i {hlf history occ} {pack configure $w.$i -padx 6}
   pack configure $w.links -expand yes -anchor e
   pack configure $w.scf -expand yes -anchor w
#   tk_menuBar $w $w.paths $w.vols \
#      $w.sections $w.high $w.links $w.history $w.shortcuts $w.occ


   ### view box
   frame $w.vf
   text $w.show -font $man(show,font) \
      -relief sunken -borderwidth 2 -padx $man(textboxmargin) -pady $man(textboxmargin) \
      -yscrollcommand "$w.v set" -exportselection yes -wrap word -cursor $manx(cursor) \
      -height 10 -width 5

   # maybe make all these "-opt val" lists so can change more than font
   eval $w.show tag configure highlight $man(show,highlight)
   $w.show tag configure title -font $man(show,bold)
#   $w.show tag configure htitle -background LightGray -borderwidth 3 -relief raised
   $w.show tag configure bold -font $man(show,bold)
   $w.show tag configure symbol -font $man(show,symbol)
   $w.show tag configure high -font $man(show,italic)
   $w.show tag configure mono -font $man(show,keyword)
   $w.show tag configure big -font $man(show,bigbold)
   $w.show tag configure sc -font $man(show,smallcaps)
   $w.show tag configure bi -font $man(show,bolditalic)
   eval $w.show tag configure hot $man(show,hot)
   $w.show tag configure help -underline yes
   $w.show tag configure search \
      -foreground [lindex [$w.show configure -background] 4] \
      -background [lindex [$w.show configure -foreground] 4]
   # bind letters to jump to that part of list
   bind $w.show <Any-KeyPress> "manKeyNav $w \[key_state2mnemon %s\] %K"
   bind $w.show <Control-KeyPress-d> "manShowSection $w \$manx(lastvol)"
   bind $w.show <Control-KeyPress-m> "manShowMan \$manx(lastman) {} $w"
   scrollbar $w.v -orient vertical -command "$w.show yview"
   pack $w.v -in $w.vf -side $man(scrollbarside) -fill y
   pack $w.show -in $w.vf -side $man(scrollbarside) -fill both -expand yes

   bind $w.show <Any-Button-1> "set text(b1-time) %t; set text(b1-x) %x; [bind Text <Button-1>]"
   bind $w.show <Any-ButtonRelease-1> "[bind Text <ButtonRelease-1>]
      if {\[expr %t-\$text(b1-time)\]<500 && \[expr %x-\$text(b1-x)\]<3} {
         manHotSpot show %W @%x,%y
         catch {if {\[string trim \[set tmp \[$w.show get hot.first hot.last\]\]\]!={}} {set manx(typein$w) \$tmp}}
      }
   "
   bind $w.show <Meta-Double-Button-1> "
      if {\[expr %t-\$text(b1-time)\]<500 && \[expr %x-\$text(b1-x)\]<3} {
         set text(b1-time) \[expr \$text(b1-time)-500\]
         manShowMan <\[manHotSpot get %W @%x,%y\] $w
      }
   "


   ### search (uses searchbox--wow, code reuse!)
   frame $w.search
   button $w.search.s -text "Search" -command "
#      if !\[catch {selection get}\] {set manx(search,string$w) \[selection get\]}
      winstdout $w.info \"Searching for regular expression \\\"\$manx(search,string$w)\\\" ...\"; update idletasks
      searchboxSearch \$manx(search,string$w) 1 $man(regexp,case) search $w.show $w.v $w.info $w.search.cnt
   "
   button $w.search.n -text "Next" \
      -command "searchboxNext search $w.show $w.v $w.info"
   label $w.search.cnt
   entry $w.search.t -relief sunken -textvariable manx(search,string$w)
   emacsbind $w.search.t
   set manx(search,oldstring$w) ""
   bind $w.search.t <KeyPress-Return> "
      if {\$manx(search,oldstring$w)!=\$manx(search,string$w)} {
         set manx(search,oldstring$w) \$manx(search,string$w)
         $w.search.s invoke
      } else {$w.search.n invoke}"
   pack $w.search.s -side left
   pack $w.search.n -side left -padx 6
   pack $w.search.t -side left -fill x -expand yes -ipadx 10 -anchor w
   pack $w.search.cnt -side left

   bind $w.show <KeyPress-slash> "focus $w.search.t"

   ### font
   # mostly mono (Mozart?)
   checkbutton $w.mono -text "Mono" -variable man(show,font) \
      -onvalue $man(show,mono) -offvalue $man(show,pro) \
      -font $manx(gui,monofont) \
      -command "
         $w.show configure -font \$man(show,font)
#         $w.show tag configure high -underline \
#            \[expr 1-\[lfifth \[$w.show tag configure high -underline\]\]\]
      "

   ### quit
   button $w.quit -text "Quit" -command "manSave; destroy ." -padx 4
   if {!$manx(quit)} {$w.quit configure -command "destroy ."}
   if {$dup} {$w.quit configure -text "Close" -command "destroy $w; incr manx(outcnt) -1; manOutput"}
#   bind $w.quit <Button-1> "[bind Button <Button-1>]; manSave"
#   bind $w.quit <Meta-Button-1> "[bind Button <Button-1>]; destroy ."
   bind all <Meta-KeyPress-q> "$w.quit invoke"

   pack $w.mono -in $w.search -side left -padx 3 -anchor e
   pack $w.quit -in $w.search -side left -padx 3


   pack $w.info $w.kind -fill x -pady 4
   pack $w.nav -fill x -pady 6
   pack $w.vf -fill both -expand yes
   pack $w.search -fill x -pady 6


   # generous hit regions, tab between
   foreach i {info kind nav} {bind $w.$i <Enter> "focus $w.mantypein"}
   foreach i {vf show v} {bind $w.$i <Enter> "focus $w.show"}
   bind $w.search <Enter> "focus $w.search.t"
   foreach i {mantypein show search.t} {
      bind $w.$i <KeyPress-Tab> "manTab $w"
# bug in Tk bind: below command binds Alt-KeyPress-Tab to KeyPress-Tab's binding
      bind $w.$i <Meta-KeyPress-Tab> "manTab $w -1"
      bind $w.$i <Shift-KeyPress-Tab> "manTab $w -1"
   }

   manHelp $w
   update idletasks
}



#--------------------------------------------------
#
# Shortcuts
#
#--------------------------------------------------

proc manShortcuts {w cmd} {
   global man manx

   set mode $manx(mode$w)
   if {$cmd!="init" && (($mode!="man"&&$mode!="txt") || $manx(man$w)=="")} return
   set n $manx(name$w); set p $manx(man$w)

   set index [lsearch $man(shortcuts) $p]
   if {$cmd=="add" && $index==-1} {lappend man(shortcuts) $p} \
   elseif {$cmd=="sub" && $index!=-1} {set man(shortcuts) [lfilter $p $man(shortcuts)]}

   # for each instantiation, update shortcuts list
   foreach w [winfo children .] {
      $w.shortcuts.m delete 0 last
      if {[llength $man(shortcuts)]} {
         foreach i $man(shortcuts) {
            if {![regexp {^[<|]} $i]} {set name [file rootname [file rootname $i]]} {set name $i}
            $w.shortcuts.m add command \
               -label $name -command "manShowMan [list $i] {} $w"
         }
      } else {
         $w.shortcuts.m add command -label "(none)"
      }
   }
}


# compensate for Tk's nonalphnum-bounded wordstart and wordend
# should have companions to wordstart that are bounded by whitespace, not nonalpha

proc manHotSpot {cmd w xy} {
   set manchars {[a-z0-9_.~/$+-]}

   # click again in hot spot without time considerations
#   if {!catch{$w index hot.first} && [$w compare hot.first <= $xy] && [$w compare $xy >= hot.last]} {
#      set cmd get
#   }

   # act on command
   if {$cmd=="get"} {
      return [$w get hot.first hot.last]
   }

   # else $cmd=="show"
   $w tag remove hot 1.0 end

   scan [$w index $xy] "%d.%d" line char
   set c0 $char; set cn $char; set cs $char
   scan [$w index "$line.$char lineend"] "%d.%d" bozo lineend

   # back
   while {$c0>=0 && [regexp -nocase $manchars [$w get $line.$c0]]} {incr c0 -1}
   incr c0
   # forward
   while {$cn<=$lineend && [regexp -nocase $manchars [$w get $line.$cn]]} {incr cn}
   incr cn -1

   # gobble whitespace and check for (xxx) for man section #
   for {set cs [expr $cn+1]} {$cs<=$lineend && [$w get $line.$cs]==" "} {incr cs} {}
   if {$cs<=$lineend && [$w get $line.$cs]=="("} {
      incr cs
      while {$cs<=$lineend && [regexp {[^) ]} [$w get $line.$cs]]} {incr cs}
      if {$cs<=$lineend && [$w get $line.$cs]==")"} {set cn $cs}
#puts -nonewline "[$w get $line.$cs]<=$lineend   "
   }

#puts [$w get $line.$c0 $line.$cn]
   $w tag add hot $line.$c0 $line.[expr $cn+1]
}



proc manOutput {} {
   global manx

   set wins [winfo children .]

   foreach i [lsort $wins] {
         set title "#[string range $i 4 4]"
         if {$title=="#"} {append title 1}
         lappend titleList [list $title $i]
   }

   foreach w $wins {
      $w.output.m delete 0 last
      foreach i $titleList {
         $w.output.m add radiobutton -label [lfirst $i] \
            -variable manx(out$w) -value [lsecond $i]
      }
   }

   if {$manx(outcnt)==1} {
      pack forget .man.output
   } else {
      foreach w $wins {pack $w.output -before $w.occ -side left -expand yes}
   }
}



#--------------------------------------------------
#
# Highlights
#
#--------------------------------------------------

# get old highlights and yview, if any

proc manHighView {w} {
   global manx

   if ![catch {set y $manx(yview,$manx(hv$w))}] {
      $w.show yview $y
      $w.show mark set xmark $y.0
   }
   manHighlights $w get
}


# highlighting commands: update (menu), get (all from database)

proc manHighlights {w {cmd update}} {
   global high man manx

   $w.high.m delete 0 last

   # get/save tags
   set f $manx(hv$w)
   if [string match <* $f] {set f [string range $f 1 end]}
   # canonical file name
   if ![catch {set sf [file readlink $f]}] {
      if [string match /* $sf] {
         set f $sf
      } else {
         set f [file dirname $f]
         set strip 1
         while {$strip} {
            switch -glob $f {
               ../* {set f [file dirname $f]; set sf [string range $sf 3 end]}
               ./* {set sf [string range $sf 2 end]}
               default {set strip 0}
            }
         }
         append f /$sf
      }
   }
   set v high($f)
   set tags ""
   if {$cmd=="update"} {
      if [file isfile $f] {set tags "[file mtime $f] "} {set tags "-1 "}
      append tags [$w.show tag ranges highlight]
      if {$f!="" && [llength $tags]>1} {set $v $tags} {catch {unset $v}}
   } elseif {[info exists $v]} {
      if {![file isfile $f] || [file mtime $f]<=[lfirst [set $v]]} {
         set tags [set $v]
      } else {
         if {![tk_dialog .dialog Warning "Highlights out of date for $f.  Delete them?" "" 1 No Yes]} {
            set $v [set tags "[file mtime $f] [lrange [set $v] 1 end]"]
         }
      }
   }
#puts stdout "v = $v, f = $f"

   # update highlighting in text, menu
   set len [llength $tags]
   for {set i 1} {$i<$len} {incr i 2} {
      set first [lindex $tags $i]
      set last [lindex $tags [expr $i+1]]
      if {$cmd=="get"} {$w.show tag add highlight $first $last}
      set label \
         [tr [string range [string trim [$w.show get $first $last]] 0 $man(high,hcontext)] \012 " "]
      $w.high.m add command -label $label \
         -command "$w.show yview [max [expr $first-$man(high,vcontext)] 1]"
   }
   if {$len<=1} {$w.high.m add command -label "(none)"}
}


#--------------------------------------------------
#
# file completion
#
#--------------------------------------------------

proc manFilecomplete {w} {
   global manx

   set line $manx(typein$w)
   set file [string trim [llast $line] <]
   set posn [string last $file $line]

   set ll [llength [set fc [filecomplete $file]]]
#puts stdout "$line, $file, $posn, $fc"
   
   # check for no matches or more than one match
   if {!$ll} {
      winstderr $w.info "no matches"
      return
   } elseif {$ll>=2} {
      foreach i $fc {lappend matches [file tail $i]}
      if {$ll<10} {winstderr $w.info [lrest $matches]} {
         manOpenText $w
         $w.show insert end [lrest $matches]
         manCloseText $w
      }
      set fc [lfirst $fc]
   }

   # show new file name
   set manx(typein$w) [string range $line 0 [expr $posn-1]]$fc

   $w.mantypein icursor end
   tk_entrySeeCaret $w.mantypein
}


#--------------------------------------------------
#
# manNewMode -- collect mode change inits in single place
#    types of modes: section, man, txt, apropos, super, help
#
#--------------------------------------------------

proc manNewMode {w mode {n {""}}} {
   global man manx

   # save old highlights --> done continuously
#   manHighlights $w
   # save yview
   scan [$w.v get] "%d %d %d %d" totalU windowU firstU lastU
   set manx(yview,$manx(hv$w)) $firstU; # can't do this on text widget--grrr!
#puts "saving yview for $manx(hv$w) = $firstU"

   set manx(oldmode$w) $manx(mode$w)
   set manx(mode$w) $mode
   set manx(manfull$w) ""
   set manx(catfull$w) ""
   set manx(man$w) ""
   set manx(name$w) ""
   set manx(num$w) ""

   # reset searching
   set manx(search,string$w) ""
   $w.search.cnt configure -text ""
   searchboxKeyNav "" Escape 0 $w.show $w.v "" 0
   if {$mode=="section"} {searchboxKeyNav C s 0 $w.show $w.v "" 1}
   after 5 selection clear $w.show

#   set sbx(lastkeys$w) ""
   set manx(vect) 1
   set manx(try) 0
   $w.sections.m delete 0 last
   $w.links.m delete 0 last


   # disable various ui buttons for different types
   #   (paths, volumes, shortcuts, history always available)
   #
   #               man help txt section apropos super
   # yview          x   x    x     ?
   # sections       x   x
   # highlights     x   x    x
   # links          x   x
   # shortcuts +/-  x        x
   # print          x

   set high(0) disabled; set high(1) normal

   set h $high([lmatch {man help} $mode]); $w.sections configure -state $h
   set h $high([lmatch {man txt help} $mode])
      foreach i {high hadd hsub} {$w.$i configure -state $h}
   set h $high([lmatch {man help} $mode]); $w.links configure -state $h
   set h $high([lmatch {man txt} $mode])
      foreach i {sadd ssub} {$w.$i configure -state $h}
   set h $high([lmatch man $mode]); $w.occ.m entryconfigure Print -state $h

   # hypertext (can key on section # if selecting from volume listing)
   bind $w.show <Double-Button-1> "
      if {\[expr %t-\$text(b1-time)\]<500 && \[expr %x-\$text(b1-x)\]<3} {
         set text(b1-time) \[expr \$text(b1-time)-500\]
         manShowMan \[manHotSpot get %W @%x,%y\] $n $w
      }
   "
}



#--------------------------------------------------
#
# manXXX - miscellaneous medium-level commands
#
#--------------------------------------------------

proc manOpenText {w} {
   global manx

   #raise $w
   $w.show configure -cursor watch; update idletasks
   $w.show configure -state normal
   $w.show delete 0.1 end
}

proc manCloseText {w} {
   global manx

   $w.show configure -state disabled
   $w.show configure -cursor $manx(cursor)
   $w.show mark set xmark 1.0
}

proc manResetEnv {} {
   global env man manx

   set manpath {}
   foreach i $manx(paths) {if {$man($i)} {append manpath :$i}}
   set env(MANPATH) [string range $manpath 1 end]
}

proc manSectButt {w n} {
   global manx

   if {[set f [lsearch $manx(manList) $n]]!=-1} {
      set manx(cursect) $n
      $w.volnow configure -text "([lindex $manx(manList) $f]) [lindex $manx(manTitleList) $f]"
   }
}

proc manTab {w {ink 1}} {
   set l "$w.mantypein $w.show $w.search.t"
   if [set i [lsearch $l [focus]]]==-1 return
   set new [expr ($i+$ink)%[llength $l]]
   focus [lindex $l $new]
}



#--------------------------------------------------
#
# manInit -- determine which sections exist,
#    load lists thereof if requested
#
#--------------------------------------------------

proc manInit {} {
   global man manx mani

   if {$manx(init)} return

   manReadSects
   set manx(init) 1
}


proc manReadSects {{w .man}} {
   global man manx mani winout

   set dirtmp [pwd]
   winstdout $w.info "Reading Volumes ... "
   $w.show configure -cursor watch; update idletasks

   # reset arrays, lists
#   catch {unset mani}
   set manx(manList) ""
   set manx(manTitleList) ""

   set cnt 0
   foreach i $mani(manList) {
      winstdout .man.info "$winout(lastMessage$w.info) $i"; update idletasks
      if {[manReadSection $i]!=0} {
         lappend manx(manList) $i
         lappend manx(manTitleList) [lindex $mani(manTitleList) $cnt]
         if {$man(preformat)} {manFormatSect $i}
      }
      incr cnt
   }

   # add pseudo-section All
   lappend manx(manList) all
   lappend manx(manTitleList) "All Volumes"
   set mani(all) {}
   set mani(all,form) ""  
   # no preformat for All


   $w.show configure -cursor $manx(cursor)
   winstdout $w.info {}
   cd $dirtmp
}



#--------------------------------------------------
#
# manReadSection
#
#--------------------------------------------------

proc manReadSection {n} {
   global man manx mani mandot env

   # follow all paths here, even if search presently turned off for that dir
   if ![info exists mani($n,dirs)] {return 0}
   set mani($n) ""
   set cnt 0
   foreach i $mani($n,dirs) {
      foreach k [glob -nocomplain $i*] {
         if {![catch {cd $k}]} {
#puts stdout $n:$k
            if {![catch {set ltmp [glob -nocomplain *]}]} {
               lappend mani($n) [list $k $ltmp]
               # collect up all man page names with dots, at the cost of longer startup time
               foreach j $ltmp {
                  if {[regexp {\..*\.} $j] && ![regexp ($man(zregexp))|(~$) $j]} {
                     set mandot([file rootname $j]) 1
                  }
               }
               incr cnt [llength $ltmp]
               }
         }
      }
   }

   # initially unformatted
   set mani($n,form) ""

   return $cnt
}



#--------------------------------------------------
#
# mandesc -like features
#
# mani(<single letter>) => name of full title main index
# mani(<letter>) => listing of full dir & list of names
# mani(<letter>,form) => formatted?
# mani(<letter>,cnt) => # of entries
# mani(<letter>,dirs) => paths x sections cross product, w/mandesc diddling
# --> everybody has a letter and letters aren't repeated
#
#--------------------------------------------------


# cross product of paths by sections
proc manDescDefaults {} {
   global man manx mani

   # don't check manx(defaults) here
   manDescManiCheck return

   foreach i $mani(manList) {
      set mani($i,dirs) {}
      foreach j $manx(paths) {
         lappend mani($i,dirs) $j/$man(mandir)$i
      }
   }
   set manx(defaults) 1
}


# commands: move, copy, delete
# from, to *list* of source, target dirs
# list is list of directory *patterns*

proc manDescMove {from to dirs} {manDesc move $from $to $dirs}
proc manDescDelete {from dirs} {manDesc delete $from "" $dirs}
proc manDescCopy {from to dirs} {manDesc copy $from $to $dirs}
proc manDescAdd {to dirs} {
   global mani

   manDescManiCheck
   foreach t $to {
      foreach d $dirs {
         lappend mani($t,dirs) $d
      }
   }
}

proc manDesc {cmd from to dirs} {
   global man manx mani

   manDescManiCheck
   if {$from=="*"} {set from $mani(manList)}
   if {$to=="*"} {set to $mani(manList)}
   foreach n "$from $to" {
      if [lsearch $mani(manList) $n]==-1 {
         puts stderr "$cmd: Section letter `$n' doesn't exist."
      }
   }

   if $manx(debug) {puts stdout "$cmd {$from} {$to} {$dirs}"}
   foreach d $dirs {
      foreach f $from {
         set newdir {}
         foreach fi $mani($f,dirs) {
            if [string match $d $fi] {
               if {$cmd=="copy"} {lappend newdir $fi}
               if {$cmd=="copy"||$cmd=="move"} {
                  foreach t $to {if {$f!=$t} {lappend mani($t,dirs) $fi} {lappend newdir $fi}}
               }
            } else {lappend newdir $fi}
         }
         set mani($f,dirs) $newdir
         if $manx(debug) {puts stdout $f:$mani($f,dirs)}
      }
   }
}

proc manDescAddSects {l {posn "end"} {what "n"}} {
   global man mani

   manDescManiCheck
   if {$posn=="before"||$posn=="after"} {set l [lreverse $l]}
   foreach i $l {
      set n [lfirst $i]; set tit [lsecond $i]
      if [lsearch $mani(manList) $n]!=-1 {
         puts stderr "Section letter `$n' already in use; request ignored."
         continue
      }

      if {$posn=="end"} {
         lappend mani(manList) $n
         lappend mani(manTitleList) $tit

      } elseif {$posn=="before"||$posn=="after"} {
         if [set ndx [lsearch $mani(manList) $what]]==-1 {
            puts stderr "Requested $posn $what, but $what doesn't exist; request ignored"
            continue
         }
         if {$posn=="after"} {incr ndx}
         set mani(manList) [linsert $mani(manList) $ndx $n]
         set mani(manTitleList) [linsert $mani(manTitleList) $ndx $tit]

      } elseif {$posn=="sort"} {
         lappend mani(manList) $n
         set mani(manList) [lsort $mani(manList)]
         set ndx [lsearch $mani(manList) $n]
         set mani(manTitleList) [linsert $mani(manTitleList) $ndx $tit]
      }

      set mani($n,dirs) {}
   }
}


# if mani array, matrix not already created, do it now

proc manDescManiCheck {{action "exit"}} {
   global man mani manx env

   if ![info exists mani(manList)] {
      foreach i [split $env(MANPATH) :] {
         if {[file isdirectory $i] && [file readable $i]} {
            lappend manx(paths) [string trimright $i /]
            if {![info exists man($i)]} {set man($i) 1}
            lappend manx(pathstat) $man($i)
         }
      }

      set mani(manList) $man(manList)
      set mani(manTitleList) $man(manTitleList)
      if {$action=="return"} return
      manDescDefaults
   }
}

# for debugging
proc manDescShow {} {
   global man manx mani

   manDescManiCheck
   puts stdout "*** manDescShow"
   foreach i $mani(manList) {
      if [info exists mani($i,dirs)] {puts stdout $i:$mani($i,dirs)}
   }
}



#--------------------------------------------------
#
# manShowSection -- show listing
#
#--------------------------------------------------

proc manShowSection {w n} {
   global man manx mani

   manNewMode $w section $n
   set manx(lastvol) $n
   set manx(hv$w) $n

   set head [lindex $manx(manTitleList) [lsearch $manx(manList) $n]]
   if {$mani($n,form)==""} {
      $w.show configure -cursor watch
      winstdout $w.info "Formatting $head ..."; update idletasks
      manFormatSect $n
   }
   winstdout $w.info $head
   manSectButt $w $n

   manOpenText $w
   $w.show insert end $mani($n,form)
   $w.search.cnt configure -text $mani($n,cnt)
   manCloseText $w
   manHighView $w
#   focus $w.show
}


proc manFormatSect {n} {
   global man manx mani mandot

   set ltmp ""

   set var mani($n,form)
   if {$n=="all"} {set nl $manx(manList)} {set nl $n}

   # build up big list of all entries
   foreach k $nl {
      foreach i $mani($k) {
         # directory turned off
         set p [file dirname [lfirst $i]]
         if {![info exists man($p)]||$man($p)} {
            foreach j [lsecond $i] {
               lappend ltmp $j
            }
         }
      }
   }
   set cnt [llength $ltmp]
   set mani($n,cnt) "$cnt entries"


   # specialized version of uniqlist
   set pr ""; set pe ""; set pl ""
   set online 0; set skip 0
   foreach i [lsort $ltmp] {
      if [string match *~ $i] continue
      set ir [file rootname $i]; set ie [file extension $i]; set il [string range $i 0 0]
      if {[regexp $man(zregexp) $ie] && ![info exists mandot($ir)]} {
         set ie [file extension $ir]; set ir [file rootname $ir]
      }

      if {$online>50} {
         append $var "\n"
         set online 0
      } elseif {$pl!=$il && $online>0 && ($n==3 || ![string match {[A-Z]} $il])} {
         append $var "\n\n"
         set online 0
      }

      if {$pr!=$ir} {
         if {$online!=0} {append $var "  "}
         append $var $ir; incr online
         set skip 0
      } elseif {$pe==$ie} {
#         append $var +
#         set skip 0
      } else {
         if {$online!=0} {
            if {!$skip} {append $var $pe}
            append $var "  "
         }
         append $var $i; incr online
         set skip 1
      }

      set pr $ir; set pe $ie; set pl $il
   }

   if {$cnt==0} {set $var "No man pages in currently enabled paths.\nTry turning on some under Paths."}
   return $cnt
}



#--------------------------------------------------
#
# manShowMan -- given various formats of names,
#    search for that man page.  if successful, call manShowManFound
#
# don't use `man' to show because may want to *always*
#    format with my own macros
#
#--------------------------------------------------

proc manShowMan {fname {goodnum ""} {w .man}} {
   global man manx mani mandot env
#puts stdout "manShowMan: $fname $goodnum $w"

   # establish valid variable values
   if {[string trim $fname]==""} return
   if {$goodnum=="all"} {set goodnum ""}


   # given full path?  if so, don't need to search
   if {[regexp {^[.~/$]} $fname]} {
      manShowManFound [fileexp [lfirst $fname]] 0 $w
      return
   }

   # `|command' for shell command, whose text is sucked into search box,
   #    to use with lman, say
   # `< file' syntax for file to suck up raw text
   # for more sophisticated file reading, use NBT

   if {[regexp {^[|<]} $fname]} {manShowText $fname $w; return}


   # need to search for name

   # construct stem, section, extension
   set oname [string trim [lfirst $fname] { .,?!;"'}]
   winstdout $w.info "Searching for $oname ..."; update idletasks
   set fname [string tolower $oname]; # lc w/  () for regexp
   set sname [string trim $fname ()]; # lc w/o ()
   set oname [string trim $oname ()]; # original case w/o ()

   set name $sname; set num ""; set ext ""
   # given section number
   if {$goodnum!=""} {
      set num $goodnum

   # extract section number in `man(sect)' format
   } elseif {![regexp -nocase {([a-z0-9_.-]+)([\t ]*)\(([^)]?)(.*)\)} \
      $fname all name spc num ext]} {

   # get section number in `man.sect' format
      # no .z's here
      # see if we have weird name with dot
      if {[regexp {\..*\.} $oname] || ![info exists mandot($oname)]} {
#puts stdout "$oname not a weird dot one: ..regexp=[regexp {\..*\.} $oname], exists=[info exists mandot($oname)]"
         set tmp [string range [file extension $oname] 1 end]
         set num [string range $tmp 0 0]
         set name [file rootname $sname]
         set oname [file rootname $oname]
         set ext [string range $tmp 1 end]
      }
   }
# pseudo sections can have empty numbers
#   set num [string tolower $num]
   if [lsearch $manx(manList) $num]==-1 {set num ""}
   set ext [string tolower $ext]
   if $manx(debug) {puts stdout "$name : $num : $ext"}
   # if names are truncated in file system
   #if [string length $name]>12 {set name [string range $name 0 11]*}

   $w.show configure -cursor watch; update idletasks

   # search for man page
   set found 0
   set foundList ""

   foreach i [if {$num!=""} {format $num} {format $manx(manList)}] {
      foreach j $mani($i) {
         set curdir [lfirst $j]
#puts stdout "curdir = $curdir, file dirname = [file dirname $curdir]"
         if {[info exists man([file dirname $curdir])]&&!$man([file dirname $curdir])} continue
#puts stdout "\tsearching"
         foreach k [lsecond $j] {
            if {[string match $name.* [string tolower $k]] && ![string match *~ $k]
               && [file readable $curdir/$k] && [file isfile $curdir/$k]
               && [lsearch -exact $foundList $curdir/$k]==-1
            } {
               lappend foundList $curdir/$k
            }
         }
      }
   }
   set found [llength $foundList]

   # if no matches with extension, look for one without
   if {!$found} {
      if {$ext!=""} {manShowMan $name $num $w; return} \
      elseif {$num!=""} {manShowMan $name {} $w; return}
   }

   $w.show configure -cursor $manx(cursor)

   # act on results of search
   if {!$found} {
      winstdout $w.info "$sname not found"; update idletasks
      return
   } elseif {$found>1} {
      after 1 pack $w.dups -before $w.mantypein -side left -anchor e; $w.dups configure -state active
      $w.dups.m delete 0 last
      set flLen [llength $foundList]
      if {$flLen<40} {set dupsFont $manx(gui,font)} {set dupsFont $man(menu,smallfont)}
      if {$flLen>60} {set foundList [lrange $foundList 0 59]}
      foreach i $foundList {$w.dups.m add command -label $i -command "manShowManFound $i 1 $w"}
      $w.dups.m configure -font $dupsFont

      # show closest match
      set show [lfirst $foundList]
#puts stdout  "manx(cursect) = $manx(cursect), $oname=?=$sname"
#puts stdout "query $name $num $ext"
#puts stdout "foundList = $foundList"
      foreach i [lreverse $foundList] {
         set ir [file rootname [file tail $i]]; set in [file extension $i]
         if {[regexp $man(zregexp) $in]} {set in [file extension $ir]; set ir [file rootname $ir]}
         set ie [string range $in 2 end]; set in [string range $in 1 1]

#puts stdout "\tdatabase $ir $in $ie"
#puts stdout "ir=$ir, oname=$oname; in=$in, num=$num; ie=$ie, ext=$ext"
          # pick a winner from all case-insensitive matches of name only
         if {$ir==$oname && $in==$num && $ie==$ext} {set show $i; break} \
         elseif {$ir==$oname && ($in==$num||$num=="")} {set show $i} \
         elseif {$in==$num} {set show $i} \
         elseif {$in==$manx(cursect)} {set show $i}
      }
      manShowManFound $show 1 $w
   } else {manShowManFound $foundList 0 $w}
}



#--------------------------------------------------
#
# manShowManFound -- display man page and update gui with parse info
#
# invariants:
#    manx(catfull$w)!="" iff loaded from .../cat. directory
#--------------------------------------------------

proc manShowManFound {f {keep 0} {w .man}} {
   global man manx

   # only redirect text of man pages
   if {[winfo exists $manx(out$w)]} {set w $manx(out$w)} {set manx(out$w) $w}

   # update gui
   manNewMode $w man
   set manx(lastman) $f
   if {!$keep} {pack forget $w.dups; #$w.dups.m unpost}
   set manx(links) ""

   # for relative names w/. and ..
   set tmpdir [file dirname $f]


   # set variables
   set manx(manfull$w) $f
   set manx(man$w) [file tail $f]
   if {[regexp $man(zregexp) $manx(man$w)]} {set manx(man$w) [file rootname $manx(man$w)]}
   set manx(name$w) [file rootname $manx(man$w)]
   set fdir [file dirname $manx(manfull$w)]
   if {[regexp $man(zregexp) $fdir]} {set fdir [file rootname $fdir]}
   set manx(num$w) [string index $fdir [expr [string length $fdir]-1]]
#   set manx(num$w) [string range [file extension $manx(man$w)] 1 1]
#   if {[file extension $manx(man$w)]==".man"} {set manx(num$w) [string range [file dirname $f] 1 1]}
   if {[regexp $man(catsig) $fdir]} {
#[string match */cat?*/* $f]} {
# $tmpdir] || [string match */cat?* [file dirname $tmpdir]]} {
      set manx(catfull$w) $manx(manfull$w)
   } else {
      set manx(catfull$w) [file dirname [file dirname $f]]/cat$manx(num$w)*/$manx(name$w).*
   }
   set manx(cat) [file dirname [file dirname $f]]/cat$manx(num$w)

   # first check for already-formatted man page
   #    that's up to date vis-a-vis source code
   if {[set path [lfirst [glob -nocomplain $manx(catfull$w)]]]!=""
      && (!$man(savenroff) || ![file writable $path] 
          || [file mtime $path]>=[file mtime $manx(manfull$w)])} {
      set manx(catfull$w) $path
      set pipe [manManPipe $path]

   } elseif {[file exists $manx(manfull$w)]} {
      # cd into top of man hierarchy in case get .so's
      if {[string match */man?* $tmpdir]} {
         set topdir [file dirname $tmpdir]
      } else {set topdir $tmpdir}
      if {[catch {cd $topdir}]} {
         winstderr $w.info "Can't cd into $topdir.  This is bad."
         return
      }
#puts stdout "\n\n\tcurrend dir [pwd]"
#puts stdout "manShowManFound\n\tcatfull = $manx(cat), manfull = $manx(manfull$w)"
      # try to save a nroff-formatted version?
      if {$man(savenroff) && [file writable $manx(cat)]} {
         set path [set manx(catfull$w) $manx(cat)/$manx(man$w)]
         winstdout $w.info "Saving nroff version ..."; update idletasks
         if {![catch {eval "exec [manManPipe $manx(manfull$w)] | $man(format) > $path"}]} {
            if {$man(compressnroff)} {   # this doesn't follow the style for H-P
               exec $man(compress) $manx(catfull$w)
               set path [set manx(catfull$w) [lfirst [glob $manx(catfull$w).*]]]
            }
            set pipe [manManPipe $path]
         } else {
            # if problem making nroff version, fall back
            set path $manx(manfull$w)
            set manx(catfull$w) ""
            set pipe "[manManPipe $path] | $man(format)"
         }

      } else {
         set path $manx(manfull$w)
         set manx(catfull$w) ""
         set pipe "[manManPipe $path] | $man(format)"
      }

   } else {
      winstdout $w.info "$manx(manfull$w) not found"
      return
   }


   # got full file name, now show it
   set errflag 0
   if [string match *roff* $pipe] {set msg "Formatting and filtering"} {set msg Filtering}
   winstdout $w.info "$msg $manx(name$w) ..."; update idletasks


   manOpenText $w

   append pipe " | bs2tk -T -m $man(subsect) $man(headfoot) $man(changeleft)"

#puts $pipe
   # source should take a pipe argument: "source |$pipe"
   if {[catch {set fid [open "|$pipe"]}]} {
      winstderr $w.info "Deep weirdness with $path.  Tell your sys admin."
      set errflag 1
   }
   while {![eof $fid]} {eval [gets $fid]}
   if [catch {close $fid}] {
      winstderr $w.info "Man page not installed properly, probably."
      set errflag 1
   }

   manCloseText $w
   if {$errflag} return
   if {$manx(catfull$w)!=""} {set manx(hv$w) $manx(catfull$w)} {set manx(hv$w) $manx(manfull$w)}



   manHighView $w

   focus $w.show

   cd $tmpdir
   manShowManStatus $w
}


proc manManPipe {f} {
   global man

   if {[regexp $man(zregexp) $f] || [regexp $man(zregexp) [file dirname $f]]} {
      set pipe $man(zcat)
   } else {set pipe cat}

   return "$pipe $f"
}

proc manShowManStatus {w} {
   global man manx

   ### update status information

   # show section
   manSectButt $w $manx(num$w)

   # typein field
   set manx(typein$w) $manx(name$w)

   # history - save entire path
   set manx(history$w) \
      [lrange [setinsert $manx(history$w) 0 [list $manx(manfull$w)]] 0 [expr $man(maxhistory)-1]]
   # should do this as a postcommand, but that's too messy
   $w.history.m delete 0 last
   foreach i $manx(history$w) {
      if {[llength [lfirst $i]]>=2} {set l [lfirst $i]} {set l $i}
      if {![regexp {^[|<]} $l]} {
         set l [file tail $l]
         if [regexp $man(zregexp) $l] {set l [file rootname $l]}
      }
      $w.history.m add command -label $l -command "manShowMan $i {} $w"
   }

   # hyperlinks
   set E 0
   foreach i $manx(links) {
       foreach j [split $i ,.] {
          if {[regexp {\([1-9lonp].*\)} $j]} {
             $w.links.m add command -label $j -command "manShowMan $j {} $w"
             incr E
          }
       }
   }
   if {!$E && $manx(mode$w)=="man"} {$w.links.m add command -label "(none)"}

   # whatis information -- find by scanning text
   winstdout $w.info ""
   if {$man(whatwhere)} {
      winstdout $w.info $manx(manfull$w)
   } else {
      scan [$w.show index end] %d n
      set n [max $n 20]
      for {set i 1} {$i<=$n} {incr i} {
         if {[regexp -- {- (.*)} [$w.show get $i.0 "$i.0 lineend"] all info]} {
            incr i
            while {[regexp {( +)(.*)} [$w.show get $i.0 "$i.0 lineend"] all spaces more]} {
               append info " $more"
               incr i
            }
            winstdout $w.info "$info"
            break
         }
      }
   }
}



#--------------------------------------------------
#
# manShowText -- use searching, etc. facilities for non-man page text
#    to use via `send', pass full path name of text file to show
#
#--------------------------------------------------

proc manShowText {f0 {w .man}} {
   global man manx

# maybe want to use `file' to check for ASCII files
#   if {![file readable $f] || [file isdirectory $f]} {
#      winstdout $w.info "Can't read $f"
#      return
#   }

   if {[string match <* $f0]} {
      set f [fileexp [string range $f0 1 end]]
      if {[regexp $man(zregexp) $f]} {set f "|$man(zcat) $f"}
# {set f "|cat $f"}
   } else {set f [pipeexp $f0]}

   # strain out control characters and show nroff files
#   append f " | bs2tk"

   manNewMode $w txt

   manOpenText $w
   if {[catch {
      set cnt 0
      set fid [open $f]
#      while {![eof $fid]} {eval [gets $fid]}
      while {![eof $fid]} {$w.show insert end [gets $fid]\n; incr cnt}
      close $fid
   }]} {manCloseText $w; winstderr $w.info "Trouble reading $f0"; return}
   manCloseText $w
   pack forget $w.dups; #$w.dups.m unpost

   if [file isfile $f] {cd [file dirname [glob $f]]}


   ### update status information

   # could always save full path here, but that occupies too much screen real estate
   set manx(man$w) $f0
   set manx(num$w) X
   set manx(hv$w) $f
   set manx(manfull$w) $f0
   set manx(catfull$w) $f
   set manx(name$w) $f0
   set manx(links) {}
   set tmp $man(whatwhere)
   set man(whatwhere) 1
   manShowManStatus $w
   set man(whatwhere) $tmp

   $w.search.cnt configure -text "$cnt lines"
   manHighView $w
   focus $w.show
}



#--------------------------------------------------
#
# manApropos -- show `apropos' (`man -k') information, w/dups filtered out
#
#--------------------------------------------------

proc manApropos {w name} {
   global man manx

   if {$name==""} {set name $manx(man$w)}
   if {$name==""} {return}

   manNewMode $w apropos
   set fid [open "|man -k $name $man(aproposfilter)"]
   manOpenText $w

   catch {
      while {![eof $fid]} {
         gets $fid line
         $w.show insert end "$line\n"
#         if {[regexp {^[a-zA-Z_\t ]+\([^)]*\)} "$line" link]} {
#           $w.links.m add command -label "$link" -command "manShowMan \"$link\" {} $w"
#         }
      }
      close $fid
   }
   
   manCloseText $w
#  focus $w.show
}



#--------------------------------------------------
#
# manSuperMan -- crossreference keyword (this is expensive!)
#
#--------------------------------------------------

# G.B. Shaw reference
proc manSuperMan {w fname} {
   global man manx env

   if {$fname==""} {set name $manx(man$w)}
   if {$fname==""} {return}
#   set dirtmp [pwd]
   set max 40

   manNewMode $w super

   if {![regexp -nocase {([a-z0-9_.-]+)([\t ]*)\(([^)]?)(.*)\)} \
      $fname all name spc num ext]} {
      set name [file rootname $fname]
      set tmp [string range [file extension $fname] 1 end]
      set num [string range $tmp 0 0]
      set ext [string range $tmp 1 end]
   }
   set num [string tolower $num]; set ext [string tolower $ext]
   if {$num!=""} {set numList $num} {set numList $manx(manList)}

   set foundList ""
   set cnt [llength $manx(paths)]
   set mcnt 1
   foreach i $manx(paths) {
      set curdir [lfirst $i]
      winstdout $w.info "Wait $cnt ..."; update idletasks
      incr cnt -1
      if {!$man($curdir)} continue

      foreach j $numList {
         foreach k [glob -nocomplain $curdir/man$j*] {
            if {[catch {cd $k}] || $mcnt>$max} continue
            catch {
            foreach l [eval exec egrep -il $name [glob *]] {
               lappend foundList $k/$l
               incr mcnt
               if {$mcnt>$max} break
            }}
         }
      }
   }


   set found [llength $foundList]
   if {$found} {
      $w.dups.m delete 0 last
      foreach i $foundList {
         $w.dups.m add command -label [file tail $i] -command "manShowManFound $i 1 $w"
      }
      if {$mcnt>$max} {$w.dups.m add command -label "(more)"}
      manShowManFound [lfirst $foundList] 1 $w
      after 1 pack $w.dups -before $w.mantypein -side left; $w.dups configure -state active
   }

#   cd $dirtmp
}



#--------------------------------------------------
#
# manPrint -- kill trees
#
#--------------------------------------------------

proc manPrint {w} {
   global man manx winout

# can't get here unless man page
#   if {$manx(mode$w)!="man" && $manx(mode$w)!="txt"} {
#      winstderr $w.info "TkMan only prints man pages."
#      return
#   }

   set f [string trim $manx(manfull$w)]
   if {$f=="" || ![file exists $f]} return
   set name [file rootname [file tail $f]]; set sect [file extension $f]
   if {[regexp $man(zregexp) $f]} {set sect [file extension $name]; set name [file rootname $name]}

   set tmp $winout(lastMessage$w.info)
   winstdout $w.info "Printing $f ..."; update idletasks
   # need to cd in case of trying to print .so pages
   set tmpdir [pwd]
   set topdir [file dirname $f]
   set printpipe $man(print)
   if {[regexp -- $man(catsig) $topdir]} {
      set printpipe $man(catprint)
      if {[tk_dialog .dialog "NO GUARANTEES" "No troff source.  Try to reverse compile cat-only page?" \
            "" 1 No Yes]} {
         set printpipe "bs2tk -r $man(changeleft) $man(subsect) -n $name -s $sect | $man(print)"
      }
   }
   if {[regexp $man(catsig) $topdir] || [string match */man?* $topdir]} {
      set topdir [file dirname $topdir]
   }

   catch {cd $topdir}
   eval exec [manManPipe $f] | $printpipe
   winstdout $w.info $tmp
   cd $tmpdir
}



#--------------------------------------------------
#
# manHelp -- dump boilerplate, decorate w/regexp-added tags
#
#--------------------------------------------------

proc manHelp {w} {
   global manx

   manNewMode $w help
   set manx(manfull$w) "help"
   set manx(hv$w) help

   manOpenText $w
   if [string length $manx(warnings)] {
      $w.show insert end Warnings\n
      $w.show insert end $manx(warnings)\n\n
   }
   $w.show insert end $manx(boilerplate)

   $w.show tag add big 1.0 "1.0 lineend"
   scan [$w.show index end] %d numLines
   for {set i 1} {$i <= $numLines} {incr i} {
      set line [$w.show get $i.0 "$i.0 lineend"]
      if {[string match ==* $line]} {
         set j [expr $i+1]
         set line [$w.show get $j.0 "$j.0 lineend"]
         $w.sections.m add command -label $line -command "$w.show yview $i"
         $w.show tag add title $j.0 "$j.0 lineend"
      } elseif {[regexp -nocase {^= [a-z ]+ =} $line]} {
         $w.show tag add high $i.0 "$i.0 lineend"
         set sline [string range $line 2 [expr [string length $line]-3]]
         $w.sections.m add command -label "  $sline" -command "$w.show yview [expr $i-1]"
      }
   }
   regexpTextSearch $w.show {[A-Z][^a-z,;.?!]*[A-Z]} sc
   regexpTextSearch $w.show {(`[^']+')|([MC]-.)|([a-z]+@[A-Za-z.]+)} mono
#   regexpTextSearch $w.show {<[^ ]+>} high

   manCloseText $w
   manHighView $w
   foreach i {man(1) man(7) catman(8) xman(1) Tcl text bind option} {
      $w.links.m add command -label $i -command "manShowMan $i {} $w"
   }
   scan [$w.show index end] %d eot
   $w.search.cnt configure -text "$eot lines"

   update idletasks
   after 1 "winstdout $w.info \"TkMan v$manx(version) by Tom Phelps (phelps@cs.Berkeley.EDU)\""
}



#--------------------------------------------------
#
# manKeyNav -- keyboard-based navigation and searching
#
#--------------------------------------------------

proc manKeyNav {w m k} {
   global man manx

   set firstmode [expr {$manx(mode$w)=="section" || $manx(mode$w)=="apropos"}]
   searchboxKeyNav $m $k $man(incr,case) $w.show $w.v $w.info $firstmode
}



#--------------------------------------------------
#
# manSave -- manage merging with old config file
# if passed name of save file, could put this into utils
#
#--------------------------------------------------

proc manSave {} {
   global man manx env

   if {$manx(savegeom)} {set man(geom) [wm geometry .man]}
   set nfn $manx(startup)
   set ofn $env(HOME)/.oldtkman

   if {![file exists $nfn] || [file writable $nfn]} {
      if {[file exists $nfn]} {exec cp $nfn $ofn}
      set fid [open $nfn w]
      foreach p [info procs *SaveConfig] {eval $p $fid}
      puts $fid $manx(userconfig)

      if {[file exists $ofn]} {
         set ofid [open $ofn]
         set p 0
         while {[gets $ofid line]!=-1} {
            if {$p} {puts $fid $line} \
            elseif {$manx(userconfig)=="$line"} {set p 1}
         }
      }
      close $fid
   }

   # don't delete ~/.oldtkman
}



#--------------------------------------------------
#
# manSaveConfig -- dump persistent variables into passed file id
#
#--------------------------------------------------

proc manSaveConfig {fid} {
   global man manx high

# if only persistent stuff in man array,
# could have general SaveConfig in utils.tcl

   # traverse widget hierarchy, saving non-default fonts and colors
   #maybe later

   # save miscellaneous variables
   puts $fid "#\n# TkMan v$manx(version)\n#\n"
   foreach i [lsort [array names man]] {
      puts $fid "set [list man($i)] [list $man($i)]"
   }

   puts $fid "\n\n#\n# Highlights\n#\n"
   foreach i [lsort [array names high]] {
      puts $fid "set [list high($i)] [list $high($i)]"
   }
   puts $fid "\n"
}



##################################################
###
### start up
###
##################################################

if {$tk_version<3.3} {puts stderr "Tcl 7.0/Tk 3.3 required"; exit 1}
#if {![info exists manx(lib)]} {
#   set manx(lib) [pwd]
#   puts stderr "To make TkMan properly, edit the Makefile and type `make install'."
#}



#--------------------------------------------------
#
# set defaults for shared global variables
#
#--------------------------------------------------

# # # # # # # # # # # # # # # # # # # # # # # # # #
# DON'T EDIT THE DEFAULTS HERE--DO IT IN ~/.tkman #
# # # # # # # # # # # # # # # # # # # # # # # # # #

# man() = persistent variables
# manx() = x-pire, system use, command line overrides

#
# man
#

set man(manList) {1 2 3 4 5 6 7 8 9 l o n p}
set man(manTitleList) {
   "User Commands" "System Calls" Subroutines
   Devices "File Formats"
   Games "Miscellaneous" "System Administration" ?
   Local Old New Public
}
set man(show,pro) "-adobe-new century schoolbook-medium-r-normal--*-140-*"
set man(show,mono) "-adobe-courier-medium-r-normal--*-120-*"
set man(show,keyword) "-adobe-courier-medium-r-normal--*-120-*"
set man(show,font) $man(show,pro)
set man(show,smallcaps) "-adobe-new century schoolbook-medium-r-normal--*-120-*"
set man(show,bold) "-adobe-new century schoolbook-bold-r-normal--*-120-*"
set man(show,hot) {-underline yes}
set man(show,symbol) "-adobe-symbol-medium-r-normal--*-140-*"
set man(show,bigbold) "-adobe-new century schoolbook-bold-r-normal--*-140-*"
set man(show,italic) "-adobe-new century schoolbook-medium-i-normal--*-140-*"
set man(show,bolditalic) "-adobe-new century schoolbook-bold-i-normal--*-120-*"
set man(gui,font) "-Adobe-Helvetica-Bold-R-Normal--*-120-*"
set man(gui,monofont) "-Adobe-Courier-Bold-R-Normal--*-120-*"
set man(show,highlight) "-background #ffd8ffffb332"; # a pale yellow
#button .a
#set man(fontcolor) [lfifth [.a configure -foreground]]
#destroy .a

if [winfo depth .]==1 {
   set man(show,highlight) "-underline yes"
   # any more modifications for monochrome?
}

#if [string match *color [winfo visual .]] {
#   set man(background) beige
#   set man(foreground) SlateGray4
#   set man(show,fontcolor) black
#} else {
#   #set man(foreground) [option get . foreground Foreground]
#   #set man(background) [option get . background Background]
#   button .a
#   set man(foreground) [lfifth [.a configure -foreground]]
#   set man(background) [lfifth [.a configure -background]]
#   destroy .a
#   set man(show,fontcolor) $man(foreground)
#   #if {$man(show,fontcolor)==""} {set man(show,fontcolor) black}
#}

set man(menu,smallfont) "-Adobe-Helvetica-Bold-R-Normal--*-100-*"
set man(symbolfont) *symbol*140*
set man(maxhistory) 15
set man(shortcuts) {}
set man(whatwhere) 0
set man(iconify) 0
set man(subsect) ""
set man(preformat) 0
set man(savenroff) 0
set man(headfoot) ""
set man(incr,case) 0
set man(regexp,case) 1
set man(aproposfilter) {| sort | uniq}
set man(scrollbarside) right
set man(textboxmargin) 5
set man(changeleft) "-c"
set man(zregexp) {\.(gz|z|Z)$}
set man(catsig) {(cat[^/]+(/cat[^/]+)?$)}
set man(compressnroff) 0
set man(high,hcontext) 35
set man(high,vcontext) 10
set man(popupchar) ""
#set man(popupchar) \254
set man(geom) 570x800+150+10
set man(iconname) TkMan
set man(iconbitmap) ""
set man(iconmask) ""
set man(iconposition) ""
set man(quit) 1
set high(*) "format: time, start/end pairs for use by text widget"


#
# man()-independent manx()
#

set manx(title) "TkMan v$manx(version)"
set manx(warnings) ""
set manx(manList) {}
set manx(manTitleList) {}
set manx(userconfig) "### your additions go below"
set manx(init) 0
set manx(single) 0
set manx(cursor) left_ptr
set manx(cursect) 1
set manx(yview,help) 0
set manx(paths) ""
set manx(pathstat) ""
set manx(uid) 1
set manx(outcnt) 1
set manx(debug) 0
set manx(defaults) 0
set manx(startup) $env(HOME)/.tkman
set manx(savegeom) 1
set manx(lastvol) 1
set manx(lastman) TkMan


#
# process command-line args
# (usually want to do this after startup file, but need to get -M here)
#

for {set i 0} \$i<[llength $argv] {incr i} {
   set arg [lindex $argv $i]; set val [lindex $argv [expr $i+1]]
   switch -glob -- $arg {
      -iconname {set manx(iconname) $val; incr i}
      -iconmask {set manx(iconmask) $val; incr i}
      -iconposition {set manx(iconposition) $val; incr i}
      -iconbitmap {set manx(iconbitmap) $val; incr i}
      -icon* {set manx(iconify) 1}
      --icon* {set manx(iconify) 0}
#      -geom* {puts stdout "hey, got $arg"}
      -tkmangeom* {set manx(geom) $val; incr i; set manx(savegeom) 0}
      # put the more permissive option names down below
      -v* {puts stdout "TkMan v$manx(version)"; exit 0}
      -t* {set manx(title) $val; incr i}
      -guifont {set manx(gui,font) $val; incr i}
      -guimonofont {set manx(gui,monofont) $val; incr i}
#      -fg -
#      -foreground {set manx(foreground) $val; incr i}
#      -bg -
#      -background {set manx(background) $val; incr i}
      -quit {if [string match no* $val] {set manx(quit) 0} {set manx(quit) 1}}
      -s* {set manx(startup) $val; incr i}
      -d* {set manx(debug) 1; set manx(quit) 0}
      --d* {set manx(debug) 0}
      -M {set env(MANPATH) $val; incr i}
      -M+ {append env(MANPATH) ":$val"; incr i}
      -+M {set env(MANPATH) "$val:$env(MANPATH)"; incr i}
      default {
         after 2000 manShowMan $arg {{}} .man
         # permit several???
         break
      }
   }
}

if {![info exists env(MANPATH)] || $env(MANPATH)==""} {
   set env(MANPATH) /usr/man:/usr/local/man
   append manx(warnings) "You don't have a MANPATH environment variable set, but you should.\n"
}

# make window here so ~/.tkman commands can manipulate it without `after'
toplevel .man -class TkMan

# read in startup file after proc/vars/ui, so can modify them
if {$manx(startup)!="" && [file readable $manx(startup)]} {source $manx(startup)}

# these get set to man counterparts, unless overwritten by command line options
foreach i {
      iconify iconname iconbitmap iconmask iconposition
      gui,font gui,monofont
      quit geom
   } {
   if {![info exists manx($i)]} {set manx($i) $man($i)}
}
option add *TkMan*font $manx(gui,font) userDefault
#option add *Foreground $manx(foreground) userDefault
#option add *Background $manx(background) userDefault
#.man configure -background $manx(background)

# almost always you'll want to make the cross product
#    if you don't, be tricky and `set manx(defaults) 1'
if {!$manx(defaults)} manDescDefaults


# cooperate with X
#      -geo* {set man(geom) [lindex $argv [expr $i+1]]; incr i}
#update idletasks; wm withdraw .
#puts stdout [wm geometry .]
#if {[wm geometry .]!="1x1+0+0"} {set man(geom) [wm geometry .]}

#if $manx(debug) {set manx(paths) $env(HOME)/man; set env(MANPATH) $env(HOME)/man; manDescDefaults}
#if {$manx(debug)} {
#    puts stdout "debugging speedup..."
#    manDescDelete {1 2 3} *
#}
#if $manx(debug) manDescShow



TkMan
#tkwait visibility .man
update idletasks

if {$manx(debug)} {
   puts stdout "init takes [time manInit]"
} else {

   set manx(tmp) $winout(lastMessage.man.info)
   winstdout .man.info "Reading Volumes ... "
   manInit
   winstdout .man.info $manx(tmp)
}

   # how to get around this duplication of code?
   .man.vols.m delete 0 last
   set ctr 0
   foreach i $manx(manList) {
      .man.vols.m add command -label "($i) [lindex $manx(manTitleList) $ctr]" \
         -command "manShowSection .man $i"
      incr ctr
   }





# debugging

if {$manx(debug)} {
   entry .man.in -relief sunken -textvariable manx(debugcmd)
   emacsbind .man.in
   bind .man.in <KeyPress-Return> {winstdout .man.info "[eval $manx(debugcmd)]"}
   pack .man.in -fill x

   # convenience variables
   set w .man
}


# drag and drop font settings
if 0 {
set foselx(standalone) 0
source ~/spine/fosel/FoSel.tcl
foselInit
FoSel .fosel
}
