X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/f4008b6ee93cd08138156009ef0c91d4f107776d..552775bd93ad39944730fa504f385412d24fe46b:/lisp/info.el diff --git a/lisp/info.el b/lisp/info.el index e207964410..83247a9332 100644 --- a/lisp/info.el +++ b/lisp/info.el @@ -47,7 +47,21 @@ The Lisp code is executed when the node is selected.") This value is used as the default for `Info-directory-list'. It is set in paths.el.") -(defvar Info-directory-list nil +(defvar Info-fontify t + "*Non-nil enables highlighting and fonts in Info nodes.") + +(defvar Info-directory-list + (let ((path (getenv "INFOPATH"))) + (if path + (let ((list nil) + idx) + (while (> (length path) 0) + (setq idx (or (string-match ":" path) (length path)) + list (cons (substring path 0 idx) list) + path (substring path (min (1+ idx) + (length path))))) + (nreverse list)) + Info-default-directory-list)) "List of directories to search for Info documentation files. nil means not yet initialized. In this case, Info uses the environment variable INFOPATH to initialize it, or `Info-default-directory-list' @@ -67,17 +81,25 @@ or nil if current info file is not split into subfiles.") "Marker pointing at beginning of current Info file's tag table. Marker points nowhere if file has no tag table.") +(defvar Info-current-file-completions nil + "Cached completion list for current Info file.") + (defvar Info-index-alternatives nil "List of possible matches for last Info-index command.") -(defvar Info-suffix-list '( ("" . nil) - (".info" . nil) - (".Z" . "uncompress") - (".Y" . "unyabba") - (".z" . "gunzip") - (".info.Z" . "uncompress") - (".info.Y" . "unyabba") - (".info.z" . "gunzip")) +(defvar Info-standalone nil + "Non-nil if Emacs was started solely as an Info browser.") + +(defvar Info-suffix-list '( (".info" . nil) + ("" . nil) + (".Z" . "uncompress") + (".Y" . "unyabba") + (".gz" . "gunzip") + (".z" . "gunzip") + (".info.Z" . "uncompress") + (".info.Y" . "unyabba") + (".info.gz" . "gunzip") + (".info.z" . "gunzip")) "List of file name suffixes and associated decoding commands. Each entry should be (SUFFIX . STRING); the file is given to the command as standard input. If STRING is nil, no decoding is done.") @@ -112,25 +134,30 @@ In interactive use, a prefix argument directs this command to read a file name from the minibuffer." (interactive (if current-prefix-arg (list (read-file-name "Info file name: " nil nil t)))) - (or Info-directory-list - (setq Info-directory-list - (let ((path (getenv "INFOPATH"))) - (if path - (let ((list nil) - idx) - (while (> (length path) 0) - (setq idx (or (string-match ":" path) (length path)) - list (cons (substring path 0 idx) list) - path (substring path (min (1+ idx) - (length path))))) - (nreverse list)) - Info-default-directory-list)))) (if file (Info-goto-node (concat "(" file ")")) (if (get-buffer "*info*") (switch-to-buffer "*info*") (Info-directory)))) +;;;###autoload +(defun info-standalone () + "Run Emacs as a standalone Info reader. +Usage: emacs -f info-standalone [filename] +In standalone mode, \\\\[Info-exit] exits Emacs itself." + (setq Info-standalone t) + (if (and command-line-args-left + (not (string-match "^-" (car command-line-args-left)))) + (condition-case err + (progn + (info (car command-line-args-left)) + (setq command-line-args-left (cdr command-line-args-left))) + (error (send-string-to-terminal + (format "%s\n" (if (eq (car-safe err) 'error) + (nth 1 err) err))) + (save-buffers-kill-emacs))) + (info))) + ;; Go to an info node specified as separate filename and nodename. ;; no-going-back is non-nil if recovering from an error in this function; ;; it says do not attempt further (recursive) error recovery. @@ -190,6 +217,7 @@ to read a file name from the minibuffer." (let ((buffer-read-only nil)) (setq Info-current-file nil Info-current-subfile nil + Info-current-file-completions nil Info-index-alternatives nil buffer-file-name nil) (erase-buffer) @@ -279,12 +307,23 @@ to read a file name from the minibuffer." ;; of the merged dir text. (defvar Info-dir-contents-directory nil) +;; Record the file attributes of all the files from which we +;; constructed Info-dir-contents. +(defvar Info-dir-file-attributes nil) + ;; Construct the Info directory node by merging the files named `dir' ;; from various directories. Set the *info* buffer's ;; default-directory to the first directory we actually get any text ;; from. (defun Info-insert-dir () - (if Info-dir-contents + (if (and Info-dir-contents Info-dir-file-attributes + ;; Verify that none of the files we used has changed + ;; since we used it. + (eval (cons 'and + (mapcar '(lambda (elt) + (equal (cdr elt) + (file-attributes (car elt)))) + Info-dir-file-attributes)))) (insert Info-dir-contents) (let ((dirs Info-directory-list) buffers buffer others nodes dirs-done) @@ -313,8 +352,14 @@ to read a file name from the minibuffer." (find-file-noselect temp))))) (setq dirs-done (cons (file-truename (expand-file-name (car dirs))) - dirs-done)) - (if buffer (setq buffers (cons buffer buffers))))) + (cons (directory-file-name + (file-truename (expand-file-name (car dirs)))) + dirs-done))) + (if buffer (setq buffers (cons buffer buffers) + Info-dir-file-attributes + (cons (cons (buffer-file-name buffer) + (file-attributes (buffer-file-name buffer))) + Info-dir-file-attributes))))) (setq dirs (cdr dirs))) ;; Distinguish the dir file that comes with Emacs from all the @@ -458,7 +503,9 @@ to read a file name from the minibuffer." (setq active-expression (read (current-buffer)))))) (point-max))) - (if Info-enable-active-nodes (eval active-expression))))) + (if Info-enable-active-nodes (eval active-expression)) + (if Info-fontify (Info-fontify-node)) + (run-hooks 'Info-selection-hook)))) (defun Info-set-mode-line () (setq mode-line-buffer-identification @@ -475,7 +522,7 @@ to read a file name from the minibuffer." (defun Info-goto-node (nodename) "Go to info node named NAME. Give just NODENAME or (FILENAME)NODENAME." - (interactive "sGoto node: ") + (interactive (list (Info-read-node-name "Goto node: "))) (let (filename) (string-match "\\s *\\((\\s *\\([^\t)]*\\)\\s *)\\s *\\|\\)\\(.*\\)" nodename) @@ -489,6 +536,42 @@ to read a file name from the minibuffer." (if trim (setq nodename (substring nodename 0 trim)))) (Info-find-node (if (equal filename "") nil filename) (if (equal nodename "") "Top" nodename)))) + +(defun Info-read-node-name (prompt &optional default) + (let* ((completion-ignore-case t) + (nodename (completing-read prompt (Info-build-node-completions)))) + (if (equal nodename "") + (or default + (Info-read-node-name prompt)) + nodename))) + +(defun Info-build-node-completions () + (or Info-current-file-completions + (let ((compl nil)) + (save-excursion + (save-restriction + (if (marker-buffer Info-tag-table-marker) + (progn + (set-buffer (marker-buffer Info-tag-table-marker)) + (goto-char Info-tag-table-marker) + (while (re-search-forward "\nNode: \\(.*\\)\177" nil t) + (setq compl + (cons (list (buffer-substring (match-beginning 1) + (match-end 1))) + compl)))) + (widen) + (goto-char (point-min)) + (while (search-forward "\n\^_" nil t) + (forward-line 1) + (let ((beg (point))) + (forward-line 1) + (if (re-search-backward "Node: *\\([^,\n]*\\) *[,\n\t]" + beg t) + (setq compl + (cons (list (buffer-substring (match-beginning 1) + (match-end 1))) + compl)))))))) + (setq Info-current-file-completions compl)))) (defun Info-restore-point (hl) "If this node has been visited, restore the point value when we left." @@ -659,11 +742,13 @@ NAME may be an abbreviation of the reference name." (cons (cons str nil) completions)))) (if completions - (list (completing-read (if default - (concat "Follow reference named: (" - default ") ") - "Follow reference named: ") - completions default t)) + (let ((input (completing-read (if default + (concat "Follow reference named: (" + default ") ") + "Follow reference named: ") + completions nil t))) + (list (if (equal input "") + default input))) (error "No cross-references in this node")))) (let (target beg i (str (concat "\\*note " footnotename))) (while (setq i (string-match " " str i)) @@ -859,8 +944,10 @@ N is the digit argument used to invoke this command." (defun Info-exit () "Exit Info by selecting some other buffer." (interactive) - (switch-to-buffer (prog1 (other-buffer (current-buffer)) - (bury-buffer (current-buffer))))) + (if Info-standalone + (save-buffers-kill-emacs) + (switch-to-buffer (prog1 (other-buffer (current-buffer)) + (bury-buffer (current-buffer)))))) (defun Info-next-menu-item () (interactive) @@ -880,21 +967,24 @@ N is the digit argument used to invoke this command." (error "No previous items in menu")) (Info-goto-node (Info-extract-menu-node-name)))) -(defmacro no-error (&rest body) +(defmacro Info-no-error (&rest body) (list 'condition-case nil (cons 'progn (append body '(t))) '(error nil))) (defun Info-next-preorder () "Go to the next node, popping up a level if there is none." (interactive) - (cond ((no-error (Info-next-menu-item)) ) - ((no-error (Info-up)) (forward-line 1)) + (cond ((looking-at "\\*note[ \n]*\\([^:]*\\):") + (Info-follow-reference + (buffer-substring (match-beginning 1) (match-end 1)))) + ((Info-no-error (Info-next-menu-item)) ) + ((Info-no-error (Info-up)) (forward-line 1)) (t (error "No more nodes")))) (defun Info-last-preorder () "Go to the last node, popping up a level if there is none." (interactive) - (cond ((no-error (Info-last-menu-item)) ) - ((no-error (Info-up)) (forward-line -1)) + (cond ((Info-no-error (Info-last-menu-item)) ) + ((Info-no-error (Info-up)) (forward-line -1)) (t (error "No previous nodes")))) (defun Info-scroll-up () @@ -913,6 +1003,39 @@ N is the digit argument used to invoke this command." (scroll-down)) ) +(defun Info-next-reference () + "Move cursor to the next cross-reference or menu item in the node." + (interactive) + (let ((pat "\\*note[ \n\t]*\\([^:]*\\):\\|^\\* .*:") + (old-pt (point))) + (or (eobp) (forward-char 1)) + (or (re-search-forward pat nil t) + (progn + (goto-char (point-min)) + (or (re-search-forward pat nil t) + (progn + (goto-char old-pt) + (error "No cross references in this node"))))) + (goto-char (match-beginning 0)) + (if (looking-at "\\* Menu:") + (Info-next-reference)))) + +(defun Info-prev-reference () + "Move cursor to the previous cross-reference or menu item in the node." + (interactive) + (let ((pat "\\*note[ \n\t]*\\([^:]*\\):\\|^\\* .*:") + (old-pt (point))) + (or (re-search-backward pat nil t) + (progn + (goto-char (point-max)) + (or (re-search-backward pat nil t) + (progn + (goto-char old-pt) + (error "No cross references in this node"))))) + (goto-char (match-beginning 0)) + (if (looking-at "\\* Menu:") + (Info-prev-reference)))) + (defun Info-index (topic) "Look up a string in the index for this file. The index is defined as the first node in the top-level menu whose @@ -1034,7 +1157,8 @@ Give a blank topic name to go to the Index node itself." (if (not (eq ?\ (setq ch (read-event)))) (progn (setq unread-command-events (list ch)) nil) flag)) - (scroll-up))))) + (scroll-up))) + (bury-buffer "*Help*"))) (defun Info-get-token (pos start all &optional errorstring) "Return the token around POS, @@ -1052,19 +1176,20 @@ SIG optional fourth argument, controls action on no match (save-excursion (goto-char pos) (re-search-backward start (max (point-min) (- pos 200)) 'yes) - (while (and (re-search-forward all (min (point-max) (+ pos 200)) 'yes) - (not (and (<= (match-beginning 0) pos) - (> (match-end 0) pos))))) - (if (and (<= (match-beginning 0) pos) - (> (match-end 0) pos)) - (buffer-substring (match-beginning 1) (match-end 1)) - (cond ((null errorstring) - nil) - ((eq errorstring t) - (beep) - nil) - (t - (error "No %s around position %d" errorstring pos)))))) + (let (found) + (while (and (re-search-forward all (min (point-max) (+ pos 200)) 'yes) + (not (setq found (and (<= (match-beginning 0) pos) + (> (match-end 0) pos)))))) + (if (and found (<= (match-beginning 0) pos) + (> (match-end 0) pos)) + (buffer-substring (match-beginning 1) (match-end 1)) + (cond ((null errorstring) + nil) + ((eq errorstring t) + (beep) + nil) + (t + (error "No %s around position %d" errorstring pos))))))) (defun Info-follow-nearest-node (click) "\\Follow a node reference near point. @@ -1105,6 +1230,8 @@ At end of the node's text, moves to the next node." (define-key Info-mode-map "." 'beginning-of-buffer) (define-key Info-mode-map " " 'Info-scroll-up) (define-key Info-mode-map "\C-m" 'Info-next-preorder) + (define-key Info-mode-map "\t" 'Info-next-reference) + (define-key Info-mode-map "\e\t" 'Info-prev-reference) (define-key Info-mode-map "1" 'Info-nth-menu-item) (define-key Info-mode-map "2" 'Info-nth-menu-item) (define-key Info-mode-map "3" 'Info-nth-menu-item) @@ -1133,11 +1260,11 @@ At end of the node's text, moves to the next node." (define-key Info-mode-map "p" 'Info-prev) (define-key Info-mode-map "q" 'Info-exit) (define-key Info-mode-map "s" 'Info-search) - (define-key Info-mode-map "t" 'Info-top) + (define-key Info-mode-map "t" 'Info-top-node) (define-key Info-mode-map "u" 'Info-up) (define-key Info-mode-map "," 'Info-index-next) (define-key Info-mode-map "\177" 'Info-scroll-down) - (define-key Info-mode-map [mouse-3] 'Info-follow-nearest-node) + (define-key Info-mode-map [mouse-2] 'Info-follow-nearest-node) ) ;; Info mode is suitable only for specially formatted data. @@ -1178,11 +1305,14 @@ Advanced commands: 2, 3, 4, 5 Pick second ... fifth item in node's menu. \\[Info-goto-node] Move to node specified by name. You may include a filename as well, as (FILENAME)NODENAME. +\\[universal-argument] \\[info] Move to new Info file with completion. \\[Info-search] Search through this Info file for specified regexp, and select the node in which the next occurrence is found. \\[Info-next-preorder] Next-preorder; that is, try to go to the next menu item, and if that fails try to move up, and if that fails, tell user - he/she is done reading." + he/she is done reading. +\\[Info-next-reference] Move cursor to next cross-reference or menu item. +\\[Info-prev-reference] Move cursor to previous cross-reference or menu item." (kill-all-local-variables) (setq major-mode 'Info-mode) (setq mode-name "Info") @@ -1197,6 +1327,20 @@ Advanced commands: (make-local-variable 'Info-tag-table-marker) (make-local-variable 'Info-history) (make-local-variable 'Info-index-alternatives) + (if (fboundp 'make-face) + (progn + (make-face 'info-node) + (make-face 'info-menu-5) + (make-face 'info-xref) + (or (face-differs-from-default-p 'info-node) + (if (face-differs-from-default-p 'bold-italic) + (copy-face 'bold-italic 'info-node) + (copy-face 'bold 'info-node))) + (or (face-differs-from-default-p 'info-menu-5) + (set-face-underline-p 'info-menu-5 t)) + (or (face-differs-from-default-p 'info-xref) + (copy-face 'bold 'info-xref))) + (setq Info-fontify nil)) (Info-set-mode-line) (run-hooks 'Info-mode-hook)) @@ -1315,6 +1459,40 @@ Interactively, if the binding is execute-extended-command, a command is read." (read-command "Find documentation for command: "))) (t (Info-goto-emacs-command-node command))))) + +(defun Info-fontify-node () + (save-excursion + (let ((buffer-read-only nil)) + (goto-char (point-min)) + (if (looking-at "^File: [^,: \t]+,?[ \t]+") + (progn + (goto-char (match-end 0)) + (while + (looking-at "[ \t]*[^:, \t\n]+:[ \t]+\\([^:,\t\n]+\\),?") + (goto-char (match-end 0)) + (put-text-property (match-beginning 1) (match-end 1) + 'face 'info-xref)))) + (goto-char (point-min)) + (while (re-search-forward "\\*Note[ \n\t]*\\([^:]*\\):" nil t) + (if (= (char-after (1- (match-beginning 0))) ?\") ; hack + nil + (put-text-property (match-beginning 1) (match-end 1) + 'face 'info-xref))) + (goto-char (point-min)) + (if (and (search-forward "\n* Menu:" nil t) + (not (string-match "\\" Info-current-node)) + ;; Don't take time to annotate huge menus + (< (- (point-max) (point)) 10000)) + (let ((n 0)) + (while (re-search-forward "^\\* \\([^:\t\n]*\\):" nil t) + (setq n (1+ n)) + (if (memq n '(5 9)) ; visual aids to help with 1-9 keys + (put-text-property (match-beginning 0) + (1+ (match-beginning 0)) + 'face 'info-menu-5)) + (put-text-property (match-beginning 1) (match-end 1) + 'face 'info-node)))) + (set-buffer-modified-p nil)))) (provide 'info)