comparison lisp/packages/jwz-man.el @ 4:b82b59fe008d r19-15b3

Import from CVS: tag r19-15b3
author cvs
date Mon, 13 Aug 2007 08:46:56 +0200
parents
children
comparison
equal deleted inserted replaced
3:30df88044ec6 4:b82b59fe008d
1 ;;; man.el --- browse UNIX manual pages
2 ;; Keywords: help
3
4 ;; Copyright (C) 1985, 1993, 1994, 1996 Free Software Foundation, Inc.
5 ;;
6 ;; This file is part of XEmacs.
7
8 ;; XEmacs is free software; you can redistribute it and/or modify it
9 ;; under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 2, or (at your option)
11 ;; any later version.
12
13 ;; XEmacs is distributed in the hope that it will be useful, but
14 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 ;; General Public License for more details.
17
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with XEmacs; see the file COPYING. If not, write to the Free
20 ;; Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
21
22 ;; This file defines "manual-entry", and the remaining definitions all
23 ;; begin with "Manual-". This makes the autocompletion on "M-x man" work.
24 ;;
25 ;; Eviscerated 26-Jun-96 by Jamie Zawinski <jwz@netscape.com>.
26 ;; All that stuff about looking at $MANPATH and building up lists of
27 ;; directories was bullshit. Now we just invoke "man" and format the
28 ;; output, end of story.
29 ;;
30 ;; [ older changelog entries removed, since they're all about code that
31 ;; I've deleted. ]
32
33 (defvar Manual-program "man" "\
34 *Name of the program to invoke in order to format the source man pages.")
35
36 (defvar Manual-buffer-view-mode t "\
37 *Whether manual buffers should be placed in view-mode.
38 nil means leave the buffer in fundamental-mode in another window.
39 t means use `view-buffer' to display the man page in the current window.
40 Any other value means use `view-buffer-other-window'.")
41
42 (defvar Manual-mode-hook nil
43 "Function or functions run on entry to Manual-mode.")
44
45 (defvar Manual-page-history nil "\
46 A list of names of previously visited man page buffers.")
47
48
49 ;; New variables.
50
51 (make-face 'man-italic)
52 (or (face-differs-from-default-p 'man-italic)
53 (copy-face 'italic 'man-italic))
54 ;; XEmacs (from Darrell Kindred): underlining is annoying due to
55 ;; large blank spaces in this face.
56 ;; (or (face-differs-from-default-p 'man-italic)
57 ;; (set-face-underline-p 'man-italic t))
58
59 (make-face 'man-bold)
60 (or (face-differs-from-default-p 'man-bold)
61 (copy-face 'bold 'man-bold))
62 (or (face-differs-from-default-p 'man-bold)
63 (copy-face 'man-italic 'man-bold))
64
65 (make-face 'man-heading)
66 (or (face-differs-from-default-p 'man-heading)
67 (copy-face 'man-bold 'man-heading))
68
69 (make-face 'man-xref)
70 (or (face-differs-from-default-p 'man-xref)
71 (set-face-underline-p 'man-xref t))
72
73 (defvar Manual-mode-map
74 (let ((m (make-sparse-keymap)))
75 (set-keymap-name m 'Manual-mode-map)
76 (define-key m "l" 'Manual-last-page)
77 (define-key m 'button2 'Manual-follow-xref)
78 (define-key m 'button3 'Manual-popup-menu)
79 m))
80
81 ;;;###autoload
82 (defun manual-entry (topic &optional arg silent)
83 "Display the Unix manual entry (or entries) for TOPIC."
84 (interactive
85 (list (let* ((fmh "-A-Za-z0-9_.")
86 (default (save-excursion
87 (buffer-substring
88 (progn
89 (re-search-backward "\\sw" nil t)
90 (skip-chars-backward fmh) (point))
91 (progn (skip-chars-forward fmh) (point)))))
92 (thing (read-string
93 (if (equal default "") "Manual entry: "
94 (concat "Manual entry: (default " default ") ")))))
95 (if (equal thing "") default thing))
96 (prefix-numeric-value current-prefix-arg)))
97 ;;(interactive "sManual entry (topic): \np")
98 (or arg (setq arg 1))
99 (let (section apropos-mode)
100 (let ((case-fold-search nil))
101 (if (and (null section)
102 (string-match "\\`[ \t]*\\([^( \t]+\\)[ \t]*(\\(.+\\))[ \t]*\\'"
103 topic))
104 (setq section (substring topic (match-beginning 2)
105 (match-end 2))
106 topic (substring topic (match-beginning 1)
107 (match-end 1)))
108 (if (string-match "\\`[ \t]*-k[ \t]+\\([^ \t]+\\)\\'" topic)
109 (setq section "-k"
110 topic (substring topic (match-beginning 1))))))
111
112 ;; jwz: turn section "3x11" and "3n" into "3".
113 (if (and section (string-match "\\`\\([0-9]+\\)[^0-9]" section))
114 (setq section (substring section 0 (match-end 1))))
115 (if (equal section "-k")
116 (setq apropos-mode t))
117
118 (let ((bufname (cond (apropos-mode
119 (concat "*man apropos " topic "*"))
120 (t
121 (concat "*man " topic
122 (if section (concat "." section) "")
123 "*"))))
124 (temp-buffer-show-function
125 (cond ((eq 't Manual-buffer-view-mode)
126 'view-buffer)
127 ((eq 'nil Manual-buffer-view-mode)
128 temp-buffer-show-function)
129 (t
130 'view-buffer-other-window))))
131
132 (cond ((get-buffer bufname)
133 ;; reselect an old man page buffer if it exists already.
134 (save-excursion
135 (set-buffer (get-buffer bufname))
136 (Manual-mode))
137 (if temp-buffer-show-function
138 (funcall temp-buffer-show-function (get-buffer bufname))
139 (display-buffer bufname)))
140 (t
141 (with-output-to-temp-buffer bufname
142 (buffer-disable-undo standard-output)
143 (save-excursion
144 (set-buffer standard-output)
145 (setq buffer-read-only nil)
146 (erase-buffer)
147
148 (let ((args (list topic))
149 args-string)
150 (if section
151 (setq args
152 (if (eq system-type 'usg-unix-v)
153 (cons "-s" (cons section args))
154 (cons section args))))
155 (setq args-string
156 (mapconcat 'identity (cons Manual-program args) " "))
157 (if (string-match "\\`\\([^ \t/]*/\\)+" args-string)
158 (setq args-string
159 (substring args-string (match-end 0))))
160
161 (message "%s (running...)" args-string)
162 (apply 'call-process Manual-program nil t nil args)
163
164 (if (< (buffer-size) 200)
165 (progn
166 (goto-char (point-min))
167 (error (buffer-substring (point)
168 (progn (end-of-line)
169 (point))))))
170
171 (message "%s (cleaning...)" args-string)
172 (Manual-nuke-nroff-bs apropos-mode)
173 (message "%s (done.)" args-string)
174 )
175
176 (set-buffer-modified-p nil)
177 (Manual-mode)
178 ))))
179 (setq Manual-page-history
180 (cons (buffer-name)
181 (delete (buffer-name) Manual-page-history)))))
182 (message nil)
183 t)
184
185 (defun Manual-mode ()
186 (kill-all-local-variables)
187 (setq buffer-read-only t)
188 (use-local-map Manual-mode-map)
189 (setq major-mode 'Manual-mode
190 mode-name "Manual")
191 ;; man pages with long lines are buggy!
192 ;; This looks slightly better if they only
193 ;; overran by a couple of chars.
194 (setq truncate-lines t)
195 ;; turn off horizontal scrollbars in this buffer
196 (set-specifier scrollbar-height (cons (current-buffer) 0))
197 (run-hooks 'Manual-mode-hook))
198
199 (defun Manual-last-page ()
200 (interactive)
201 (while (or (not (get-buffer (car (or Manual-page-history
202 (error "No more history.")))))
203 (eq (get-buffer (car Manual-page-history)) (current-buffer)))
204 (setq Manual-page-history (cdr Manual-page-history)))
205 (switch-to-buffer (car Manual-page-history)))
206
207
208 (defmacro Manual-delete-char (n)
209 ;; in v19, delete-char is compiled as a function call, but delete-region
210 ;; is byte-coded, so it's much faster. (We were spending 40% of our time
211 ;; in delete-char alone.)
212 (list 'delete-region '(point) (list '+ '(point) n)))
213
214 ;; Hint: BS stands form more things than "back space"
215 (defun Manual-nuke-nroff-bs (&optional apropos-mode)
216 (interactive "*")
217 ;;
218 ;; turn underlining into italics
219 ;;
220 (goto-char (point-min))
221 (while (search-forward "_\b" nil t)
222 ;; searching for underscore-backspace and then comparing the following
223 ;; chars until the sequence ends turns out to be much faster than searching
224 ;; for a regexp which matches the whole sequence.
225 (let ((s (match-beginning 0)))
226 (goto-char s)
227 (while (and (= (following-char) ?_)
228 (= (char-after (1+ (point))) ?\b))
229 (Manual-delete-char 2)
230 (forward-char 1))
231 (set-extent-face (make-extent s (point)) 'man-italic)))
232 ;;
233 ;; turn overstriking into bold
234 ;;
235 (goto-char (point-min))
236 (while (re-search-forward "\\([^\n]\\)\\(\b\\1\\)" nil t)
237 ;; Surprisingly, searching for the above regexp is faster than searching
238 ;; for a backspace and then comparing the preceding and following chars,
239 ;; I presume because there are many false matches, meaning more funcalls
240 ;; to re-search-forward.
241 (let ((s (match-beginning 0)))
242 (goto-char s)
243 ;; Some systems (SGI) overstrike multiple times, eg, "M\bM\bM\bM".
244 (while (looking-at "\\([^\n]\\)\\(\b\\1\\)+")
245 (delete-region (+ (point) 1) (match-end 0))
246 (forward-char 1))
247 (set-extent-face (make-extent s (point)) 'man-bold)))
248 ;;
249 ;; hack bullets: o^H+ --> +
250 (goto-char (point-min))
251 (while (search-forward "\b" nil t)
252 (Manual-delete-char -2))
253
254 (if (> (buffer-size) 100) ; minor kludge
255 (Manual-nuke-nroff-bs-footers))
256 ;;
257 ;; turn subsection header lines into bold
258 ;;
259 (goto-char (point-min))
260 (if apropos-mode
261 (while (re-search-forward "[a-zA-Z0-9] ([0-9]" nil t)
262 (forward-char -2)
263 (delete-backward-char 1))
264
265 ;; (while (re-search-forward "^[^ \t\n]" nil t)
266 ;; (set-extent-face (make-extent (match-beginning 0)
267 ;; (progn (end-of-line) (point)))
268 ;; 'man-heading))
269
270 ;; boldface the first line
271 (if (looking-at "[^ \t\n].*$")
272 (set-extent-face (make-extent (match-beginning 0) (match-end 0))
273 'man-bold))
274
275 ;; boldface subsequent title lines
276 ;; Regexp to match section headers changed to match a non-indented
277 ;; line preceded by a blank line and followed by an indented line.
278 ;; This seems to work ok for manual pages but gives better results
279 ;; with other nroff'd files
280 (while (re-search-forward "\n\n\\([^ \t\n].*\\)\n[ \t]+[^ \t\n]" nil t)
281 (goto-char (match-end 1))
282 (set-extent-face (make-extent (match-beginning 1) (match-end 1))
283 'man-heading)
284 (forward-line 1))
285 )
286
287 ;; Zap ESC7, ESC8, and ESC9
288 ;; This is for Sun man pages like "man 1 csh"
289 (goto-char (point-min))
290 (while (re-search-forward "\e[789]" nil t)
291 (replace-match ""))
292
293 ;; Nuke blanks lines at start.
294 ;; (goto-char (point-min))
295 ;; (skip-chars-forward "\n")
296 ;; (delete-region (point-min) (point))
297
298 (Manual-mouseify-xrefs)
299 )
300
301 (fset 'nuke-nroff-bs 'Manual-nuke-nroff-bs) ; use old name
302
303
304 (defun Manual-nuke-nroff-bs-footers ()
305 ;; Nuke headers and footers.
306 ;;
307 ;; nroff assumes pages are 66 lines high. We assume that, and that the
308 ;; first and last line on each page is expendible. There is no way to
309 ;; tell the difference between a page break in the middle of a paragraph
310 ;; and a page break between paragraphs (the amount of extra whitespace
311 ;; that nroff inserts is the same in both cases) so this might strip out
312 ;; a blank line were one should remain. I think that's better than
313 ;; leaving in a blank line where there shouldn't be one. (Need I say
314 ;; it: FMH.)
315 ;;
316 ;; Note that if nroff spits out error messages, pages will be more than
317 ;; 66 lines high, and we'll lose badly. That's ok because standard
318 ;; nroff doesn't do any diagnostics, and the "gnroff" wrapper for groff
319 ;; turns off error messages for compatibility. (At least, it's supposed
320 ;; to.)
321 ;;
322 (goto-char (point-min))
323 ;; first lose the status output
324 (let ((case-fold-search t))
325 (if (and (not (looking-at "[^\n]*warning"))
326 (looking-at "Reformatting.*\n"))
327 (delete-region (match-beginning 0) (match-end 0))))
328
329 ;; kludge around a groff bug where it won't keep quiet about some
330 ;; warnings even with -Wall or -Ww.
331 (cond ((looking-at "grotty:")
332 (while (looking-at "grotty:")
333 (delete-region (point) (progn (forward-line 1) (point))))
334 (if (looking-at " *done\n")
335 (delete-region (point) (match-end 0)))))
336
337 (let ((pages '())
338 p)
339 ;; collect the page boundary markers before we start deleting, to make
340 ;; it easier to strip things out without changing the page sizes.
341 (while (not (eobp))
342 (forward-line 66)
343 (setq pages (cons (point-marker) pages)))
344 (setq pages (nreverse pages))
345 (while pages
346 (goto-char (car pages))
347 (set-marker (car pages) nil)
348 ;;
349 ;; The lines are: 3 blank; footer; 6 blank; header; 3 blank.
350 ;; We're in between the previous footer and the following header,
351 ;;
352 ;; First lose 3 blank lines, the header, and then 3 more.
353 ;;
354 (setq p (point))
355 (skip-chars-forward "\n")
356 (delete-region p (point))
357 (and (looking-at "[^\n]+\n\n?\n?\n?")
358 (delete-region (match-beginning 0) (match-end 0)))
359 ;;
360 ;; Next lose the footer, and the 3 blank lines after, and before it.
361 ;; But don't lose the last footer of the manual entry; that contains
362 ;; the "last change" date, so it's not completely uninteresting.
363 ;; (Actually lose all blank lines before it; sh(1) needs this.)
364 ;;
365 (skip-chars-backward "\n")
366 (beginning-of-line)
367 (if (null (cdr pages))
368 nil
369 (and (looking-at "[^\n]+\n\n?\n?\n?")
370 (delete-region (match-beginning 0) (match-end 0))))
371 (setq p (point))
372 (skip-chars-backward "\n")
373 (if (> (- p (point)) 4)
374 (delete-region (+ 2 (point)) p)
375 (delete-region (1+ (point)) p))
376 ; (and (looking-at "\n\n?\n?")
377 ; (delete-region (match-beginning 0) (match-end 0)))
378
379 (setq pages (cdr pages)))
380 ;;
381 ;; Now nuke the extra blank lines at the beginning and end.
382 (goto-char (point-min))
383 (if (looking-at "\n+")
384 (delete-region (match-beginning 0) (match-end 0)))
385 (forward-line 1)
386 (if (looking-at "\n\n+")
387 (delete-region (1+ (match-beginning 0)) (match-end 0)))
388 (goto-char (point-max))
389 (skip-chars-backward "\n")
390 (delete-region (point) (point-max))
391 (beginning-of-line)
392 (forward-char -1)
393 (setq p (point))
394 (skip-chars-backward "\n")
395 (if (= ?\n (following-char)) (forward-char 1))
396 (if (> (point) (1+ p))
397 (delete-region (point) p))
398 ))
399
400 (defun Manual-mouseify-xrefs ()
401 (goto-char (point-min))
402 (forward-line 1)
403 (let ((case-fold-search nil)
404 s e name extent)
405 ;; possibly it would be faster to rewrite this expression to search for
406 ;; a less common sequence first (like "([0-9]") and then back up to see
407 ;; if it's really a match. This function is 15% of the total time, 13%
408 ;; of which is this call to re-search-forward.
409 (while (re-search-forward "[a-zA-Z_][-a-zA-Z0-9_.]*([0-9][a-zA-Z0-9]*)"
410 nil t)
411 (setq s (match-beginning 0)
412 e (match-end 0)
413 name (buffer-substring s e))
414 (goto-char s)
415 (skip-chars-backward " \t")
416 (if (and (bolp)
417 (progn (backward-char 1) (= (preceding-char) ?-)))
418 (progn
419 (setq s (point))
420 (skip-chars-backward "-a-zA-Z0-9_.")
421 (setq name (concat (buffer-substring (point) (1- s)) name))
422 (setq s (point))))
423 ;; if there are upper case letters in the section, downcase them.
424 (if (string-match "(.*[A-Z]+.*)$" name)
425 (setq name (concat (substring name 0 (match-beginning 0))
426 (downcase (substring name (match-beginning 0))))))
427 ;; (setq already-fontified (extent-at s))
428 (setq extent (make-extent s e))
429 (set-extent-property extent 'man (list 'Manual-follow-xref name))
430 (set-extent-property extent 'highlight t)
431 ;; (if (not already-fontified)...
432 (set-extent-face extent 'man-xref)
433 (goto-char e))))
434
435 (defun Manual-follow-xref (&optional name-or-event)
436 "Invoke `manual-entry' on the cross-reference under the mouse.
437 When invoked noninteractively, the arg may be an xref string to parse instead."
438 (interactive "e")
439 (if (eventp name-or-event)
440 (let* ((p (event-point name-or-event))
441 (extent (and p (extent-at p
442 (event-buffer name-or-event)
443 'highlight)))
444 (data (and extent (extent-property extent 'man))))
445 (if (eq (car-safe data) 'Manual-follow-xref)
446 (eval data)
447 (error "no manual cross-reference there.")))
448 (or (manual-entry name-or-event)
449 ;; If that didn't work, maybe it's in a different section than the
450 ;; man page writer expected. For example, man pages tend assume
451 ;; that all user programs are in section 1, but X tends to generate
452 ;; makefiles that put things in section "n" instead...
453 (and (string-match "[ \t]*([^)]+)\\'" name-or-event)
454 (progn
455 (message "No entries found for %s; checking other sections..."
456 name-or-event)
457 (manual-entry
458 (substring name-or-event 0 (match-beginning 0))
459 nil t))))))
460
461 (defun Manual-popup-menu (&optional event)
462 "Pops up a menu of cross-references in this manual page.
463 If there is a cross-reference under the mouse button which invoked this
464 command, it will be the first item on the menu. Otherwise, they are
465 on the menu in the order in which they appear in the buffer."
466 (interactive "e")
467 (let ((buffer (current-buffer))
468 (sep "---")
469 (prefix "Show Manual Page for ")
470 xref items)
471 (cond (event
472 (setq buffer (event-buffer event))
473 (let* ((p (event-point event))
474 (extent (and p (extent-at p buffer 'highlight)))
475 (data (and extent (extent-property extent 'man))))
476 (if (eq (car-safe data) 'Manual-follow-xref)
477 (setq xref (nth 1 data))))))
478 (if xref (setq items (list sep xref)))
479 (map-extents #'(lambda (extent ignore)
480 (let ((data (extent-property extent 'man)))
481 (if (and (eq (car-safe data) 'Manual-follow-xref)
482 (not (member (nth 1 data) items)))
483 (setq items (cons (nth 1 data) items)))
484 nil))
485 buffer)
486 (if (eq sep (car items)) (setq items (cdr items)))
487 (let ((popup-menu-titles nil))
488 (popup-menu
489 (cons "Manual Entry"
490 (mapcar #'(lambda (item)
491 (if (eq item sep)
492 item
493 (vector (concat prefix item)
494 (list 'Manual-follow-xref item) t)))
495 (nreverse items)))))))
496
497 (defun pager-cleanup-hook ()
498 "cleanup man page if called via $PAGER"
499 (let ((buf-name (or buffer-file-name (buffer-name))))
500 (if (or (string-match "^/tmp/man[0-9]+" buf-name)
501 (string-match ".*/man/\\(man\\|cat\\)[1-9a-z]/" buf-name))
502 (let (buffer manpage)
503 (require 'man)
504 (goto-char (point-min))
505 (setq buffer-read-only nil)
506 (Manual-nuke-nroff-bs)
507 (goto-char (point-min))
508 (if (re-search-forward "[^ \t]")
509 (goto-char (- (point) 1)))
510 (if (looking-at "\\([a-zA-Z0-9]+\\)[ \t]*(")
511 (setq manpage (buffer-substring (match-beginning 1)
512 (match-end 1)))
513 (setq manpage "???"))
514 (setq buffer
515 (rename-buffer
516 (generate-new-buffer-name (concat "*man " manpage "*"))))
517 (setq buffer-file-name nil)
518 (goto-char (point-min))
519 (insert (format "%s\n" buf-name))
520 (goto-char (point-min))
521 (buffer-disable-undo buffer)
522 (set-buffer-modified-p nil)
523 (Manual-mode)
524 ))))
525
526 (add-hook 'server-visit-hook 'pager-cleanup-hook)
527 (provide 'man)
528
529 ;;; man.el ends here