+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
+
+;;; Commentary:
+
+;; This code provides a function, `man', with which you can browse
+;; UNIX manual pages. Formatting is done in background so that you
+;; can continue to use your Emacs while processing is going on.
+;;
+;; The mode also supports hypertext-like following of manual page SEE
+;; ALSO references, and other features. See below or do `?' in a
+;; manual page buffer for details.
+
+;; ========== Credits and History ==========
+;; In mid 1991, several people posted some interesting improvements to
+;; man.el from the standard emacs 18.57 distribution. I liked many of
+;; these, but wanted everything in one single package, so I decided
+;; to incorporate them into a single manual browsing mode. While
+;; much of the code here has been rewritten, and some features added,
+;; these folks deserve lots of credit for providing the initial
+;; excellent packages on which this one is based.
+
+;; Nick Duffek <duffek@chaos.cs.brandeis.edu>, posted a very nice
+;; improvement which retrieved and cleaned the manpages in a
+;; background process, and which correctly deciphered such options as
+;; man -k.
+
+;; Eric Rose <erose@jessica.stanford.edu>, submitted manual.el which
+;; provided a very nice manual browsing mode.
+
+;; This package was available as `superman.el' from the LCD package
+;; for some time before it was accepted into Emacs 19. The entry
+;; point and some other names have been changed to make it a drop-in
+;; replacement for the old man.el package.
+
+;; Francesco Potorti` <pot@cnuce.cnr.it> cleaned it up thoroughly,
+;; making it faster, more robust and more tolerant of different
+;; systems' man idiosyncrasies.
+
+;; ========== Features ==========
+;; + Runs "man" in the background and pipes the results through a
+;; series of sed and awk scripts so that all retrieving and cleaning
+;; is done in the background. The cleaning commands are configurable.
+;; + Syntax is the same as Un*x man
+;; + Functionality is the same as Un*x man, including "man -k" and
+;; "man <section>", etc.
+;; + Provides a manual browsing mode with keybindings for traversing
+;; the sections of a manpage, following references in the SEE ALSO
+;; section, and more.
+;; + Multiple manpages created with the same man command are put into
+;; a narrowed buffer circular list.
+
+;; ============= TODO ===========
+;; - Add a command for printing.
+;; - The awk script deletes multiple blank lines. This behaviour does
+;; not allow to understand if there was indeed a blank line at the
+;; end or beginning of a page (after the header, or before the
+;; footer). A different algorithm should be used. It is easy to
+;; compute how many blank lines there are before and after the page
+;; headers, and after the page footer. But it is possible to compute
+;; the number of blank lines before the page footer by euristhics
+;; only. Is it worth doing?
+;; - Allow a user option to mean that all the manpages should go in
+;; the same buffer, where they can be browsed with M-n and M-p.
+;; - Allow completion on the manpage name when calling man. This
+;; requires a reliable list of places where manpages can be found. The
+;; drawback would be that if the list is not complete, the user might
+;; be led to believe that the manpages in the missing directories do
+;; not exist.
+
+\f
+;;; Code:
+
+(require 'assoc)
+
+;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+;; empty defvars (keep the compiler quiet)
+
+(defgroup man nil
+ "Browse UNIX manual pages."
+ :prefix "Man-"
+ :group 'help)
+
+
+(defvar Man-notify)
+(defvar Man-current-page)
+(defvar Man-page-list)
+(defcustom Man-filter-list nil
+ "*Manpage cleaning filter command phrases.
+This variable contains a list of the following form:
+
+'((command-string phrase-string*)*)
+
+Each phrase-string is concatenated onto the command-string to form a
+command filter. The (standard) output (and standard error) of the Un*x
+man command is piped through each command filter in the order the
+commands appear in the association list. The final output is placed in
+the manpage buffer."
+ :type '(repeat (list (string :tag "Command String")
+ (repeat :inline t
+ (string :tag "Phrase String"))))
+ :group 'man)
+
+(defvar Man-original-frame)
+(defvar Man-arguments)
+(defvar Man-sections-alist)
+(defvar Man-refpages-alist)
+(defvar Man-uses-untabify-flag t
+ "When non-nil use `untabify' instead of Man-untabify-command.")
+(defvar Man-page-mode-string)
+(defvar Man-sed-script nil
+ "Script for sed to nuke backspaces and ANSI codes from manpages.")
+
+;; vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
+;; user variables
+
+(defcustom Man-fontify-manpage-flag t
+ "*Make up the manpage with fonts."
+ :type 'boolean
+ :group 'man)
+
+(defcustom Man-overstrike-face 'bold
+ "*Face to use when fontifying overstrike."
+ :type 'face
+ :group 'man)
+
+(defcustom Man-underline-face 'underline
+ "*Face to use when fontifying underlining."
+ :type 'face
+ :group 'man)
+
+;; Use the value of the obsolete user option Man-notify, if set.
+(defcustom Man-notify-method (if (boundp 'Man-notify) Man-notify 'friendly)
+ "*Selects the behavior when manpage is ready.
+This variable may have one of the following values, where (sf) means
+that the frames are switched, so the manpage is displayed in the frame
+where the man command was called from:
+
+newframe -- put the manpage in its own frame (see `Man-frame-parameters')
+pushy -- make the manpage the current buffer in the current window
+bully -- make the manpage the current buffer and only window (sf)
+aggressive -- make the manpage the current buffer in the other window (sf)
+friendly -- display manpage in the other window but don't make current (sf)
+polite -- don't display manpage, but prints message and beep when ready
+quiet -- like `polite', but don't beep
+meek -- make no indication that the manpage is ready
+
+Any other value of `Man-notify-method' is equivalent to `meek'."
+ :type '(radio (const newframe) (const pushy) (const bully)
+ (const aggressive) (const friendly)
+ (const polite) (const quiet) (const meek))
+ :group 'man)
+
+(defcustom Man-frame-parameters nil
+ "*Frame parameter list for creating a new frame for a manual page."
+ :type 'sexp
+ :group 'man)
+
+(defcustom Man-downcase-section-letters-flag t
+ "*Letters in sections are converted to lower case.
+Some Un*x man commands can't handle uppercase letters in sections, for
+example \"man 2V chmod\", but they are often displayed in the manpage
+with the upper case letter. When this variable is t, the section
+letter (e.g., \"2V\") is converted to lowercase (e.g., \"2v\") before
+being sent to the man background process."
+ :type 'boolean
+ :group 'man)
+
+(defcustom Man-circular-pages-flag t
+ "*If t, the manpage list is treated as circular for traversal."
+ :type 'boolean
+ :group 'man)
+
+(defcustom Man-section-translations-alist
+ (list
+ '("3C++" . "3")
+ ;; Some systems have a real 3x man section, so let's comment this.
+ ;; '("3X" . "3") ; Xlib man pages
+ '("3X11" . "3")
+ '("1-UCB" . ""))
+ "*Association list of bogus sections to real section numbers.
+Some manpages (e.g. the Sun C++ 2.1 manpages) have section numbers in
+their references which Un*x `man' does not recognize. This
+association list is used to translate those sections, when found, to
+the associated section number."
+ :type '(repeat (cons (string :tag "Bogus Section")
+ (string :tag "Real Section")))
+ :group 'man)
+
+(defvar manual-program "man"
+ "The name of the program that produces man pages.")
+
+(defvar Man-untabify-command "pr"
+ "Command used for untabifying.")
+
+(defvar Man-untabify-command-args (list "-t" "-e")
+ "List of arguments to be passed to Man-untabify-command (which see).")
+
+(defvar Man-sed-command "sed"
+ "Command used for processing sed scripts.")
+
+(defvar Man-awk-command "awk"
+ "Command used for processing awk scripts.")
+
+(defvar Man-mode-line-format
+ '("-"
+ mode-line-mule-info
+ mode-line-modified
+ mode-line-frame-identification
+ mode-line-buffer-identification " "
+ global-mode-string
+ " " Man-page-mode-string
+ " %[(" mode-name mode-line-process minor-mode-alist "%n)%]--"
+ (line-number-mode "L%l--")
+ (column-number-mode "C%c--")
+ (-3 . "%p") "-%-")
+ "Mode line format for manual mode buffer.")
+
+(defvar Man-mode-map nil
+ "Keymap for Man mode.")
+
+(defvar Man-mode-hook nil
+ "Hook run when Man mode is enabled.")
+
+(defvar Man-cooked-hook nil
+ "Hook run after removing backspaces but before Man-mode processing.")
+
+(defvar Man-name-regexp "[-a-zA-Z0-9_][-a-zA-Z0-9_.]*"
+ "Regular expression describing the name of a manpage (without section).")
+
+(defvar Man-section-regexp "[0-9][a-zA-Z+]*\\|[LNln]"
+ "Regular expression describing a manpage section within parentheses.")
+
+(defvar Man-page-header-regexp
+ (concat "^[ \t]*\\(" Man-name-regexp
+ "(\\(" Man-section-regexp "\\))\\).*\\1")
+ "Regular expression describing the heading of a page.")
+
+(defvar Man-heading-regexp "^\\([A-Z][A-Z ]+\\)$"
+ "Regular expression describing a manpage heading entry.")
+
+(defvar Man-see-also-regexp "SEE ALSO"
+ "Regular expression for SEE ALSO heading (or your equivalent).
+This regexp should not start with a `^' character.")
+
+(defvar Man-first-heading-regexp "^[ \t]*NAME$\\|^[ \t]*No manual entry fo.*$"
+ "Regular expression describing first heading on a manpage.
+This regular expression should start with a `^' character.")
+
+(defvar Man-reference-regexp
+ (concat "\\(" Man-name-regexp "\\)(\\(" Man-section-regexp "\\))")
+ "Regular expression describing a reference in the SEE ALSO section.")
+
+(defvar Man-switches ""
+ "Switches passed to the man command, as a single string.")
+
+(defvar Man-specified-section-option
+ (if (string-match "-solaris[0-9.]*$" system-configuration)
+ "-s"
+ "")
+ "Option that indicates a specified a manual section name.")
+
+;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+;; end user variables
+\f
+;; other variables and keymap initializations
+(make-variable-buffer-local 'Man-sections-alist)
+(make-variable-buffer-local 'Man-refpages-alist)
+(make-variable-buffer-local 'Man-page-list)
+(make-variable-buffer-local 'Man-current-page)
+(make-variable-buffer-local 'Man-page-mode-string)
+(make-variable-buffer-local 'Man-original-frame)
+(make-variable-buffer-local 'Man-arguments)
+
+(setq-default Man-sections-alist nil)
+(setq-default Man-refpages-alist nil)
+(setq-default Man-page-list nil)
+(setq-default Man-current-page 0)
+(setq-default Man-page-mode-string "1 of 1")
+
+(defconst Man-sysv-sed-script "\
+/\b/ { s/_\b//g
+ s/\b_//g
+ s/o\b+/o/g
+ s/+\bo/o/g
+ :ovstrk
+ s/\\(.\\)\b\\1/\\1/g
+ t ovstrk
+ }
+/\e\\[[0-9][0-9]*m/ s///g"
+ "Script for sysV-like sed to nuke backspaces and ANSI codes from manpages.")
+
+(defconst Man-berkeley-sed-script "\
+/\b/ { s/_\b//g\\
+ s/\b_//g\\
+ s/o\b+/o/g\\
+ s/+\bo/o/g\\
+ :ovstrk\\
+ s/\\(.\\)\b\\1/\\1/g\\
+ t ovstrk\\
+ }\\
+/\e\\[[0-9][0-9]*m/ s///g"
+ "Script for berkeley-like sed to nuke backspaces and ANSI codes from manpages.")
+
+(defvar man-mode-syntax-table
+ (let ((table (copy-syntax-table (standard-syntax-table))))
+ (modify-syntax-entry ?. "w" table)
+ (modify-syntax-entry ?_ "w" table)
+ table)
+ "Syntax table used in Man mode buffers.")
+
+(if Man-mode-map
+ nil
+ (setq Man-mode-map (make-keymap))
+ (suppress-keymap Man-mode-map)
+ (define-key Man-mode-map " " 'scroll-up)
+ (define-key Man-mode-map "\177" 'scroll-down)
+ (define-key Man-mode-map "n" 'Man-next-section)
+ (define-key Man-mode-map "p" 'Man-previous-section)
+ (define-key Man-mode-map "\en" 'Man-next-manpage)
+ (define-key Man-mode-map "\ep" 'Man-previous-manpage)
+ (define-key Man-mode-map ">" 'end-of-buffer)
+ (define-key Man-mode-map "<" 'beginning-of-buffer)
+ (define-key Man-mode-map "." 'beginning-of-buffer)
+ (define-key Man-mode-map "r" 'Man-follow-manual-reference)
+ (define-key Man-mode-map "g" 'Man-goto-section)
+ (define-key Man-mode-map "s" 'Man-goto-see-also-section)
+ (define-key Man-mode-map "k" 'Man-kill)
+ (define-key Man-mode-map "q" 'Man-quit)
+ (define-key Man-mode-map "m" 'man)
+ (define-key Man-mode-map "\r" 'man-follow)
+ (define-key Man-mode-map "?" 'describe-mode)
+ )
+
+\f
+;; ======================================================================
+;; utilities
+
+(defun Man-init-defvars ()
+ "Used for initialising variables based on the value of window-system.
+This is necessary if one wants to dump man.el with emacs."
+
+ ;; The following is necessary until fonts are implemented on
+ ;; terminals.
+ (setq Man-fontify-manpage-flag (and Man-fontify-manpage-flag
+ window-system))
+
+ ;; Avoid possible error in call-process by using a directory that must exist.
+ (let ((default-directory "/"))
+ (setq Man-sed-script
+ (cond
+ (Man-fontify-manpage-flag
+ nil)
+ ((= 0 (call-process Man-sed-command nil nil nil Man-sysv-sed-script))
+ Man-sysv-sed-script)
+ ((= 0 (call-process Man-sed-command nil nil nil Man-berkeley-sed-script))
+ Man-berkeley-sed-script)
+ (t
+ nil))))
+
+ (setq Man-filter-list
+ (list
+ (cons
+ Man-sed-command
+ (list
+ (if Man-sed-script
+ (concat "-e '" Man-sed-script "'")
+ "")
+ "-e '/^[\001-\032][\001-\032]*$/d'"
+ "-e '/\e[789]/s///g'"
+ "-e '/Reformatting page. Wait/d'"
+ "-e '/Reformatting entry. Wait/d'"
+ "-e '/^[ \t]*Hewlett-Packard[ \t]Company[ \t]*-[ \t][0-9]*[ \t]-/d'"
+ "-e '/^[ \t]*Hewlett-Packard[ \t]*-[ \t][0-9]*[ \t]-.*$/d'"
+ "-e '/^[ \t][ \t]*-[ \t][0-9]*[ \t]-[ \t]*Formatted:.*[0-9]$/d'"
+ "-e '/^[ \t]*Page[ \t][0-9]*.*(printed[ \t][0-9\\/]*)$/d'"
+ "-e '/^Printed[ \t][0-9].*[0-9]$/d'"
+ "-e '/^[ \t]*X[ \t]Version[ \t]1[01].*Release[ \t][0-9]/d'"
+ "-e '/^[A-Za-z].*Last[ \t]change:/d'"
+ "-e '/^Sun[ \t]Release[ \t][0-9].*[0-9]$/d'"
+ "-e '/[ \t]*Copyright [0-9]* UNIX System Laboratories, Inc.$/d'"
+ "-e '/^[ \t]*Rev\\..*Page [0-9][0-9]*$/d'"
+ ))
+ (cons
+ Man-awk-command
+ (list
+ "'\n"
+ "BEGIN { blankline=0; anonblank=0; }\n"
+ "/^$/ { if (anonblank==0) next; }\n"
+ "{ anonblank=1; }\n"
+ "/^$/ { blankline++; next; }\n"
+ "{ if (blankline>0) { print \"\"; blankline=0; } print $0; }\n"
+ "'"
+ ))
+ (if (not Man-uses-untabify-flag)
+ (cons
+ Man-untabify-command
+ Man-untabify-command-args)
+ )))
+)
+
+(defsubst Man-match-substring (&optional n string)
+ "Return the substring matched by the last search.
+Optional arg N means return the substring matched by the Nth paren
+grouping. Optional second arg STRING means return a substring from
+that string instead of from the current buffer."
+ (if (null n) (setq n 0))
+ (if string
+ (substring string (match-beginning n) (match-end n))
+ (buffer-substring (match-beginning n) (match-end n))))
+
+(defsubst Man-make-page-mode-string ()
+ "Formats part of the mode line for Man mode."
+ (format "%s page %d of %d"
+ (or (nth 2 (nth (1- Man-current-page) Man-page-list))
+ "")
+ Man-current-page
+ (length Man-page-list)))
+
+(defsubst Man-build-man-command ()
+ "Builds the entire background manpage and cleaning command."
+ (let ((command (concat manual-program " " Man-switches
+ ; Stock MS-DOS shells cannot redirect stderr;
+ ; `call-process' below sends it to /dev/null,
+ ; so we don't need `2>' even with DOS shells
+ ; which do support stderr redirection.
+ (if (not (fboundp 'start-process))
+ " %s"
+ " %s 2>/dev/null")))
+ (flist Man-filter-list))
+ (while (and flist (car flist))
+ (let ((pcom (car (car flist)))
+ (pargs (cdr (car flist))))
+ (setq command
+ (concat command " | " pcom " "
+ (mapconcat '(lambda (phrase)
+ (if (not (stringp phrase))
+ (error "Malformed Man-filter-list"))
+ phrase)
+ pargs " ")))
+ (setq flist (cdr flist))))
+ command))
+
+(defun Man-translate-references (ref)
+ "Translates REF from \"chmod(2V)\" to \"2v chmod\" style.
+Leave it as is if already in that style. Possibly downcase and
+translate the section (see the Man-downcase-section-letters-flag
+and the Man-section-translations-alist variables)."
+ (let ((name "")
+ (section "")
+ (slist Man-section-translations-alist))
+ (cond
+ ;; "chmod(2V)" case ?
+ ((string-match (concat "^" Man-reference-regexp "$") ref)
+ (setq name (Man-match-substring 1 ref)
+ section (Man-match-substring 2 ref)))
+ ;; "2v chmod" case ?
+ ((string-match (concat "^\\(" Man-section-regexp
+ "\\) +\\(" Man-name-regexp "\\)$") ref)
+ (setq name (Man-match-substring 2 ref)
+ section (Man-match-substring 1 ref))))
+ (if (string= name "")
+ ref ; Return the reference as is
+ (if Man-downcase-section-letters-flag
+ (setq section (downcase section)))
+ (while slist
+ (let ((s1 (car (car slist)))
+ (s2 (cdr (car slist))))
+ (setq slist (cdr slist))
+ (if Man-downcase-section-letters-flag
+ (setq s1 (downcase s1)))
+ (if (not (string= s1 section)) nil
+ (setq section (if Man-downcase-section-letters-flag
+ (downcase s2)
+ s2)
+ slist nil))))
+ (concat Man-specified-section-option section " " name))))
+
+\f
+;; ======================================================================
+;; default man entry: get word under point
+
+(defsubst Man-default-man-entry ()
+ "Make a guess at a default manual entry.
+This guess is based on the text surrounding the cursor, and the
+default section number is selected from `Man-auto-section-alist'."
+ (let (word)