Mercurial > hg > xemacs-beta
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 |