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

3
;; Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008,
Glenn Morris's avatar
Glenn Morris committed
4
;;   2009, 2010  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

;; This file is part of GNU Emacs.

15
;; GNU Emacs is free software: you can redistribute it and/or modify
Eli Zaretskii's avatar
Eli Zaretskii committed
16
;; it under the terms of the GNU General Public License as published by
17 18
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
Eli Zaretskii's avatar
Eli Zaretskii committed
19 20 21 22 23 24 25

;; 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
26
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
Eli Zaretskii's avatar
Eli Zaretskii committed
27 28 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

;;; 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
98 99
;;          (lambda ()
;;            (define-key dired-mode-map "W" 'woman-dired-find-file)))
Eli Zaretskii's avatar
Eli Zaretskii committed
100 101 102 103 104 105 106 107 108 109 110 111
;; 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
Glenn Morris's avatar
Glenn Morris committed
112
;; behavior is controlled by the user option `woman-dired-keys'.
Eli Zaretskii's avatar
Eli Zaretskii committed
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
;; 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


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

141 142 143 144 145 146
;; 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
147

148 149
;; 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
150 151

;;  (global-set-key "\C-cw"
152
;;  		  (lambda ()
Eli Zaretskii's avatar
Eli Zaretskii committed
153
;;  		    (interactive)
154
;;  		    (let ((woman-use-topic-at-point t))
Eli Zaretskii's avatar
Eli Zaretskii committed
155 156 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
;;  		      (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
;; ======================

Glenn Morris's avatar
Glenn Morris committed
237
;; This is modeled on the byte-compiler.  It logs all files formatted
Eli Zaretskii's avatar
Eli Zaretskii committed
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
;; 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
268 269 270
;; 	     (lambda (arg)
;; 		set-visited-file-name
;; 		(file-name-sans-extension buffer-file-name)))))
Eli Zaretskii's avatar
Eli Zaretskii committed
271 272 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
;;       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
;; =====

330
;; Reconsider case sensitivity of file names.
Eli Zaretskii's avatar
Eli Zaretskii committed
331 332 333 334
;; 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?
335 336
;; Emulate more complete preprocessor support for tbl (.TS/.TE)
;; Emulate some preprocessor support for eqn (.EQ/.EN)
Eli Zaretskii's avatar
Eli Zaretskii committed
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351
;; 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?
352
;; Fix .fc properly?
Eli Zaretskii's avatar
Eli Zaretskii committed
353 354 355 356 357 358 359 360 361 362 363 364 365 366


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

367 368 369
;; 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
370 371 372 373 374 375 376 377 378 379 380 381


;; 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>
382
;;   Juanma Barranquero <lekktu@gmail.com>
Eli Zaretskii's avatar
Eli Zaretskii committed
383 384
;;   Karl Berry <kb@cs.umb.edu>
;;   Jim Chapman <jchapman@netcomuk.co.uk>
385
;;   Kin Cho <kin@neoscale.com>
Eli Zaretskii's avatar
Eli Zaretskii committed
386 387 388 389 390 391 392 393 394 395 396 397 398 399
;;   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>
400
;;   David Kastrup <dak@gnu.org>
Eli Zaretskii's avatar
Eli Zaretskii committed
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415
;;   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>
416
;;   Arrigo Triulzi <arrigo@maths.qmw.ac.uk>
Eli Zaretskii's avatar
Eli Zaretskii committed
417
;;   Geoff Voelker <voelker@cs.washington.edu>
Eli Zaretskii's avatar
Eli Zaretskii committed
418
;;   Eli Zaretskii <eliz@gnu.org>
Eli Zaretskii's avatar
Eli Zaretskii committed
419 420 421 422


;;; Code:

423 424
(defvar woman-version "0.551 (beta)" "WoMan version information.")

Eli Zaretskii's avatar
Eli Zaretskii committed
425
(require 'man)
426
(require 'button)
427
(define-button-type 'WoMan-xref-man-page
428
  :supertype 'Man-abstract-xref-man-page
429 430 431 432 433
  '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)
434
	       (substring arg 0 (match-end 1))
435
	     arg))))
436

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

442
(defun woman-mapcan (fn x)
443
  "Return concatenated list of FN applied to successive `car' elements of X.
444 445
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!
446 447
  ;; More concise implementation than the recursive one.  -- dak
  (apply #'nconc (mapcar fn x)))
448

449 450 451
(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.
452
Replace null components by calling `woman-parse-man.conf'.
453 454
As a special case, if PATHS is nil then replace it by calling
`woman-parse-man.conf'."
455
  ;; Based on suggestions by Jari Aalto and Eli Zaretskii.
456 457 458 459 460 461 462 463 464 465 466 467 468 469 470
  ;; 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...
471
	     (list paths))
472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
	    (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."
487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
  ;; MANPATH_MAP conses are not converted since they presumably map
  ;; Cygwin to Cygwin form.
  (if (consp file)
      file
    ;; 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)
503
	     (if (match-beginning 1)		; /cygdrive/x/ or //x/ -> /x/
504 505 506 507 508
		 (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
509 510 511 512 513 514 515 516 517 518 519 520


;;; 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
521
  "If non-nil then show the *WoMan-Log* buffer if appropriate.
Eli Zaretskii's avatar
Eli Zaretskii committed
522 523 524 525 526
I.e. if any warning messages are written to it.  Default is nil."
  :type 'boolean
  :group 'woman)

(defcustom woman-pre-format-hook nil
527
  "Hook run by WoMan immediately before formatting a buffer.
Eli Zaretskii's avatar
Eli Zaretskii committed
528 529 530 531 532
Change only via `Customization' or the function `add-hook'."
  :type 'hook
  :group 'woman)

(defcustom woman-post-format-hook nil
533
  "Hook run by WoMan immediately after formatting a buffer.
Eli Zaretskii's avatar
Eli Zaretskii committed
534 535 536 537 538 539 540 541 542 543 544 545
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)

546
(defcustom woman-man.conf-path
547
  (let ((path '("/usr/lib" "/etc")))
548 549 550 551 552
    (cond ((eq system-type 'windows-nt)
	   (mapcar 'woman-Cyg-to-Win path))
	  ((eq system-type 'darwin)
	   (cons "/usr/share/misc" path))
	  (t path)))
553
  "List of dirs to search and/or files to try for man config file.
554 555 556 557 558 559
A trailing separator (`/' for UNIX etc.) on directories is
optional, and the filename is used if a directory specified is
the first to start with \"man\" and has an extension starting
with \".conf\".  If MANPATH is not set but a config file is found
then it is parsed instead to provide a default value for
`woman-manpath'."
560 561 562 563
  :type '(repeat string)
  :group 'woman-interface)

(defun woman-parse-man.conf ()
564
  "Parse if possible configuration file for man command.
565
Used only if MANPATH is not set or contains null components.
566 567
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
568
  MANPATH  /usr/man
569
or
570 571
  MANDATORY_MANPATH  /usr/man
or
572 573 574
  OPTIONAL_MANPATH  /usr/man
or
  MANPATH_MAP /opt/bin /opt/man"
575 576 577 578 579 580 581 582 583 584 585
  ;; 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
586
			     (directory-files file t "\\`man.*\\.conf[a-z]*\\'" t))
587 588 589 590 591
		       (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
592 593
			    ;; `\(?: ... \)' is a "shy group"
			    "\
594 595 596 597
^[ \t]*\\(?:\\(?:MANDATORY_\\|OPTIONAL_\\)?MANPATH[ \t]+\\(\\S-+\\)\\|\
MANPATH_MAP[ \t]+\\(\\S-+\\)[ \t]+\\(\\S-+\\)\\)" nil t)
		      (add-to-list 'manpath
				   (if (match-beginning 1)
598
				       (match-string 1)
599 600
				     (cons (match-string 2)
					   (match-string 3)))))
601 602 603 604 605
		    manpath))
		 ))
      (setq path (cdr path)))
    (nreverse manpath)))

Glenn Morris's avatar
Glenn Morris committed
606 607 608 609 610 611 612 613 614 615 616 617 618 619 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 648 649 650 651 652 653 654 655 656
;; Autoload so set-locale-environment can operate on it.
;;;###autoload
(defcustom woman-locale nil
  "String specifying a manual page locale, or nil.
If a manual page is available in the specified locale
\(e.g. \"sv_SE.ISO8859-1\"), it will be offered in preference to the
default version.  Normally, `set-locale-environment' sets this at startup."
  :type '(choice string (const nil))
  :group 'woman-interface
  :version "23.1")

;; FIXME Is this a sensible list of alternatives?
(defun woman-expand-locale (locale)
  "Expand a locale into a list suitable for man page lookup.
Expands a locale of the form LANGUAGE_TERRITORY.CHARSET into the list:
LANGUAGE_TERRITORY.CHARSET LANGUAGE_TERRITORY LANGUAGE.CHARSET LANGUAGE.
The TERRITORY and CHARSET portions may be absent."
  (string-match "\\([^._]*\\)\\(_[^.]*\\)?\\(\\..*\\)?" locale)
  (let ((lang (match-string 1 locale))
        (terr (match-string 2 locale))
        (charset (match-string 3 locale)))
    (delq nil (list locale
                    (and charset terr (concat lang terr))
                    (and charset terr (concat lang charset))
                    (if (or charset terr) lang)))))

(defun woman-manpath-add-locales (manpath)
  "Add locale-specific subdirectories to the elements of MANPATH.
MANPATH is a list of the form of `woman-manpath'.  Returns a list
with those locale-specific subdirectories specified by the action
of `woman-expand-locale' on `woman-locale' added, where they exist."
  (if (zerop (length woman-locale))
      manpath
    (let ((subdirs (woman-expand-locale woman-locale))
          lst dir)
      (dolist (elem manpath (nreverse lst))
        (dolist (sub subdirs)
          (when (file-directory-p
                 (setq dir
                       ;; Use f-n-a-d because parse-colon-path does.
                       (file-name-as-directory
                        (expand-file-name sub (substitute-in-file-name
                                               (if (consp elem)
                                                   (cdr elem)
                                                 elem))))))
            (add-to-list 'lst (if (consp elem)
                                  (cons (car elem) dir)
                                dir))))
        ;; Non-locale-specific has lowest precedence.
        (add-to-list 'lst elem)))))

Eli Zaretskii's avatar
Eli Zaretskii committed
657
(defcustom woman-manpath
Glenn Morris's avatar
Glenn Morris committed
658 659 660
  ;; Locales could also be added in woman-expand-directory-path.
  (or (woman-manpath-add-locales
       (woman-parse-colon-path (getenv "MANPATH")))
661
      '("/usr/man" "/usr/share/man" "/usr/local/man"))
662
  "List of DIRECTORY TREES to search for UN*X manual files.
Eli Zaretskii's avatar
Eli Zaretskii committed
663 664 665
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
666 667
and unreadable files are ignored.

668 669 670 671 672
Elements can also be a cons cell indicating a mapping from PATH
to manual trees: if such an element's car is equal to a path
element of the environment variable PATH, the cdr of the cons
cell is included in the directory tree search.

673 674
If not set then the environment variable MANPATH is used.  If no such
environment variable is found, the default list is determined by
675 676 677
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
678

679
Any environment variables (names must have the UN*X-style form $NAME,
680
e.g. $HOME, $EMACSDATA, $emacs_dir) are evaluated first but each
Eli Zaretskii's avatar
Eli Zaretskii committed
681 682 683 684 685 686
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.

687
  (\"C:/Cygwin/usr/man/\" \"C:/Cygwin/usr/local/man\").
Eli Zaretskii's avatar
Eli Zaretskii committed
688 689

The MANPATH environment variable may be set using DOS semi-colon-
690
separated or UN*X/Cygwin colon-separated syntax (but not mixed)."
691
  :type '(repeat (choice string (cons string string)))
Glenn Morris's avatar
Glenn Morris committed
692
  :version "23.1"                    ; added woman-manpath-add-locales
Eli Zaretskii's avatar
Eli Zaretskii committed
693 694 695 696 697 698 699 700 701 702 703 704 705
  :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]"))
706
  "List of SPECIFIC DIRECTORIES to search for UN*X manual files.
Eli Zaretskii's avatar
Eli Zaretskii committed
707 708 709 710 711 712 713 714 715 716 717 718 719
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,
720
e.g. $HOME, $EMACSDATA, $emacs_dir) are evaluated first but each
Eli Zaretskii's avatar
Eli Zaretskii committed
721 722 723
element must evaluate to a SINGLE directory name (regexp, see above).
For example

724
  (\"$EMACSDATA\") [or equivalently (\"$emacs_dir/etc\")].
Eli Zaretskii's avatar
Eli Zaretskii committed
725 726 727 728 729 730 731 732

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
733
  "The level of topic caching.
Eli Zaretskii's avatar
Eli Zaretskii committed
734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751
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
752
  "The full pathname of the WoMan directory and topic cache file.
Eli Zaretskii's avatar
Eli Zaretskii committed
753 754 755 756 757 758 759 760 761 762 763
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
764
  "List of `dired' mode keys to define to run WoMan on current file.
Eli Zaretskii's avatar
Eli Zaretskii committed
765 766 767 768 769 770 771 772 773 774 775
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))
776
  "Imenu support for Sections and Subsections.
Eli Zaretskii's avatar
Eli Zaretskii committed
777 778 779 780 781 782
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
783
  "If non-nil then WoMan adds a Contents menu to the menubar.
784
It does this by calling `imenu-add-to-menubar'.  Default is nil."
Eli Zaretskii's avatar
Eli Zaretskii committed
785 786 787 788
  :type 'boolean
  :group 'woman-interface)

(defcustom woman-imenu-title "CONTENTS"
789
  "The title to use if WoMan adds a Contents menu to the menubar.
Eli Zaretskii's avatar
Eli Zaretskii committed
790 791 792 793
Default is \"CONTENTS\"."
  :type 'string
  :group 'woman-interface)

794 795 796
(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
797
  ;; `woman-file-name' sets it to this value if it is unbound.
798
  "Default value for `woman-use-topic-at-point'."
Eli Zaretskii's avatar
Eli Zaretskii committed
799
  :type '(choice (const :tag "Yes" t)
800
		 (const :tag "No" nil))
Eli Zaretskii's avatar
Eli Zaretskii committed
801 802
  :group 'woman-interface)

803
(defcustom woman-use-topic-at-point woman-use-topic-at-point-default
804
  "Control use of the word at point as the default topic.
805 806
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
807
  :type '(choice (const :tag "Yes" t)
808
		 (const :tag "No" nil))
Eli Zaretskii's avatar
Eli Zaretskii committed
809 810 811 812 813 814 815 816 817 818 819 820 821
  :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.
822
Used as :set cookie by Customize when customizing the user options
Eli Zaretskii's avatar
Eli Zaretskii committed
823 824 825 826 827 828 829 830 831 832 833 834
`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
835
  "Do not change this unless you are sure you know what you are doing!
Eli Zaretskii's avatar
Eli Zaretskii committed
836 837 838 839 840 841 842 843 844 845 846 847 848 849
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\\)\\'"
850
  "Do not change this unless you are sure you know what you are doing!
Eli Zaretskii's avatar
Eli Zaretskii committed
851
Regexp used to match compressed man file extensions for which
852
decompressors are available and handled by auto-compression mode,
Eli Zaretskii's avatar
Eli Zaretskii committed
853 854
e.g. \"\\\\.\\\\(g?z\\\\|bz2\\\\)\\\\'\" for `gzip' or `bzip2'.
Should begin with \\. and end with \\' and MUST NOT be optional."
855 856 857 858
  ;; 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
859 860 861 862
  :type 'regexp
  :set 'set-woman-file-regexp
  :group 'woman-interface)

863
(defcustom woman-use-own-frame nil
864
  "If non-nil then use a dedicated frame for displaying WoMan windows.
865 866 867 868
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
869 870 871 872 873 874 875 876 877

;; 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
878
  "Right margin for formatted text -- default is 65."
Eli Zaretskii's avatar
Eli Zaretskii committed
879 880 881 882 883
  :type 'integer
  :group 'woman-formatting)

(defcustom woman-fill-frame nil
  ;; Based loosely on a suggestion by Theodore Jump:
884
  "If non-nil then most of the window width is used."
Eli Zaretskii's avatar
Eli Zaretskii committed
885 886 887 888
  :type 'boolean
  :group 'woman-formatting)

(defcustom woman-default-indent 5
889
  "Default prevailing indent set by -man macros -- default is 5.
890
Set this variable to 7 to emulate GNU man formatting."
Eli Zaretskii's avatar
Eli Zaretskii committed
891 892 893 894
  :type 'integer
  :group 'woman-formatting)

(defcustom woman-bold-headings t
895
  "If non-nil then embolden section and subsection headings.  Default is t.
896
Heading emboldening is NOT standard `man' behavior."
Eli Zaretskii's avatar
Eli Zaretskii committed
897 898 899 900
  :type 'boolean
  :group 'woman-formatting)

(defcustom woman-ignore t
Glenn Morris's avatar
Glenn Morris committed
901
  "If non-nil then unrecognized requests etc. are ignored.  Default is t.
902
This gives the standard ?roff behavior.  If nil then they are left in
Eli Zaretskii's avatar
Eli Zaretskii committed
903 904 905 906
the buffer, which may aid debugging."
  :type 'boolean
  :group 'woman-formatting)

907
(defcustom woman-preserve-ascii t
908
  "If non-nil, preserve ASCII characters in the WoMan buffer.
909 910 911 912 913 914 915
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
916 917 918
  :type 'boolean
  :group 'woman-formatting)

919
(defcustom woman-emulation 'nroff
920
  "WoMan emulation, currently either nroff or troff.  Default is nroff.
921 922 923 924 925
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
926 927 928 929 930 931 932 933 934 935 936 937 938

;; 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))
939
  "If non-nil then WoMan assumes that face support is available.
Eli Zaretskii's avatar
Eli Zaretskii committed
940 941 942 943 944
It defaults to a non-nil value if the display supports either colors
or different fonts."
  :type 'boolean
  :group 'woman-faces)

945
(defface woman-italic
946
  '((t :inherit italic))
947
  "Face for italic font in man pages."
Eli Zaretskii's avatar
Eli Zaretskii committed
948
  :group 'woman-faces)
949
(define-obsolete-face-alias 'woman-italic-face 'woman-italic "22.1")
Eli Zaretskii's avatar
Eli Zaretskii committed
950

951
(defface woman-bold
952
  '((t :inherit bold))
953
  "Face for bold font in man pages."
Eli Zaretskii's avatar
Eli Zaretskii committed
954
  :group 'woman-faces)
955
(define-obsolete-face-alias 'woman-bold-face 'woman-bold "22.1")
Eli Zaretskii's avatar
Eli Zaretskii committed
956

957
(defface woman-unknown
958
  '((t :inherit font-lock-warning-face))
959
  "Face for all unknown fonts in man pages."
Eli Zaretskii's avatar
Eli Zaretskii committed
960
  :group 'woman-faces)
961
(define-obsolete-face-alias 'woman-unknown-face 'woman-unknown "22.1")
Eli Zaretskii's avatar
Eli Zaretskii committed
962

963
(defface woman-addition
964
  '((t :inherit font-lock-builtin-face))
965
  "Face for all WoMan additions to man pages."
Eli Zaretskii's avatar
Eli Zaretskii committed
966
  :group 'woman-faces)
967
(define-obsolete-face-alias 'woman-addition-face 'woman-addition "22.1")
Eli Zaretskii's avatar
Eli Zaretskii committed
968

969
(defun woman-default-faces ()
970
  "Set foreground colors of italic and bold faces to their default values."
Eli Zaretskii's avatar
Eli Zaretskii committed
971
  (interactive)
972 973
  (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
974

975
(defun woman-monochrome-faces ()
976
  "Set foreground colors of italic and bold faces to that of the default face.
977
This is usually either black or white."
Eli Zaretskii's avatar
Eli Zaretskii committed
978
  (interactive)
979 980
  (set-face-foreground 'woman-italic 'unspecified)
  (set-face-foreground 'woman-bold 'unspecified))
Eli Zaretskii's avatar
Eli Zaretskii committed
981 982

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
983 984
;; Experimental font support, initially only for MS-Windows.
(defconst woman-font-support
985
  (eq window-system 'w32)		; Support X later!
986 987 988 989 990 991 992
  "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 ...
993 994 995 996
    (dolist (font fonts)
      (and (string-match "-Symbol-" font)
	   (not (member font symbol-fonts))
	   (setq symbol-fonts (cons font symbol-fonts))))
997 998
    symbol-fonts))

999 1000 1001
(declare-function x-list-fonts "xfaces.c"
		  (pattern &optional face frame maximum width))

1002
(when woman-font-support
1003
  (make-face 'woman-symbol)
Eli Zaretskii's avatar
Eli Zaretskii committed
1004

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

1008
  (defcustom woman-use-extended-font t
1009
    "If non-nil then may use non-ASCII characters from the default font."
1010 1011 1012 1013
    :type 'boolean
    :group 'woman-faces)

  (defcustom woman-use-symbol-font nil
1014
    "If non-nil then may use the symbol font.
1015 1016
It is off by default, mainly because it may change the line spacing
\(in NTEmacs 20.5)."
Eli Zaretskii's avatar
Eli Zaretskii committed
1017 1018 1019 1020
    :type 'boolean
    :group 'woman-faces)

  (defconst woman-symbol-font-list
1021 1022 1023
    (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
1024 1025

  (defcustom woman-symbol-font (car woman-symbol-font-list)
1026
    "A string describing the symbol font to use for special characters.
Eli Zaretskii's avatar
Eli Zaretskii committed
1027 1028 1029 1030
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
1031
	    ,@(mapcar (lambda (x) (list 'const x))
Eli Zaretskii's avatar
Eli Zaretskii committed
1032 1033 1034 1035 1036 1037
		      woman-symbol-font-list)
	    string)
    :group 'woman-faces)

  )

1038 1039 1040 1041
;; 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
1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;; 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 ()
1091
  "Set `woman-nospace' to nil."
Eli Zaretskii's avatar
Eli Zaretskii committed
1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102
  (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
1103
  "Buffer-local: set to true if function `woman-imenu' has been called.")
Eli Zaretskii's avatar
Eli Zaretskii committed
1104 1105 1106 1107 1108 1109 1110 1111 1112
(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
1113 1114
  "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
1115 1116 1117 1118 1119

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

1120 1121 1122 1123 1124
(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
1125 1126 1127 1128 1129 1130

;;; Specialized utility functions:

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

(defun woman-delete-line (&optional arg)
1131
  "Delete rest of current line; if all blank then delete thru newline.
Eli Zaretskii's avatar
Eli Zaretskii committed
1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 1167 1168 1169 1170 1171 1172 1173 1174 1175 1176 1177
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)
1178
  "Browse UN*X man page for TOPIC (Without using external Man program).
Eli Zaretskii's avatar
Eli Zaretskii committed
1179 1180 1181 1182 1183 1184 1185
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).

1186 1187
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
1188 1189
  (interactive (list nil current-prefix-arg))
  ;; The following test is for non-interactive calls via gnudoit etc.
1190
  (if (or (not (stringp topic)) (string-match "\\S " topic))
Eli Zaretskii's avatar
Eli Zaretskii committed
1191 1192 1193 1194 1195
      (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")
1196
	  (ding)))
Eli Zaretskii's avatar
Eli Zaretskii committed
1197
    (message "WoMan Error: No topic specified in non-interactive call")
1198
    (ding)))
Eli Zaretskii's avatar
Eli Zaretskii committed
1199

1200
;; Allow WoMan to be called via the standard Help menu:
Eli Zaretskii's avatar
Eli Zaretskii committed
1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213
(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
1214 1215 1216 1217 1218 1219 1220 1221
	(let (lst path)
	  (dolist (dir woman-manpath (nreverse lst))
	    (when (consp dir)
	      (unless path
		(setq path
		      (split-string (getenv "PATH") path-separator t)))
	      (setq dir (and (member (car dir) path) (cdr dir))))
	    (when dir (add-to-list 'lst (substitute-in-file-name dir)))))
Eli Zaretskii's avatar
Eli Zaretskii committed
1222 1223 1224 1225
	(mapcar 'substitute-in-file-name woman-path)))

(defun woman-read-directory-cache ()
  "Load the directory and topic cache.
1226 1227
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
1228 1229 1230 1231 1232 1233 1234
  (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.
1235
It is saved to the file named by the variable `woman-cache-filename'."
Eli Zaretskii's avatar
Eli Zaretskii committed
1236
  (if woman-cache-filename
1237
      (with-current-buffer (generate-new-buffer "WoMan tmp buffer")
Eli Zaretskii's avatar
Eli Zaretskii committed
1238
	;; Make a temporary buffer; name starting with space "hides" it.
1239
	(let ((standard-output (current-buffer))
Eli Zaretskii's avatar
Eli Zaretskii committed
1240 1241 1242 1243 1244 1245 1246 1247 1248 1249 1250 1251 1252 1253 1254 1255 1256 1257
	      (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)
	  ))))

1258
(defvaralias 'woman-topic-history 'Man-topic-history)
Eli Zaretskii's avatar
Eli Zaretskii committed
1259 1260 1261 1262
(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.
1263 1264 1265 1266 1267
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
1268
  ;; Handle the caching of the directory and topic lists:
1269 1270 1271 1272
  (unless (and (not re-cache)
	       (or
		(and woman-expanded-directory-path woman-topic-all-completions)
		(woman-read-directory-cache