woman.el 166 KB
Newer Older
Eli Zaretskii's avatar
Eli Zaretskii committed
1 2
;;; woman.el --- browse UN*X manual pages `wo (without) man'

3 4
;; Copyright (C) 2000, 2002, 2003, 2004, 2005,
;;   2006 Free Software Foundation, Inc.
Eli Zaretskii's avatar
Eli Zaretskii committed
5

6
;; Author: Francis J. Wright <F.J.Wright@qmul.ac.uk>
7
;; Maintainer: FSF
8
;; Keywords: help, unix
Eli Zaretskii's avatar
Eli Zaretskii committed
9
;; Adapted-By: Eli Zaretskii <eliz@gnu.org>
10
;; Version: see `woman-version'
11
;; URL: http://centaur.maths.qmul.ac.uk/Emacs/WoMan/
Eli Zaretskii's avatar
Eli Zaretskii committed
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26

;; This file is part of GNU Emacs.

;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.  If not, write to the
Lute Kamstra's avatar
Lute Kamstra committed
27 28
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
Eli Zaretskii's avatar
Eli Zaretskii committed
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99

;;; Commentary:

;; WoMan implements a subset of the formatting performed by the Emacs
;; `man' (or `manual-entry') command to format a UN*X manual `page'
;; for display, but without calling any external programs.  It is
;; intended to emulate the whole of the -man macro package, plus those
;; ?roff requests that are most commonly used in man pages.  However,
;; the emulation is modified to include the reformatting done by the
;; Emacs `man' command.  No hyphenation is performed.

;; Advantages

;;   Much more direct, does not require any external programs.
;;   Supports completion on man page names.

;; Disadvantages

;;   Not a complete emulation.  Currently no support for eqn or tbl.
;;   Slightly slower for large man pages (but usually faster for
;;   small- and medium-size pages).

;; This browser works quite well on simple well-written man files.  It
;; works less well on idiosyncratic files that `break the rules' or
;; use the more obscure ?roff requests directly.  Current test results
;; are available in the file woman.status.

;; WoMan supports the use of compressed man files via
;; `auto-compression-mode' by turning it on if necessary.  But you may
;; need to adjust the user option `woman-file-compression-regexp'.

;; Read on for (currently) the only documentation for WoMan!

;; See also the documentation for the WoMan interactive commands and
;; user option variables, all of which begin with the prefix `woman-'.
;; This can be done most easily by loading WoMan and then running the
;; command `woman-mini-help', or selecting the WoMan menu option `Mini
;; Help' when WoMan is running.

;; WoMan is still under development!  Please let me know what doesn't
;; work -- I am adding and improving functionality as testing shows
;; that it is necessary.  See below for guidance on reporting bugs.

;; Recommended use
;; ===============

;; Put this in your .emacs:
;;   (autoload 'woman "woman"
;;             "Decode and browse a UN*X man page." t)
;;   (autoload 'woman-find-file "woman"
;;             "Find, decode and browse a specific UN*X man-page file." t)

;; Then either (1 -- *RECOMMENDED*): If the `MANPATH' environment
;; variable is set then WoMan will use it; otherwise you may need to
;; reset the Lisp variable `woman-manpath', and you may also want to
;; set the Lisp variable `woman-path'.  Please see the online
;; documentation for these variables.  Now you can execute the
;; extended command `woman', enter or select a manual entry topic,
;; using completion, and if necessary select a filename, using
;; completion.  By default, WoMan suggests the word nearest to the
;; cursor in the current buffer as the topic.

;; Or (2): Execute the extended command `woman-find-file' and enter a
;; filename, using completion.  This mode of execution may be useful
;; for temporary files outside the standard UN*X manual directory
;; structure.

;; Or (3): Put the next two sexpr's in your .emacs:
;; (autoload 'woman-dired-find-file "woman"
;;   "In dired, run the WoMan man-page browser on this file." t)
;; (add-hook 'dired-mode-hook
100 101
;;          (lambda ()
;;            (define-key dired-mode-map "W" 'woman-dired-find-file)))
Eli Zaretskii's avatar
Eli Zaretskii committed
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
;; and open the directory containing the man page file using dired,
;; put the cursor on the file, and press `W'.

;; In each case, the result should (!) be a buffer in Man mode showing
;; a formatted manual entry.  When called from WoMan, Man mode should
;; work as advertised, but modified where necessary in the context of
;; WoMan.  (However, `Man' will still invoke the standard Emacs
;; manual-browsing facility rather than `WoMan' -- this is
;; intentional!)

;; (By default, WoMan will automatically define the dired keys "W" and
;; "w" when it loads, but only if they are not already defined.  This
;; behaviour is controlled by the user option `woman-dired-keys'.
;; Note that the `dired-x' (dired extra) package binds
;; `dired-copy-filename-as-kill' to the key "w" (as pointed out by Jim
;; Davidson), although "W" appears to be really unused.  The `dired-x'
;; package will over-write the WoMan binding to "w", whereas (by
;; default) WoMan will not overwrite the `dired-x' binding.)

;; The following is based on suggestions by Guy Gascoigne-Piggford and
;; Juanma Barranquero.  If you really want to square the man-woman
;; circle then you might care to define the following bash function in
;; .bashrc:

;;   man() { gnudoit -q '(raise-frame (selected-frame)) (woman' \"$1\" ')' ; }

;; If you use Microsoft COMMAND.COM then you can create a file called
;; man.bat somewhere in your path containing the two lines:

;;   @echo off
;;   gnudoit -q (raise-frame (selected-frame)) (woman \"%1\")

;; and then (e.g. from a command prompt or the Run... option in the
;; Start menu) just execute

;;   man man_page_name


140 141
;; Using the word at point as the default topic
;; ============================================
Eli Zaretskii's avatar
Eli Zaretskii committed
142

143 144 145 146 147 148
;; The `woman' command uses the word nearest to point in the current
;; buffer as the default topic to look up if it matches the name of a
;; manual page installed on the system.  The default topic can also be
;; used without confirmation by setting the user-option
;; `woman-use-topic-at-point' to t; thanks to Benjamin Riefenstahl for
;; suggesting this functionality.
Eli Zaretskii's avatar
Eli Zaretskii committed
149

150 151
;; The variable `woman-use-topic-at-point' can be rebound locally,
;; which may be useful to provide special private key bindings, e.g.
Eli Zaretskii's avatar
Eli Zaretskii committed
152 153

;;  (global-set-key "\C-cw"
154
;;  		  (lambda ()
Eli Zaretskii's avatar
Eli Zaretskii committed
155
;;  		    (interactive)
156
;;  		    (let ((woman-use-topic-at-point t))
Eli Zaretskii's avatar
Eli Zaretskii committed
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
;;  		      (woman)))))


;; Customization, Hooks and Imenu
;; ==============================

;; WoMan supports the GNU Emacs 20+ customization facility, and puts
;; a customization group called `WoMan' in the `Help' group under the
;; top-level `Emacs' group.  In order to be able to customize WoMan
;; without first loading it, add the following sexp to your .emacs:

;;  (defgroup woman nil
;;     "Browse UNIX manual pages `wo (without) man'."
;;     :tag "WoMan" :group 'help :load "woman")


;; WoMan currently runs two hooks: `woman-pre-format-hook' immediately
;; before formatting a buffer and `woman-post-format-hook' immediately
;; after formatting a buffer.  These hooks can be used for special
;; customizations that require code to be executed, etc., although
;; most customization should be possible by setting WoMan user option
;; variables, e.g. in `.emacs' and should NOT require the use of the
;; hooks.  `woman-pre-format-hook' might be appropriate for face
;; customization, whereas `woman-post-format-hook' might be
;; appropriate for installing a dynamic menu using `imenu' (although
;; it is better to use the built-in WoMan imenu support).

;; The WoMan menu provides an option to make a contents menu for the
;; current man page (using imenu).  Alternatively, if you set the
;; variable `woman-imenu' to `t' then WoMan will do it automatically
;; for every man page.  The menu title is the value of the variable
;; `woman-imenu-title', which is "CONTENTS" by default.  By default,
;; the menu shows manual sections and subsections, but you can change
;; this by changing the value of `woman-imenu-generic-expression'.
;; This facility is not yet widely tested and may be fooled by obscure
;; man pages that `break the rules'.

;; WoMan is configured not to replace spaces in an imenu *Completion*
;; buffer.  For further documentation of the use of imenu, such as
;; menu sorting, see the source file imenu.el, which is distributed
;; with GNU Emacs.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; Howard Melman made (essentially) the following suggestions, which
;; are slightly different from the expression that I currently use.
;; You may prefer one of Howard's suggestions, which I think assume
;; that `case-fold-search' is `t' (which it is by default):

;; (setq woman-imenu-generic-expression
;;       '((nil "^\\(   \\)?\\([A-Z][A-Z ]+[A-Z]\\)[ \t]*$" 2)))

;; will give support for .SH and .SS, though it won't show the heading
;; name hierarchy.  If you just want .SH in the imenu then use:

;; (setq woman-imenu-generic-expression
;;       '((nil "^\\([A-Z][A-Z ]+[A-Z]\\)[ \t]*$" 1)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;; Vertical spacing and blank lines
;; ================================

;; The number of consecutive blank lines in the formatted buffer
;; should be either 0 or 1.  A blank line should leave a space like
;; .sp 1 (p. 14).  Current policy is to output vertical space only
;; immediately before text is output.


;; Horizontal and vertical spacing and resolution
;; ==============================================

;; WoMan currently assumes 10 characters per inch horizontally, hence
;; a horizontal resolution of 24 basic units, and 5 lines per inch
;; vertically, hence a vertical resolution of 48 basic units.  (nroff
;; uses 240 per inch).


;; The *WoMan-Log* buffer
;; ======================

;; This is modelled on the byte-compiler.  It logs all files formatted
;; by WoMan, and if WoMan finds anything that it cannot handle then it
;; writes a warning to this buffer.  If the variable `woman-show-log'
;; is non-nil (by default it is `nil') then WoMan automatically
;; displays this buffer.  Many WoMan warnings can be completely
;; ignored, because they are reporting the fact that WoMan has ignored
;; requests that it is correct to ignore.  In some future version this
;; level of paranoia will be reduced, but not until WoMan is more
;; reliable.  At present, all warnings should be treated with some
;; suspicion.  Uninterpreted escape sequences are also logged (in some
;; cases).

;; Uninterpreted ?roff requests can optionally be left in the
;; formatted buffer to indicate precisely where they occur by
;; resetting the variable `woman-ignore' to `nil' (by default it is
;; `t').

;; Automatic initiation of woman decoding

;; (Probably not a good idea.  If you use it, be careful!)

;; Put something like this in your .emacs.  The call to
;; set-visited-file-name is to avoid font-locking triggered by
;; automatic major mode selection.

;; (autoload 'woman-decode-region "woman")

;; (setq format-alist
;;       (cons
;;        '(man "UN*X man-page source format" "\\.\\(TH\\|ig\\) "
;; 	     woman-decode-region nil nil
270 271 272
;; 	     (lambda (arg)
;; 		set-visited-file-name
;; 		(file-name-sans-extension buffer-file-name)))))
Eli Zaretskii's avatar
Eli Zaretskii committed
273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331
;;       format-alist))


;; Reporting Bugs
;; ==============

;; If WoMan fails completely, or formats a file incorrectly
;; (i.e. obviously wrongly or significantly differently from man) or
;; inelegantly, then please

;; (a) check that you are running the latest version of woman.el
;;     available from my web site (see above),

;; (b) check that the problem is not already described in the file
;;     woman.status, also available from my web site.

;; If both of the above are true then please email me the entry from
;; the *WoMan-Log* buffer relating to the problem file, together with
;; a brief description of the problem.  Please indicate where you got
;; the source file from, but do not send it to me unless I ask you to!
;; Thanks.  (There is at present no automated bug-reporting facility
;; for WoMan.)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

;; NOTE:

;; CASE-DEPENDENCE OF FILENAMES.  By default, WoMan ignores case in
;; file pathnames only when it seems appropriate.  MS-Windows users
;; who want complete case independence should set the NTEmacs variable
;; `w32-downcase-file-names' to `t' and use all lower case when
;; setting WoMan file paths.

;; (1) INCOMPATIBLE CHANGE!  WoMan no longer uses a persistent topic
;; cache by default.  (It caused too much confusion!)  Explicitly set
;; the variable `woman-cache-filename' to save the cache between Emacs
;; sessions.  This is recommended only if the command `woman' is too
;; slow the first time that it is run in an Emacs session, while it
;; builds its cache in main memory, which MAY be VERY slow.

;; (2) The user option `woman-cache-level' controls the amount of
;; information cached (in main memory and, optionally, saved to disc).

;; (3) UPDATING THE CACHE.  A prefix argument always causes the
;; `woman' command (only) to rebuild its topic cache, and to re-save
;; it to `woman-cache-filename' if this variable has a non-nil value.
;; This is necessary if the NAMES (not contents) of any of the
;; directories or files in the paths specified by `woman-manpath' or
;; `woman-path' change.  If WoMan user options that affect the cache
;; are changed then WoMan will automatically update its cache file on
;; disc (if one is in use) the next time it is run in a new Emacs
;; session.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;; TO DO
;; =====

332
;; Reconsider case sensitivity of file names.
Eli Zaretskii's avatar
Eli Zaretskii committed
333 334 335 336
;; MUST PROCESS .if, .nr IN ORDER ENCOUNTERED IN FILE! (rcsfile, mf).
;; Allow general delimiter in `\v', cf. `\h'.
;; Improve major-mode documentation.
;; Pre-process conditionals in macro bodies if possible for speed?
337 338
;; Emulate more complete preprocessor support for tbl (.TS/.TE)
;; Emulate some preprocessor support for eqn (.EQ/.EN)
Eli Zaretskii's avatar
Eli Zaretskii committed
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
;; Re-write filling and adjusting code!
;; Allow word wrap at comma (for long option lists)?
;; Buffer list handling not quite right.
;; Make 10 or 12 pitch (cpi) optional -- 12 => ll = 78
;; Use unpaddable space for tabbing?
;; Tidy up handling of fonts when filling and adjusting
;;   -- see text/text properties?
;; Improve speed
;; Add font-lock support (for quoted strings, etc.)?
;; Optionally save large files in enriched format?
;; Add apropos facility by searching NAME (?) entry in man files?
;; Documentation -- optional auto-display of formatted WoMan man page?
;; Implement a bug reporter?
;; Support diversion and traps (to some extent) - for Tcl/tk pages?
;; Add a menu of WoMan buffers?
354
;; Fix .fc properly?
Eli Zaretskii's avatar
Eli Zaretskii committed
355 356 357 358 359 360 361 362 363 364 365 366 367 368


;; Implementation strategy [this description is now well out of date!]
;; -- three main passes, each to process respectively:

;;   1) non-breaking `.' requests including font macros
;;   2) \ escape sequences, mainly special characters and font changes
;;   3) breaking `.' requests, mainly filling and justification

;; For each pass, a control function finds and pre-processes the
;; escape or request and then calls the appropriate function to
;; perform the required formatting.  Based originally on enriched.el
;; and format.el.

369 370 371
;; The background information that made this project possible is
;; freely available courtesy of Bell Labs from
;; http://cm.bell-labs.com/7thEdMan/
Eli Zaretskii's avatar
Eli Zaretskii committed
372 373 374 375 376 377 378 379 380 381 382 383


;; Acknowledgements
;; ================

;; For Heather, Kathryn and Madelyn, the women in my life
;; (although they will probably never use it)!

;; I also thank the following for helpful suggestions, bug reports,
;; code fragments, general interest, etc.:
;;   Jari Aalto <jari.aalto@cs.tpu.fi>
;;   Dean Andrews <dean@dra.com>
384
;;   Juanma Barranquero <lekktu@gmail.com>
Eli Zaretskii's avatar
Eli Zaretskii committed
385 386
;;   Karl Berry <kb@cs.umb.edu>
;;   Jim Chapman <jchapman@netcomuk.co.uk>
387
;;   Kin Cho <kin@neoscale.com>
Eli Zaretskii's avatar
Eli Zaretskii committed
388 389 390 391 392 393 394 395 396 397 398 399 400 401
;;   Frederic Corne <frederic.corne@erli.fr>
;;   Peter Craft <craft@alacritech.com>
;;   Charles Curley <ccurley@trib.com>
;;   Jim Davidson <jdavidso@teknowledge.com>
;;   Kevin D'Elia <Kevin.DElia@mci.com>
;;   John Fitch <jpff@maths.bath.ac.uk>
;;   Hans Frosch <jwfrosch@rish.b17c.ingr.com>
;;   Guy Gascoigne-Piggford <ggp@informix.com>
;;   Brian Gorka <gorkab@sanchez.com>
;;   Nicolai Henriksen <nhe@lyngso-industri.dk>
;;   Thomas Herchenroeder <the@software-ag.de>
;;   Alexander Hinds <ahinds@thegrid.net>
;;   Stefan Hornburg <sth@hacon.de>
;;   Theodore Jump <tjump@cais.com>
402
;;   David Kastrup <dak@gnu.org>
Eli Zaretskii's avatar
Eli Zaretskii committed
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417
;;   Paul Kinnucan <paulk@mathworks.com>
;;   Jonas Linde <jonas@init.se>
;;   Andrew McRae <andrewm@optimation.co.nz>
;;   Howard Melman <howard@silverstream.com>
;;   Dennis Pixton <dennis@math.binghamton.edu>
;;   T. V. Raman <raman@Adobe.COM>
;;   Bruce Ravel <bruce.ravel@nist.gov>
;;   Benjamin Riefenstahl <benny@crocodial.de>
;;   Kevin Ruland <kruland@seistl.com>
;;   Tom Schutter <tom@platte.com>
;;   Wei-Xue Shi <wxshi@ma.neweb.ne.jp>
;;   Fabio Somenzi <fabio@joplin.colorado.edu>
;;   Karel Sprenger <ks@ic.uva.nl>
;;   Chris Szurgot <szurgot@itribe.net>
;;   Paul A. Thompson <pat@po.cwru.edu>
418
;;   Arrigo Triulzi <arrigo@maths.qmw.ac.uk>
Eli Zaretskii's avatar
Eli Zaretskii committed
419
;;   Geoff Voelker <voelker@cs.washington.edu>
Eli Zaretskii's avatar
Eli Zaretskii committed
420
;;   Eli Zaretskii <eliz@gnu.org>
Eli Zaretskii's avatar
Eli Zaretskii committed
421 422 423 424


;;; Code:

425 426
(defvar woman-version "0.551 (beta)" "WoMan version information.")

Eli Zaretskii's avatar
Eli Zaretskii committed
427
(require 'man)
428
(require 'button)
429
(define-button-type 'WoMan-xref-man-page
430
  :supertype 'Man-abstract-xref-man-page
431 432 433 434 435
  'func (lambda (arg)
	  (woman
	   ;; `woman' cannot deal with arguments that contain a
	   ;; section name, like close(2), so strip the section name.
	   (if (string-match Man-reference-regexp arg)
436
	       (substring arg 0 (match-end 1))
437
	     arg))))
438

Eli Zaretskii's avatar
Eli Zaretskii committed
439 440 441 442
(eval-when-compile			; to avoid compiler warnings
  (require 'dired)
  (require 'apropos))

443
(defun woman-mapcan (fn x)
444
  "Return concatenated list of FN applied to successive `car' elements of X.
445 446
FN must return a list, cons or nil.  Useful for splicing into a list."
  ;; Based on the Standard Lisp function MAPCAN but with args swapped!
447 448
  ;; More concise implementation than the recursive one.  -- dak
  (apply #'nconc (mapcar fn x)))
449

450 451 452
(defun woman-parse-colon-path (paths)
  "Explode search path string PATHS into a list of directory names.
Allow Cygwin colon-separated search paths on Microsoft platforms.
453
Replace null components by calling `woman-parse-man.conf'.
454 455
As a special case, if PATHS is nil then replace it by calling
`woman-parse-man.conf'."
456
  ;; Based on suggestions by Jari Aalto and Eli Zaretskii.
457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505
  ;; parse-colon-path returns nil for a null path component and
  ;; an empty substring of MANPATH denotes the default list.
  (if (memq system-type '(windows-nt ms-dos))
      (cond ((null paths)
	     (mapcar 'woman-Cyg-to-Win (woman-parse-man.conf)))
	    ((string-match ";" paths)
	     ;; Assume DOS-style path-list...
	     (woman-mapcan		; splice list into list
	      (lambda (x)
		(if x
		    (list x)
		  (mapcar 'woman-Cyg-to-Win (woman-parse-man.conf))))
	      (parse-colon-path paths)))
	    ((string-match "\\`[a-zA-Z]:" paths)
	     ;; Assume single DOS-style path...
	     paths)
	    (t
	     ;; Assume UNIX/Cygwin-style path-list...
	     (woman-mapcan		; splice list into list
	      (lambda (x)
		(mapcar 'woman-Cyg-to-Win
			(if x (list x) (woman-parse-man.conf))))
	      (let ((path-separator ":"))
		(parse-colon-path paths)))))
    ;; Assume host-default-style path-list...
    (woman-mapcan			; splice list into list
     (lambda (x) (if x (list x) (woman-parse-man.conf)))
     (parse-colon-path (or paths "")))))

(defun woman-Cyg-to-Win (file)
  "Convert an absolute filename FILE from Cygwin to Windows form."
  ;; Code taken from w32-symlinks.el
  (if (eq (aref file 0) ?/)
      ;; Try to use Cygwin mount table via `cygpath.exe'.
      (condition-case nil
	  (with-temp-buffer
	    ;; cygpath -m file
	    (call-process "cygpath" nil t nil "-m" file)
	    (buffer-substring 1 (buffer-size)))
	(error
	 ;; Assume no `cygpath' program available.
	 ;; Hack /cygdrive/x/ or /x/ or (obsolete) //x/ to x:/
	 (when (string-match "\\`\\(/cygdrive\\|/\\)?/./" file)
	   (if (match-string 1)		; /cygdrive/x/ or //x/ -> /x/
	       (setq file (substring file (match-end 1))))
	   (aset file 0 (aref file 1))	; /x/ -> xx/
	   (aset file 1 ?:))		; xx/ -> x:/
	 file))
    file))
Eli Zaretskii's avatar
Eli Zaretskii committed
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542


;;; User options:

;; NB: Group identifiers must be lowercase!

(defgroup woman nil
  "Browse UNIX manual pages `wo (without) man'."
  :tag "WoMan"
  :group 'help)

(defcustom woman-show-log nil
  "*If non-nil then show the *WoMan-Log* buffer if appropriate.
I.e. if any warning messages are written to it.  Default is nil."
  :type 'boolean
  :group 'woman)

(defcustom woman-pre-format-hook nil
  "*Hook run by WoMan immediately before formatting a buffer.
Change only via `Customization' or the function `add-hook'."
  :type 'hook
  :group 'woman)

(defcustom woman-post-format-hook nil
  "*Hook run by WoMan immediately after formatting a buffer.
Change only via `Customization' or the function `add-hook'."
  :type 'hook
  :group 'woman)


;; Interface options

(defgroup woman-interface nil
  "Interface options for browsing UNIX manual pages `wo (without) man'."
  :tag "WoMan Interface"
  :group 'woman)

543
(defcustom woman-man.conf-path
544 545 546 547
  (let ((path '("/usr/lib" "/etc")))
    (if (eq system-type 'windows-nt)
	(mapcar 'woman-Cyg-to-Win path)
      path))
548
  "*List of dirs to search and/or files to try for man config file.
549 550 551
A trailing separator (`/' for UNIX etc.) on directories is optional,
and the filename is used if a directory specified is the first to
contain the strings \"man\" and \".conf\" (in that order).
552 553 554 555 556 557
If MANPATH is not set but a config file is found then it is parsed
instead to provide a default value for `woman-manpath'."
  :type '(repeat string)
  :group 'woman-interface)

(defun woman-parse-man.conf ()
558
  "Parse if possible configuration file for man command.
559
Used only if MANPATH is not set or contains null components.
560 561
Look in `woman-man.conf-path' and return a value for `woman-manpath'.
Concatenate data from all lines in the config file of the form
562
  MANPATH  /usr/man
563
or
564 565 566
  MANDATORY_MANPATH  /usr/man
or
  OPTIONAL_MANPATH  /usr/man"
567 568 569 570 571 572 573 574 575 576 577
  ;; Functionality suggested by Charles Curley.
  (let ((path woman-man.conf-path)
	file manpath)
    (while (and
	    path
	    (not (and
		  (file-readable-p (setq file (car path)))
		  ;; If not a file then find the file:
		  (or (not (file-directory-p file))
		      (and
		       (setq file
578
			     (directory-files file t "man.*\\.conf" t))
579 580 581 582 583
		       (file-readable-p (setq file (car file)))))
		  ;; Parse the file -- if no MANPATH data ignore it:
		  (with-temp-buffer
		    (insert-file-contents file)
		    (while (re-search-forward
584 585
			    ;; `\(?: ... \)' is a "shy group"
			    "\
586
^[ \t]*\\(?:MANDATORY_\\|OPTIONAL_\\)?MANPATH[ \t]+\\(\\S-+\\)" nil t)
587
		      (setq manpath (cons (match-string 1) manpath)))
588 589 590 591 592
		    manpath))
		 ))
      (setq path (cdr path)))
    (nreverse manpath)))

Eli Zaretskii's avatar
Eli Zaretskii committed
593
(defcustom woman-manpath
594 595
  (or (woman-parse-colon-path (getenv "MANPATH"))
      '("/usr/man" "/usr/share/man" "/usr/local/man"))
Eli Zaretskii's avatar
Eli Zaretskii committed
596 597 598 599
  "*List of DIRECTORY TREES to search for UN*X manual files.
Each element should be the name of a directory that contains
subdirectories of the form `man?', or more precisely subdirectories
selected by the value of `woman-manpath-man-regexp'.  Non-directory
600 601 602 603
and unreadable files are ignored.

If not set then the environment variable MANPATH is used.  If no such
environment variable is found, the default list is determined by
604 605 606
consulting the man configuration file if found, which is determined by
the user option `woman-man.conf-path'.  An empty substring of MANPATH
denotes the default list.
Eli Zaretskii's avatar
Eli Zaretskii committed
607

608
Any environment variables (names must have the UN*X-style form $NAME,
609
e.g. $HOME, $EMACSDATA, $emacs_dir) are evaluated first but each
Eli Zaretskii's avatar
Eli Zaretskii committed
610 611 612 613 614 615
element must evaluate to a SINGLE directory name.  Trailing `/'s are
ignored.  (Specific directories in `woman-path' are also searched.)

Microsoft platforms:
I recommend including drive letters explicitly, e.g.

616
  (\"C:/Cygwin/usr/man/\" \"C:/Cygwin/usr/local/man\").
Eli Zaretskii's avatar
Eli Zaretskii committed
617 618

The MANPATH environment variable may be set using DOS semi-colon-
619
separated or UN*X/Cygwin colon-separated syntax (but not mixed)."
Eli Zaretskii's avatar
Eli Zaretskii committed
620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
  :type '(repeat string)
  :group 'woman-interface)

(defcustom woman-manpath-man-regexp "[Mm][Aa][Nn]"
  "Regexp to match man directories UNDER `woman-manpath' directories.
These normally have names of the form `man?'.  Its default value is
\"[Mm][Aa][Nn]\", which is case-insensitive mainly for the benefit of
Microsoft platforms.  Its purpose is to avoid `cat?', `.', `..', etc."
  ;; Based on a suggestion by Wei-Xue Shi.
  :type 'string
  :group 'woman-interface)

(defcustom woman-path
  (if (eq system-type 'ms-dos) '("$DJDIR/info" "$DJDIR/man/cat[1-9onlp]"))
  "*List of SPECIFIC DIRECTORIES to search for UN*X manual files.
For example

  (\"/emacs/etc\").

These directories are searched in addition to the directory trees
specified in `woman-manpath'.  Each element should be a directory
string or nil, which represents the current directory when the path is
expanded and cached.  However, the last component (only) of each
directory string is treated as a regexp \(Emacs, not shell) and the
string is expanded into a list of matching directories.  Non-directory
and unreadable files are ignored.  The default value is nil.

Any environment variables (which must have the UN*X-style form $NAME,
648
e.g. $HOME, $EMACSDATA, $emacs_dir) are evaluated first but each
Eli Zaretskii's avatar
Eli Zaretskii committed
649 650 651
element must evaluate to a SINGLE directory name (regexp, see above).
For example

652
  (\"$EMACSDATA\") [or equivalently (\"$emacs_dir/etc\")].
Eli Zaretskii's avatar
Eli Zaretskii committed
653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710

Trailing `/'s are discarded.  (The directory trees in `woman-manpath'
are also searched.)  On Microsoft platforms I recommend including
drive letters explicitly."
  :type '(repeat (choice string (const nil)))
  :group 'woman-interface)

(defcustom woman-cache-level 2
  "*The level of topic caching.
1 - cache only the topic and directory lists
    (the only level before version 0.34 - only for compatibility);
2 - cache also the directories for each topic
    (faster, without using much more memory);
3 - cache also the actual filenames for each topic
    (fastest, but uses twice as much memory).
The default value is currently 2, a good general compromise.
If the `woman' command is slow to find files then try 3, which may be
particularly beneficial with large remote-mounted man directories.
Run the `woman' command with a prefix argument or delete the cache
file `woman-cache-filename' for a change to take effect.
\(Values < 1 behave like 1; values > 3 behave like 3.)"
  :type '(choice (const :tag "Minimal" 1)
		 (const :tag "Default" 2)
		 (const :tag "Maximal" 3))
  :group 'woman-interface)

(defcustom woman-cache-filename nil
  "*The full pathname of the WoMan directory and topic cache file.
It is used to save and restore the cache between sessions.  This is
especially useful with remote-mounted man page files!  The default
value of nil suppresses this action.  The `standard' non-nil
filename is \"~/.wmncach.el\".  Remember that a prefix argument forces
the `woman' command to update and re-write the cache."
  :type '(choice (const :tag "None" nil)
		 (const :tag "~/.wmncach.el" "~/.wmncach.el")
		 file)
  :group 'woman-interface)

(defcustom woman-dired-keys t
  "*List of `dired' mode keys to define to run WoMan on current file.
E.g. '(\"w\" \"W\"), or any non-null atom to automatically define
\"w\" and \"W\" if they are unbound, or nil to do nothing.
Default is t."
  :type '(choice (const :tag "None" nil)
		 (repeat string)
		 (other :tag "Auto" t))
  :group 'woman-interface)

(defcustom woman-imenu-generic-expression
  '((nil "\n\\([A-Z].*\\)" 1) ; SECTION, but not TITLE
    ("*Subsections*" "^   \\([A-Z].*\\)" 1))
  "*Imenu support for Sections and Subsections.
An alist with elements of the form (MENU-TITLE REGEXP INDEX) --
see the documentation for `imenu-generic-expression'."
  :type 'sexp
  :group 'woman-interface)

(defcustom woman-imenu nil
711 712
  "*If non-nil then WoMan adds a Contents menu to the menubar.
It does this by calling `imenu-add-to-menubar'.  Default is nil."
Eli Zaretskii's avatar
Eli Zaretskii committed
713 714 715 716 717 718 719 720 721
  :type 'boolean
  :group 'woman-interface)

(defcustom woman-imenu-title "CONTENTS"
  "*The title to use if WoMan adds a Contents menu to the menubar.
Default is \"CONTENTS\"."
  :type 'string
  :group 'woman-interface)

722 723 724
(defcustom woman-use-topic-at-point-default nil
  ;; `woman-use-topic-at-point' may be let-bound when woman is loaded,
  ;; in which case its global value does not get defined.
Eli Zaretskii's avatar
Eli Zaretskii committed
725
  ;; `woman-file-name' sets it to this value if it is unbound.
726
  "*Default value for `woman-use-topic-at-point'."
Eli Zaretskii's avatar
Eli Zaretskii committed
727
  :type '(choice (const :tag "Yes" t)
728
		 (const :tag "No" nil))
Eli Zaretskii's avatar
Eli Zaretskii committed
729 730
  :group 'woman-interface)

731 732 733 734
(defcustom woman-use-topic-at-point woman-use-topic-at-point-default
  "*Control use of the word at point as the default topic.
If non-nil the `woman' command uses the word at point automatically,
without interactive confirmation, if it exists as a topic."
Eli Zaretskii's avatar
Eli Zaretskii committed
735
  :type '(choice (const :tag "Yes" t)
736
		 (const :tag "No" nil))
Eli Zaretskii's avatar
Eli Zaretskii committed
737 738 739 740 741 742 743 744 745 746 747 748 749
  :group 'woman-interface)

(defvar woman-file-regexp nil
  "Regexp used to select (possibly compressed) man source files, e.g.
\"\\.\\([0-9lmnt]\\w*\\)\\(\\.\\(g?z\\|bz2\\)\\)?\\'\".
Built automatically from the customizable user options
`woman-uncompressed-file-regexp' and `woman-file-compression-regexp'.")

(defvar woman-uncompressed-file-regexp)	; for the compiler
(defvar woman-file-compression-regexp)	; for the compiler

(defun set-woman-file-regexp (symbol value)
  "Bind SYMBOL to VALUE and set `woman-file-regexp' as per user customizations.
750
Used as :set cookie by Customize when customizing the user options
Eli Zaretskii's avatar
Eli Zaretskii committed
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779
`woman-uncompressed-file-regexp' and `woman-file-compression-regexp'."
  (set-default symbol value)
  (and (boundp 'woman-uncompressed-file-regexp)
       (boundp 'woman-file-compression-regexp)
       (setq woman-file-regexp
	     (concat woman-uncompressed-file-regexp
		     "\\("
		     (substring woman-file-compression-regexp 0 -2)
		     "\\)?\\'"))))

(defcustom woman-uncompressed-file-regexp
  "\\.\\([0-9lmnt]\\w*\\)"		; disallow no extension
  "*Do not change this unless you are sure you know what you are doing!
Regexp used to select man source files (ignoring any compression extension).

The SysV standard man pages use two character suffixes, and this is
becoming more common in the GNU world.  For example, the man pages
in the ncurses package include `toe.1m', `form.3x', etc.

Note: an optional compression regexp will be appended, so this regexp
MUST NOT end with any kind of string terminator such as $ or \\'."
  :type 'regexp
  :set 'set-woman-file-regexp
  :group 'woman-interface)

(defcustom woman-file-compression-regexp
  "\\.\\(g?z\\|bz2\\)\\'"
  "*Do not change this unless you are sure you know what you are doing!
Regexp used to match compressed man file extensions for which
780
decompressors are available and handled by auto-compression mode,
Eli Zaretskii's avatar
Eli Zaretskii committed
781 782
e.g. \"\\\\.\\\\(g?z\\\\|bz2\\\\)\\\\'\" for `gzip' or `bzip2'.
Should begin with \\. and end with \\' and MUST NOT be optional."
783 784 785 786
  ;; Should be compatible with car of
  ;; `jka-compr-file-name-handler-entry', but that is unduly
  ;; complicated, includes an inappropriate extension (.tgz) and is
  ;; not loaded by default!
Eli Zaretskii's avatar
Eli Zaretskii committed
787 788 789 790
  :type 'regexp
  :set 'set-woman-file-regexp
  :group 'woman-interface)

791 792 793
(defcustom woman-use-own-frame		; window-system
  (or (and (fboundp 'display-graphic-p) (display-graphic-p)) ; Emacs 21
      (memq window-system '(x w32)))	; Emacs 20
794 795 796 797 798
  "*If non-nil then use a dedicated frame for displaying WoMan windows.
Only useful when run on a graphic display such as X or MS-Windows."
  :type 'boolean
  :group 'woman-interface)

Eli Zaretskii's avatar
Eli Zaretskii committed
799 800 801 802 803 804 805 806 807 808 809 810 811 812 813

;; Formatting options

(defgroup woman-formatting nil
  "Formatting options for browsing UNIX manual pages `wo (without) man'."
  :tag "WoMan Formatting"
  :group 'woman)

(defcustom woman-fill-column 65
  "*Right margin for formatted text -- default is 65."
  :type 'integer
  :group 'woman-formatting)

(defcustom woman-fill-frame nil
  ;; Based loosely on a suggestion by Theodore Jump:
Juri Linkov's avatar
Juri Linkov committed
814
  "*If non-nil then most of the window width is used."
Eli Zaretskii's avatar
Eli Zaretskii committed
815 816 817 818 819
  :type 'boolean
  :group 'woman-formatting)

(defcustom woman-default-indent 5
  "*Default prevailing indent set by -man macros -- default is 5.
820
Set this variable to 7 to emulate GNU man formatting."
Eli Zaretskii's avatar
Eli Zaretskii committed
821 822 823 824 825
  :type 'integer
  :group 'woman-formatting)

(defcustom woman-bold-headings t
  "*If non-nil then embolden section and subsection headings.  Default is t.
826
Heading emboldening is NOT standard `man' behavior."
Eli Zaretskii's avatar
Eli Zaretskii committed
827 828 829 830
  :type 'boolean
  :group 'woman-formatting)

(defcustom woman-ignore t
831
  "*If non-nil then unrecognized requests etc. are ignored.  Default is t.
832
This gives the standard ?roff behavior.  If nil then they are left in
Eli Zaretskii's avatar
Eli Zaretskii committed
833 834 835 836
the buffer, which may aid debugging."
  :type 'boolean
  :group 'woman-formatting)

837 838 839 840 841 842 843 844 845
(defcustom woman-preserve-ascii t
  "*If non-nil, preserve ASCII characters in the WoMan buffer.
Otherwise, to save time, some backslashes and spaces may be
represented differently (as the values of the variables
`woman-escaped-escape-char' and `woman-unpadded-space-char'
respectively) so that the buffer content is strictly wrong even though
it should display correctly.  This should be irrelevant unless the
buffer text is searched, copied or saved to a file."
  ;; This option should probably be removed!
Eli Zaretskii's avatar
Eli Zaretskii committed
846 847 848
  :type 'boolean
  :group 'woman-formatting)

849 850 851 852 853 854 855
(defcustom woman-emulation 'nroff
  "*WoMan emulation, currently either nroff or troff.  Default is nroff.
Troff emulation is experimental and largely untested.
\(Add groff later?)"
  :type '(choice (const nroff) (const troff))
  :group 'woman-formatting)

Eli Zaretskii's avatar
Eli Zaretskii committed
856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874

;; Faces:

(defgroup woman-faces nil
  "Face options for browsing UNIX manual pages `wo (without) man'."
  :tag "WoMan Faces"
  :group 'woman
  :group 'faces)

(defcustom woman-fontify
  (or (and (fboundp 'display-color-p) (display-color-p))
      (and (fboundp 'display-graphic-p) (display-graphic-p))
      (x-display-color-p))
  "*If non-nil then WoMan assumes that face support is available.
It defaults to a non-nil value if the display supports either colors
or different fonts."
  :type 'boolean
  :group 'woman-faces)

875 876 877
;; This is overkill!  Troff uses just italic; Nroff uses just underline.
;; You should probably select either italic or underline as you prefer, but
;; not both, although italic and underline work together perfectly well!
878
(defface woman-italic
879
  `((((min-colors 88) (background light))
880 881
     (:slant italic :underline t :foreground "red1"))
    (((background light)) (:slant italic :underline t :foreground "red"))
882
    (((background dark)) (:slant italic :underline t)))
883
  "Face for italic font in man pages."
Eli Zaretskii's avatar
Eli Zaretskii committed
884
  :group 'woman-faces)
885 886
;; backward-compatibility alias
(put 'woman-italic-face 'face-alias 'woman-italic)
Eli Zaretskii's avatar
Eli Zaretskii committed
887

888
(defface woman-bold
889 890
  '((((min-colors 88) (background light)) (:weight bold :foreground "blue1"))
    (((background light)) (:weight bold :foreground "blue"))
891
    (((background dark)) (:weight bold :foreground "green2")))
892
  "Face for bold font in man pages."
Eli Zaretskii's avatar
Eli Zaretskii committed
893
  :group 'woman-faces)
894 895
;; backward-compatibility alias
(put 'woman-bold-face 'face-alias 'woman-bold)
Eli Zaretskii's avatar
Eli Zaretskii committed
896

897 898 899
;; Brown is a good compromise: it is distinguishable from the default
;; but not enough so to make font errors look terrible.  (Files that use
;; non-standard fonts seem to do so badly or in idiosyncratic ways!)
900
(defface woman-unknown
901
  '((((background light)) (:foreground "brown"))
902
    (((min-colors 88) (background dark)) (:foreground "cyan1"))
903 904
    (((background dark)) (:foreground "cyan")))
  "Face for all unknown fonts in man pages."
Eli Zaretskii's avatar
Eli Zaretskii committed
905
  :group 'woman-faces)
906 907
;; backward-compatibility alias
(put 'woman-unknown-face 'face-alias 'woman-unknown)
Eli Zaretskii's avatar
Eli Zaretskii committed
908

909
(defface woman-addition
Eli Zaretskii's avatar
Eli Zaretskii committed
910
  '((t (:foreground "orange")))
911
  "Face for all WoMan additions to man pages."
Eli Zaretskii's avatar
Eli Zaretskii committed
912
  :group 'woman-faces)
913 914
;; backward-compatibility alias
(put 'woman-addition-face 'face-alias 'woman-addition)
Eli Zaretskii's avatar
Eli Zaretskii committed
915

916
(defun woman-default-faces ()
917
  "Set foreground colors of italic and bold faces to their default values."
Eli Zaretskii's avatar
Eli Zaretskii committed
918
  (interactive)
919 920
  (face-spec-set 'woman-italic (face-user-default-spec 'woman-italic))
  (face-spec-set 'woman-bold (face-user-default-spec 'woman-bold)))
Eli Zaretskii's avatar
Eli Zaretskii committed
921

922
(defun woman-monochrome-faces ()
923
  "Set foreground colors of italic and bold faces to that of the default face.
924
This is usually either black or white."
Eli Zaretskii's avatar
Eli Zaretskii committed
925
  (interactive)
926 927
  (set-face-foreground 'woman-italic 'unspecified)
  (set-face-foreground 'woman-bold 'unspecified))
Eli Zaretskii's avatar
Eli Zaretskii committed
928 929

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
930 931
;; Experimental font support, initially only for MS-Windows.
(defconst woman-font-support
932
  (eq window-system 'w32)		; Support X later!
933 934 935 936 937 938 939 940 941 942 943 944 945 946 947
  "If non-nil then non-ASCII characters and symbol font supported.")

(defun woman-select-symbol-fonts (fonts)
  "Select symbol fonts from a list FONTS of font name strings."
  (let (symbol-fonts)
    ;; With NTEmacs 20.5, the PATTERN option to `x-list-fonts' does
    ;; not seem to work and fonts may be repeated, so ...
    (while fonts
      (and (string-match "-Symbol-" (car fonts))
	   (not (member (car fonts) symbol-fonts))
	   (setq symbol-fonts (cons (car fonts) symbol-fonts)))
      (setq fonts (cdr fonts)))
    symbol-fonts))

(when woman-font-support
948
  (make-face 'woman-symbol)
Eli Zaretskii's avatar
Eli Zaretskii committed
949

950
  ;; Set the symbol font only if `woman-use-symbol-font' is true, to
Eli Zaretskii's avatar
Eli Zaretskii committed
951 952
  ;; avoid unnecessarily upsetting the line spacing in NTEmacs 20.5!

953 954 955 956 957 958
  (defcustom woman-use-extended-font t
    "*If non-nil then may use non-ASCII characters from the default font."
    :type 'boolean
    :group 'woman-faces)

  (defcustom woman-use-symbol-font nil
959 960 961
    "*If non-nil then may use the symbol font.
It is off by default, mainly because it may change the line spacing
\(in NTEmacs 20.5)."
Eli Zaretskii's avatar
Eli Zaretskii committed
962 963 964 965
    :type 'boolean
    :group 'woman-faces)

  (defconst woman-symbol-font-list
966 967 968
    (or (woman-select-symbol-fonts (x-list-fonts "*" 'default))
	(woman-select-symbol-fonts (x-list-fonts "*")))
    "Symbol font(s), preferably same size as default when WoMan was loaded.")
Eli Zaretskii's avatar
Eli Zaretskii committed
969 970 971 972 973 974 975

  (defcustom woman-symbol-font (car woman-symbol-font-list)
    "*A string describing the symbol font to use for special characters.
It should be compatible with, and the same size as, the default text font.
Under MS-Windows, the default is
  \"-*-Symbol-normal-r-*-*-*-*-96-96-p-*-ms-symbol\"."
    :type `(choice
976
	    ,@(mapcar (lambda (x) (list 'const x))
Eli Zaretskii's avatar
Eli Zaretskii committed
977 978 979 980 981 982
		      woman-symbol-font-list)
	    string)
    :group 'woman-faces)

  )

983 984 985 986
;; For non windows-nt ...
(defvar woman-use-extended-font nil)
(defvar woman-use-symbol-font nil)
(defvar woman-symbol-font nil)
Eli Zaretskii's avatar
Eli Zaretskii committed
987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;; Internal variables:

(defconst woman-justify-list
  '(left right center full)
  "Justify styles for `fill-region-as-paragraph'.")
(defconst woman-adjust-left 0		; == adjust off, noadjust
  "Adjustment indicator `l' -- adjust left margin only.")
(defconst woman-adjust-right 1
  "Adjustment indicator `r' -- adjust right margin only.")
(defconst woman-adjust-center 2
  "Adjustment indicator `c' -- center.")
(defconst woman-adjust-both 3		; default -- adj,both
  "Adjustment indicator `b' or `n' -- adjust both margins.")

(defvar woman-adjust woman-adjust-both
  "Current adjustment number-register value.")
(defvar woman-adjust-previous woman-adjust
  "Previous adjustment number-register value.")
(defvar woman-justify
  (nth woman-adjust woman-justify-list)	; use vector?
  "Current justification style for `fill-region-as-paragraph'.")
(defvar woman-justify-previous woman-justify
  "Previous justification style for `fill-region-as-paragraph'.")

(defvar woman-left-margin woman-default-indent
  "Current left margin.")
(defvar woman-prevailing-indent woman-default-indent
  "Current prevailing indent.")
(defvar woman-interparagraph-distance 1
  "Interparagraph distance in lines.
Set by .PD; used by .SH, .SS, .TP, .LP, .PP, .P, .IP, .HP.")
(defvar woman-leave-blank-lines nil
  "Blank lines to leave as vertical space.")
(defconst woman-tab-width 5
  "Default tab width set by -man macros.")
(defvar woman-nofill nil
  "Current fill mode: nil for filling.")
(defvar woman-RS-left-margin nil
  "Left margin stack for nested use of `.RS/.RE'.")
(defvar woman-RS-prevailing-indent nil
  "Prevailing indent stack for nested use of `.RS/.RE'.")
(defvar woman-nospace nil
  "Current no-space mode: nil for normal spacing.
Set by `.ns' request; reset by any output or `.rs' request")

(defsubst woman-reset-nospace ()
1036
  "Set `woman-nospace' to nil."
Eli Zaretskii's avatar
Eli Zaretskii committed
1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047
  (setq woman-nospace nil))

(defconst woman-request-regexp "^[.'][ \t]*\\(\\S +\\) *"
  ;; Was "^\\.[ \t]*\\([a-z0-9]+\\) *" but cvs.1 uses a macro named
  ;; "`" and CGI.man uses a macro named "''"!
  ;; CGI.man uses ' as control character in places -- it *should*
  ;; suppress breaks!
  ;; Could end with "\\( +\\|$\\)" instead of " *"
  "Regexp to match a ?roff request plus trailing white space.")

(defvar woman-imenu-done nil
1048
  "Buffer-local: set to true if function `woman-imenu' has been called.")
Eli Zaretskii's avatar
Eli Zaretskii committed
1049 1050 1051 1052 1053 1054 1055 1056 1057
(make-variable-buffer-local 'woman-imenu-done)

;; From imenu.el -- needed when reformatting a file in its old buffer.
;; The latest buffer index used to update the menu bar menu.
(eval-when-compile
  (require 'imenu))
(make-variable-buffer-local 'imenu--last-menubar-index-alist)

(defvar woman-buffer-alist nil
1058 1059
  "An alist representing WoMan buffers that are already decoded.
Each element is of the form (FILE-NAME . BUFFER-NAME).")
Eli Zaretskii's avatar
Eli Zaretskii committed
1060 1061 1062 1063 1064

(defvar woman-buffer-number 0
  "Ordinal number of current buffer entry in `woman-buffer-alist'.
The ordinal numbers start from 0.")

1065 1066 1067 1068 1069
(defvar woman-if-conditions-true '(?n ?e ?o)
  "List of one-character built-in condition names that are true.
Should include ?e, ?o (page even/odd) and either ?n (nroff) or ?t (troff).
Default is '(?n ?e ?o).  Set via `woman-emulation'.")

Eli Zaretskii's avatar
Eli Zaretskii committed
1070 1071 1072 1073 1074 1075

;;; Specialized utility functions:

;;; Fast deletion without saving on the kill ring (cf. simple.el):

(defun woman-delete-line (&optional arg)
1076
  "Delete rest of current line; if all blank then delete thru newline.
Eli Zaretskii's avatar
Eli Zaretskii committed
1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122
With a numeric argument ARG, delete that many lines from point.
Negative arguments delete lines backward."
  ;; This is a non-interactive version of kill-line in simple.el that
  ;; deletes instead of killing and assumes kill-whole-line is nil,
  ;; which is essential!
  (delete-region (point)
		 (progn
		   (if arg
		       (forward-line arg)
		     (if (eobp)
			 (signal 'end-of-buffer nil))
		     (if (looking-at "[ \t]*$")
			 (forward-line 1)
		       (end-of-line)))
		 (point))))

(defsubst woman-delete-whole-line ()
  "Delete current line from beginning including eol."
  (beginning-of-line)
  (woman-delete-line 1))

(defsubst woman-delete-following-space ()
  "Delete all spaces and tabs FOLLOWING point (cf. `delete-horizontal-space')."
  ;; cf. delete-horizontal-space in simple.el:
  (delete-region (point) (progn (skip-chars-forward " \t") (point))))

(defsubst woman-delete-match (subexp)
  "Delete subexpression SUBEXP of buffer text matched by last search."
  (delete-region (match-beginning subexp) (match-end subexp)))

;; delete-char does not kill by default
;; delete-backward-char does not kill by default
;; delete-horizontal-space does not kill
;; delete-blank-lines does not kill


;;; File handling:

(defvar woman-expanded-directory-path nil
  "Expanded directory list cache.  Resetting to nil forces update.")

(defvar woman-topic-all-completions nil
  "Expanded topic alist cache.  Resetting to nil forces update.")

;;;###autoload
(defun woman (&optional topic re-cache)
1123
  "Browse UN*X man page for TOPIC (Without using external Man program).
Eli Zaretskii's avatar
Eli Zaretskii committed
1124 1125 1126 1127 1128 1129 1130
The major browsing mode used is essentially the standard Man mode.
Choose the filename for the man page using completion, based on the
topic selected from the directories specified in `woman-manpath' and
`woman-path'.  The directory expansions and topics are cached for
speed, but a non-nil interactive argument forces the caches to be
updated (e.g. to re-interpret the current directory).

1131 1132
Used non-interactively, arguments are optional: if given then TOPIC
should be a topic string and non-nil RE-CACHE forces re-caching."
Eli Zaretskii's avatar
Eli Zaretskii committed
1133 1134
  (interactive (list nil current-prefix-arg))
  ;; The following test is for non-interactive calls via gnudoit etc.
1135
  (if (or (not (stringp topic)) (string-match "\\S " topic))
Eli Zaretskii's avatar
Eli Zaretskii committed
1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146
      (let ((file-name (woman-file-name topic re-cache)))
	(if file-name
	    (woman-find-file file-name)
	  (message
	   "WoMan Error: No matching manual files found in search path")
	  (ding))
	)
    (message "WoMan Error: No topic specified in non-interactive call")
    (ding))
  )

1147
;; Allow WoMan to be called via the standard Help menu:
Eli Zaretskii's avatar
Eli Zaretskii committed
1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165
(define-key-after menu-bar-manuals-menu [woman]
  '(menu-item "Read Man Page (WoMan)..." woman
	:help "Man-page documentation Without Man") t)

(defvar woman-cached-data nil
  "A list of cached data used to determine cache validity.
Set from the cache by `woman-read-directory-cache'.")

(defun woman-cached-data ()
  "Generate a list of data used to determine cache validity.
Called both to generate and to check the cache!"
  ;; Must use substituted paths because values of env vars may change!
  (list woman-cache-level
	(mapcar 'substitute-in-file-name woman-manpath)
	(mapcar 'substitute-in-file-name woman-path)))

(defun woman-read-directory-cache ()
  "Load the directory and topic cache.
1166 1167
It is loaded from the file named by the variable `woman-cache-filename'.
Return t if the file exists, nil otherwise."
Eli Zaretskii's avatar
Eli Zaretskii committed
1168 1169 1170 1171 1172 1173 1174
  (and
   woman-cache-filename
   (load woman-cache-filename t nil t)	; file exists
   (equal woman-cached-data (woman-cached-data)))) ; cache valid

(defun woman-write-directory-cache ()
  "Save the directory and topic cache.
1175
It is saved to the file named by the variable `woman-cache-filename'."
Eli Zaretskii's avatar
Eli Zaretskii committed
1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203
  (if woman-cache-filename
      (save-excursion			; to restore current buffer
	;; Make a temporary buffer; name starting with space "hides" it.
	(let ((standard-output
	       (set-buffer (generate-new-buffer "WoMan tmp buffer")))
	      (backup-inhibited t))
	  ;; (switch-to-buffer standard-output t) ; only for debugging
	  (buffer-disable-undo standard-output)
	  (princ
	   ";;; WoMan directory and topic cache -- generated automatically\n")
	  (print
	   ;; For data validity check:
	   `(setq woman-cached-data ',(woman-cached-data)))
	  (print
	   `(setq woman-expanded-directory-path
		  ',woman-expanded-directory-path))
	  (print
	   `(setq woman-topic-all-completions
		  ',woman-topic-all-completions))
	  (write-file woman-cache-filename) ; write CURRENT buffer
	  (kill-buffer standard-output)
	  ))))

(defvar woman-topic-history nil "Topic read history.")
(defvar woman-file-history nil "File-name read history.")

(defun woman-file-name (topic &optional re-cache)
  "Get the name of the UN*X man-page file describing a chosen TOPIC.
1204 1205 1206 1207 1208
When `woman' is called interactively, the word at point may be
automatically used as the topic, if the value of the user option
`woman-use-topic-at-point' is non-nil.  Return nil if no file can
be found.  Optional argument RE-CACHE, if non-nil, forces the
cache to be re-read."
Eli Zaretskii's avatar
Eli Zaretskii committed
1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225
  ;; Handle the caching of the directory and topic lists:
  (if (and (not re-cache)
	   (or
	    (and woman-expanded-directory-path woman-topic-all-completions)
	    (woman-read-directory-cache)))
      ()
    (message "Building list of manual directory expansions...")
    (setq woman-expanded-directory-path
	  (woman-expand-directory-path woman-manpath woman-path))
    (message "Building completion list of all manual topics...")
    (setq woman-topic-all-completions
	  (woman-topic-all-completions woman-expanded-directory-path))
    (woman-write-directory-cache))
  ;; There is a problem in that I want to offer case-insensitive
  ;; completions, but to return only a case-sensitive match.  This
  ;; does not seem to work properly by default, so I re-do the
  ;; completion if necessary.
1226 1227
  (let (files
	(default (current-word)))
Eli Zaretskii's avatar
Eli Zaretskii committed
1228
    (or (stringp topic)
1229 1230 1231 1232 1233 1234
	(and (if (boundp 'woman-use-topic-at-point)
		 woman-use-topic-at-point
	       ;; Was let-bound when file loaded, so ...
	       (setq woman-use-topic-at-point woman-use-topic-at-point-default))
	     (setq topic (or (current-word t) "")) ; only within or adjacent to word
	     (test-completion topic woman-topic-all-completions))
Eli Zaretskii's avatar
Eli Zaretskii committed
1235
	(setq topic
1236 1237 1238 1239 1240 1241 1242 1243
	      (let* ((word-at-point (current-word))
		     (default
		       (when (and word-at-point
				  (test-completion
				   word-at-point woman-topic-all-completions))
			 word-at-point)))
		(completing-read
		 (if default
1244
		     (format "Manual entry (default %s): " default)
1245 1246 1247 1248 1249
		   "Manual entry: ")
		 woman-topic-all-completions nil 1
		 nil
		 'woman-topic-history
		 default))))
Eli Zaretskii's avatar
Eli Zaretskii committed
1250 1251 1252 1253 1254 1255 1256 1257 1258 1259 1260 1261 1262 1263 1264 1265 1266 1267 1268
    ;; Note that completing-read always returns a string.
    (if (= (length topic) 0)
	nil				; no topic, so no file!
      (cond
       ((setq files (woman-file-name-all-completions topic)))
       ;; Complete topic more carefully, i.e. use the completion
       ;; rather than the string entered by the user:
       ((setq files (all-completions topic woman-topic-all-completions))
	(while (/= (length topic) (length (car files)))
	  (setq files (cdr files)))
	(setq files (woman-file-name-all-completions (car files)))))
      (cond
       ((null files) nil)		; no file found for topic.
       ((null (cdr files)) (car (car files))) ; only 1 file for topic.
       (t
	;; Multiple files for topic, so must select 1.
	;; Unread the command event (TAB = ?\t = 9) that runs the command
	;; `minibuffer-complete' in order to automatically complete the
	;; minibuffer contents as far as possible.
1269
	(setq unread-command-events '(9)) ; and delete any type-ahead!
Eli Zaretskii's avatar
Eli Zaretskii committed
1270
	(completing-read "Manual file: " files nil 1
1271
			 (try-completion "" files) 'woman-file-history))))))
Eli Zaretskii's avatar
Eli Zaretskii committed
1272 1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295 1296 1297 1298

(defun woman-select (predicate list)
  "Select unique elements for which PREDICATE is true in LIST.
\(Note that this function changes the value of LIST.)"
  ;; Intended to be fast by avoiding recursion and list copying.
  (while (and list
	      (or
	       (member (car list) (cdr list))
	       (not (funcall predicate (car list)))))
    (setq list (cdr list)))
  (if list
      (let ((newlist list) cdr_list)
	(while (setq cdr_list (cdr list))
	  (if (and
	       (not (member (car cdr_list) (cdr cdr_list)))
	       (funcall predicate (car cdr_list)))
	      (setq list cdr_list)
	    (setcdr list (cdr cdr_list)))
	  )
	newlist)))

(defun woman-file-readable-p (dir)
  "Return t if DIR is readable, otherwise log a warning."
  (or (file-readable-p dir)
      (WoMan-warn "Ignoring unreadable `manpath' directory tree `%s'!" dir)))

(defun woman-directory-files (head dir)
1299
  "Return a sorted list of files in directory HEAD matching regexp in DIR.
Eli Zaretskii's avatar
Eli Zaretskii committed
1300 1301 1302 1303 1304 1305 1306 1307 1308 1309 1310 1311 1312 1313 1314
Value is a sorted list of the absolute pathnames of all the files in
directory HEAD, or the current directory if HEAD is nil, that match the
regexp that is the final component of DIR.  Log a warning if list is empty."
  (or (directory-files
       (or head (directory-file-name default-directory)) ; was "."
       t
       (file-name-nondirectory dir))
      (WoMan-warn "No directories match `woman-path' entry `%s'!" dir)))

(defun woman-file-accessible-directory-p (dir)
  "Return t if DIR is accessible, otherwise log a warning."
  (or (file-accessible-directory-p dir)
      (WoMan-warn "Ignoring inaccessible `man-page' directory `%s'!" dir)))

(defun woman-expand-directory-path (woman-manpath woman-path)
1315 1316 1317
  "Expand the manual directories in WOMAN-MANPATH and WOMAN-PATH.
WOMAN-MANPATH should be a list of general manual directories, while
WOMAN-PATH should be a list of specific manual directory regexps.
Eli Zaretskii's avatar
Eli Zaretskii committed
1318 1319 1320 1321 1322 1323 1324 1325 1326 1327 1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341 1342 1343 1344 1345 1346 1347 1348 1349
Ignore any paths that are unreadable or not directories."
  ;; Allow each path to be a single string or a list of strings:
  (if (not (listp woman-manpath)) (setq woman-manpath (list woman-manpath)))
  (if (not (listp woman-path)) (setq woman-path (list woman-path)))
  (let (dir head dirs)
    (while woman-manpath
      (setq dir (car woman-manpath)
	    woman-manpath (cdr woman-manpath))
      (if (and dir (woman-file-readable-p dir))
	  ;; NB: `parse-colon-path' creates null elements for
	  ;; redundant (semi-)colons and trailing `/'s!
	  ;; If does not actually matter here if dir ends with `/'.
	  ;; Need regexp "man" here to avoid "cat?", `.', `..', etc.
	  (setq dir (woman-canonicalize-dir dir)
		dirs (nconc dirs (directory-files
				  dir t woman-manpath-man-regexp)))))
    (while woman-path
      (setq dir (car woman-path)
	    woman-path (cdr woman-path))
      (if (or (null dir)
	      (null (setq dir (woman-canonicalize-dir dir)
			  head (file-name-directory dir)))
	      (woman-file-readable-p head))
	  (setq dirs
		(if dir
		    (nconc dirs (woman-directory-files head dir))
		  (cons (directory-file-name default-directory) dirs))
		;; was "." -- at head of list for later filtering
		)))
    (woman-select 'woman-file-accessible-directory-p dirs)))

(defun woman-canonicalize-dir (dir)
1350
  "Canonicalize the directory name DIR.
Eli Zaretskii's avatar
Eli Zaretskii committed
1351 1352 1353 1354 1355 1356
Any UN*X-style environment variables are evaluated first."
  (setq dir (expand-file-name (substitute-in-file-name dir)))
  ;; A path that ends with / matches all directories in it,
  ;; including `.' and `..', so remove any trailing / !!!
  (if (string= (substring dir -1) "/")
      (setq dir (substring dir 0 -1)))
Juanma Barranquero's avatar
Juanma Barranquero committed
1357
  (if (memq system-type '(windows-nt ms-dos cygwin)) ; what else?
Eli Zaretskii's avatar
Eli Zaretskii committed
1358 1359 1360 1361 1362 1363
      ;; Match capitalization used by `file-name-directory':
      (setq dir (concat (file-name-directory dir)
			(file-name-nondirectory dir))))
  dir)

(defsubst woman-not-member (dir path)
1364
  "Return t if DIR is not a member of the list PATH, nil otherwise.
Eli Zaretskii's avatar
Eli Zaretskii committed
1365 1366 1367 1368 1369 1370 1371 1372 1373 1374 1375 1376
If DIR is `.' it is first replaced by the current directory."
  (not (member dir path)))

(defun woman-topic-all-completions (path)
  "Return an alist of the man files in all man directories in the list PATH.
The cdr of each alist element is the path-index / filename."
  ;; Support 3 levels of caching: each element of the alist `files'
  ;; will be a list of the first `woman-cache-level' elements of the
  ;; following list: (topic path-index filename).  This alist `files'
  ;; is re-processed by `woman-topic-all-completions-merge'.
  (let (dir files (path-index 0))	; indexing starts at zero
    (while path
1377
      (setq dir (pop path))
Eli Zaretskii's avatar
Eli Zaretskii committed
1378
      (if (woman-not-member dir path)	; use each directory only once!
1379 1380
	  (push (woman-topic-all-completions-1 dir path-index)
		files))
Eli Zaretskii's avatar
Eli Zaretskii committed
1381 1382
      (setq path-index (1+ path-index)))
    ;; Uniquefy topics:
1383 1384 1385 1386
    ;; Concate all lists with a single nconc call to
    ;; avoid retraversing the first lists repeatedly  -- dak
    (woman-topic-all-completions-merge
     (apply #'nconc files))))
Eli Zaretskii's avatar
Eli Zaretskii committed
1387 1388

(defun woman-topic-all-completions-1 (dir path-index)
1389 1390 1391 1392 1393 1394 1395 1396 1397 1398
  "Return an alist of the man topics in directory DIR with index PATH-INDEX.
A topic is a filename sans type-related extensions.
Support 3 levels of caching: each element of the alist will be a list
of the first `woman-cache-level' elements from the following list:
\(topic path-index filename)."
  ;; This function used to check that each file in the directory was
  ;; not itself a directory, but this is very slow and should be
  ;; unnecessary.  So let us assume that `woman-file-regexp' will
  ;; filter out any directories, which probably should not be there
  ;; anyway, i.e. it is a user error!
1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420
  ;;
  ;; Don't sort files: we do that when merging, anyway.  -- dak
  (let (newlst (lst (directory-files dir nil woman-file-regexp t))
	       ;; Make an explicit regexp for stripping extension and
	       ;; compression extension: file-name-sans-extension is a
	       ;; far too costly function.  -- dak
	       (ext (format "\\(\\.[^.\\/]*\\)?\\(%s\\)?\\'"
			    woman-file-compression-regexp)))
    ;; Use a loop instead of mapcar in order to avoid the speed
    ;; penalty of binding function arguments.  -- dak
      (dolist (file lst newlst)
	(push
	 (cons
	  (if (string-match ext file)
	      (substring file 0 (match-beginning 0))
	    file)
	  (and (> woman-cache-level 1)
	       (cons
		path-index
		(and (> woman-cache-level 2)
		     (list file)))))
	 newlst))))
Eli Zaretskii's avatar
Eli Zaretskii committed
1421 1422 1423

(defun woman-topic-all-completions-merge (alist)
  "Merge the alist ALIST so that the keys are unique.
1424
Also make each path-info component into a list.
Eli Zaretskii's avatar
Eli Zaretskii committed
1425
\(Note that this function changes the value of ALIST.)"