woman.el 171 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-2011  Free Software Foundation, Inc.
Eli Zaretskii's avatar
Eli Zaretskii committed
4

5
;; Author: Francis J. Wright <F.J.Wright@qmul.ac.uk>
6
;; Maintainer: FSF
7
;; Keywords: help, unix
Eli Zaretskii's avatar
Eli Zaretskii committed
8
;; Adapted-By: Eli Zaretskii <eliz@gnu.org>
9
;; Version: 0.551
10
;; URL: http://centaur.maths.qmul.ac.uk/Emacs/WoMan/
Eli Zaretskii's avatar
Eli Zaretskii committed
11 12 13

;; This file is part of GNU Emacs.

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

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

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


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

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

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

;;  (global-set-key "\C-cw"
151
;;  		  (lambda ()
Eli Zaretskii's avatar
Eli Zaretskii committed
152
;;  		    (interactive)
153
;;  		    (let ((woman-use-topic-at-point t))
Eli Zaretskii's avatar
Eli Zaretskii committed
154 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
;;  		      (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
236
;; This is modeled on the byte-compiler.  It logs all files formatted
Eli Zaretskii's avatar
Eli Zaretskii committed
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
;; 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
267 268 269
;; 	     (lambda (arg)
;; 		set-visited-file-name
;; 		(file-name-sans-extension buffer-file-name)))))
Eli Zaretskii's avatar
Eli Zaretskii committed
270 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
;;       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
;; =====

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


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

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


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


;;; Code:

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

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

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

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

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

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

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

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

545
(defcustom woman-man.conf-path
546
  (let ((path '("/usr/lib" "/etc")))
547 548 549 550 551
    (cond ((eq system-type 'windows-nt)
	   (mapcar 'woman-Cyg-to-Win path))
	  ((eq system-type 'darwin)
	   (cons "/usr/share/misc" path))
	  (t path)))
552
  "List of dirs to search and/or files to try for man config file.
553 554 555 556 557 558
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'."
559 560 561 562
  :type '(repeat string)
  :group 'woman-interface)

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

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

667 668 669 670 671
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.

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

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

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

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

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

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

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

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

802
(defcustom woman-use-topic-at-point woman-use-topic-at-point-default
803
  "Control use of the word at point as the default topic.
804 805
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
806
  :type '(choice (const :tag "Yes" t)
807
		 (const :tag "No" nil))
Eli Zaretskii's avatar
Eli Zaretskii committed
808 809 810 811
  :group 'woman-interface)

(defvar woman-file-regexp nil
  "Regexp used to select (possibly compressed) man source files, e.g.
812
\"\\.\\([0-9lmnt]\\w*\\)\\(\\.\\(g?z\\|bz2\\|xz\\)\\)?\\'\".
Eli Zaretskii's avatar
Eli Zaretskii committed
813 814 815 816 817 818 819 820
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.
821
Used as :set cookie by Customize when customizing the user options
Eli Zaretskii's avatar
Eli Zaretskii committed
822 823 824 825 826 827 828 829 830 831 832 833
`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
834
  "Do not change this unless you are sure you know what you are doing!
Eli Zaretskii's avatar
Eli Zaretskii committed
835 836 837 838 839 840 841 842 843 844 845 846 847
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
848
  "\\.\\(g?z\\|bz2\\|xz\\)\\'"
849
  "Do not change this unless you are sure you know what you are doing!
Eli Zaretskii's avatar
Eli Zaretskii committed
850
Regexp used to match compressed man file extensions for which
851
decompressors are available and handled by auto-compression mode,
852
e.g. \"\\\\.\\\\(g?z\\\\|bz2\\\\|xz\\\\)\\\\'\" for `gzip', `bzip2', or `xz'.
Eli Zaretskii's avatar
Eli Zaretskii committed
853
Should begin with \\. and end with \\' and MUST NOT be optional."
854 855 856 857
  ;; 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!
858
  :version "24.1"                       ; added xz
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