]> code.delx.au - gnu-emacs/blobdiff - lisp/info.el
(Info-summary): Handle any event when flushing the display.
[gnu-emacs] / lisp / info.el
index 7d0f0d61fedbe81eee3206c8691e7ce73cedc7a7..b3744c8bcd68f2d8d5613a817a7b84736cfb6da3 100644 (file)
@@ -1,12 +1,15 @@
-;;; info.el --- info package for Emacs  -- could use a "create node" feature.
+;;; info.el --- info package for Emacs.
 
-;; Copyright (C) 1985, 1986 Free Software Foundation, Inc.
+;; Copyright (C) 1985, 1986, 1992 Free Software Foundation, Inc.
+
+;; Maintainer: FSF
+;; Keywords: help
 
 ;; This file is part of GNU Emacs.
 
 ;; GNU Emacs is free software; you can redistribute it and/or modify
 ;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 1, or (at your option)
+;; the Free Software Foundation; either version 2, or (at your option)
 ;; any later version.
 
 ;; GNU Emacs is distributed in the hope that it will be useful,
 ;; along with GNU Emacs; see the file COPYING.  If not, write to
 ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 
+;;; Commentary:
+
+;;; Note that nowadays we expect info files to be made using makeinfo.
+
+;;; Code:
+
 (defvar Info-history nil
   "List of info nodes user has visited.
 Each element of list is a list (FILENAME NODENAME BUFFERPOS).")
@@ -41,8 +50,8 @@ in paths.el.")
 (defvar Info-directory-list nil
   "List of directories to search for Info documentation files.
 nil means not yet initialized.  In this case, Info uses the environment
-variable INFODIR to initialize it, or `Info-default-directory-list'
-if there is no INFODIR variable in the environment.")
+variable INFOPATH to initialize it, or `Info-default-directory-list'
+if there is no INFOPATH variable in the environment.")
 
 (defvar Info-current-file nil
   "Info file that Info is now looking at, or nil.")
@@ -96,27 +105,29 @@ to read a file name from the minibuffer."
   (if filename
       (let (temp temp-downcase found)
        (setq filename (substitute-in-file-name filename))
-       (let ((dirs (if (string-match "^\\./" filename)
-                       ;; If specified name starts with `./'
-                       ;; then just try current directory.
-                       '("./")
-                     Info-directory-list)))
-         ;; Search the directory list for file FILENAME.
-         (while (and dirs (not found))
-           (setq temp (expand-file-name filename (car dirs)))
-           (setq temp-downcase
-                 (expand-file-name (downcase filename) (car dirs)))
-           ;; Try several variants of specified name.
-           ;; Try downcasing, appending `.info', or both.
-           (cond ((file-exists-p temp)
-                  (setq found temp))
-                 ((file-exists-p temp-downcase)
-                  (setq found temp-downcase))
-                 ((file-exists-p (concat temp ".info"))
-                  (setq found (concat temp ".info")))
-                 ((file-exists-p (concat temp-downcase ".info"))
-                  (setq found (concat temp-downcase ".info"))))
-           (setq dirs (cdr dirs))))
+       (if (string= (downcase (file-name-nondirectory filename)) "dir")
+           (setq found t)
+         (let ((dirs (if (string-match "^\\./" filename)
+                         ;; If specified name starts with `./'
+                         ;; then just try current directory.
+                         '("./")
+                       Info-directory-list)))
+           ;; Search the directory list for file FILENAME.
+           (while (and dirs (not found))
+             (setq temp (expand-file-name filename (car dirs)))
+             (setq temp-downcase
+                   (expand-file-name (downcase filename) (car dirs)))
+             ;; Try several variants of specified name.
+             ;; Try downcasing, appending `.info', or both.
+             (cond ((file-exists-p temp)
+                    (setq found temp))
+                   ((file-exists-p temp-downcase)
+                    (setq found temp-downcase))
+                   ((file-exists-p (concat temp ".info"))
+                    (setq found (concat temp ".info")))
+                   ((file-exists-p (concat temp-downcase ".info"))
+                    (setq found (concat temp-downcase ".info"))))
+             (setq dirs (cdr dirs)))))
        (if found
            (setq filename found)
          (error "Info file %s does not exist" filename))))
@@ -127,7 +138,7 @@ to read a file name from the minibuffer."
                  Info-history)))
   ;; Go into info buffer.
   (switch-to-buffer "*info*")
-  (buffer-flush-undo (current-buffer))
+  (buffer-disable-undo (current-buffer))
   (or (eq major-mode 'Info-mode)
       (Info-mode))
   (widen)
@@ -142,9 +153,11 @@ to read a file name from the minibuffer."
                    Info-current-subfile nil
                    buffer-file-name nil)
              (erase-buffer)
-             (insert-file-contents filename t)
+             (if (eq filename t)
+                 (Info-insert-dir)
+               (insert-file-contents filename t)
+               (setq default-directory (file-name-directory filename)))
              (set-buffer-modified-p nil)
-             (setq default-directory (file-name-directory filename))
              ;; See whether file has a tag table.  Record the location if yes.
              (set-marker Info-tag-table-marker nil)
              (goto-char (point-max))
@@ -164,28 +177,30 @@ to read a file name from the minibuffer."
                        (save-excursion
                          (let ((buf (current-buffer)))
                            (set-buffer (get-buffer-create " *info tag table*"))
-                            (buffer-flush-undo (current-buffer))
+                            (buffer-disable-undo (current-buffer))
                            (setq case-fold-search t)
                            (erase-buffer)
                            (insert-buffer-substring buf)
                            (set-marker Info-tag-table-marker
                                        (match-end 0))))
-                    (set-marker Info-tag-table-marker pos))))
+                     (set-marker Info-tag-table-marker pos))))
              (setq Info-current-file
-                   (file-name-sans-versions buffer-file-name))))
+                   (if (eq filename t) "dir"
+                     (file-name-sans-versions buffer-file-name)))))
        (if (equal nodename "*")
            (progn (setq Info-current-node nodename)
                   (Info-set-mode-line))
          ;; Search file for a suitable node.
-         ;; First get advice from tag table if file has one.
-         ;; Also, if this is an indirect info file,
-         ;; read the proper subfile into this buffer.
-         (let ((guesspos (point-min)))
+         (let ((guesspos (point-min))
+               (regexp (concat "Node: *" (regexp-quote nodename) " *[,\t\n\177]")))
+           ;; First get advice from tag table if file has one.
+           ;; Also, if this is an indirect info file,
+           ;; read the proper subfile into this buffer.
            (if (marker-position Info-tag-table-marker)
                (save-excursion
                  (set-buffer (marker-buffer Info-tag-table-marker))
                  (goto-char Info-tag-table-marker)
-                 (if (search-forward (concat "Node: " nodename "\177") nil t)
+                 (if (re-search-forward regexp nil t)
                      (progn
                        (setq guesspos (read (current-buffer)))
                        ;; If this is an indirect file,
@@ -195,10 +210,9 @@ to read a file name from the minibuffer."
                            (setq guesspos
                                  (Info-read-subfile guesspos))))
                    (error "No such node: \"%s\"" nodename))))
-           (goto-char (max (point-min) (- guesspos 1000))))
-         ;; Now search from our advised position (or from beg of buffer)
-         ;; to find the actual node.
-         (let ((regexp (concat "Node: *" (regexp-quote nodename) " *[,\t\n]")))
+           (goto-char (max (point-min) (- guesspos 1000)))
+           ;; Now search from our advised position (or from beg of buffer)
+           ;; to find the actual node.
            (catch 'foo
              (while (search-forward "\n\^_" nil t)
                (forward-line 1)
@@ -217,6 +231,127 @@ to read a file name from the minibuffer."
          (goto-char (nth 2 hist)))))
   (goto-char (point-min)))
 
+;; Cache the contents of the (virtual) dir file, once we have merged
+;; it for the first time, so we can save time subsequently.
+(defvar Info-dir-contents nil)
+
+;; Cache for the directory we decided to use for the default-directory
+;; of the merged dir text.
+(defvar Info-dir-contents-directory 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
+      (insert Info-dir-contents)
+    (let ((dirs Info-directory-list)
+         buffers buffer others nodes)
+
+      ;; Search the directory list for the directory file.
+      (while dirs
+       ;; Try several variants of specified name.
+       ;; Try upcasing, appending `.info', or both.
+       (let (temp
+             (buffer
+              (cond
+               ((progn (setq temp (expand-file-name "DIR" (car dirs)))
+                       (file-exists-p temp))
+                (find-file-noselect temp))
+               ((progn (setq temp (expand-file-name "dir" (car dirs)))
+                       (file-exists-p temp))
+                (find-file-noselect temp))
+               ((progn (setq temp (expand-file-name "DIR.INFO" (car dirs)))
+                       (file-exists-p temp))
+                (find-file-noselect temp))
+               ((progn (setq temp (expand-file-name "dir.info" (car dirs)))
+                       (file-exists-p temp))
+                (find-file-noselect temp)))))
+         (if buffer (setq buffers (cons buffer buffers)))
+         (setq dirs (cdr dirs))))
+
+      ;; Distinguish the dir file that comes with Emacs from all the
+      ;; others.  [This sounds like baloney - who knows what order
+      ;; Info-directory-list is in, especially after checking the
+      ;; INFOPATH variable, and why should Emacs's dir be special?  If
+      ;; you understand what this comment should have said, please
+      ;; change it.]
+      (setq buffer (car buffers)
+           others (cdr buffers))
+
+      ;; Insert the entire original dir file as a start; use its
+      ;; default directory as the default directory for the whole
+      ;; concatenation.
+      (insert-buffer buffer)
+      (setq Info-dir-contents-directory (save-excursion
+                                         (set-buffer buffer)
+                                         default-directory))
+
+      ;; Look at each of the other buffers one by one.
+      (while others
+       (let ((other (car others)))
+         ;; In each, find all the menus.
+         (save-excursion
+           (set-buffer other)
+           (goto-char (point-min))
+           ;; Find each menu, and add an elt to NODES for it.
+           (while (re-search-forward "^\\* Menu:" nil t)
+             (let (beg nodename end)
+               (forward-line 1)
+               (setq beg (point))
+               (search-backward "\n\1f")
+               (search-forward "Node: ")
+               (setq nodename (Info-following-node-name))
+               (search-forward "\n\1f" nil 'move)
+               (beginning-of-line)
+               (setq end (point))
+               (setq nodes (cons (list nodename other beg end) nodes))))))
+       (setq others (cdr others)))
+      ;; Add to the main menu a menu item for each other node.
+      (re-search-forward "^\\* Menu:")
+      (forward-line 1)
+      (let ((menu-items '("top"))
+           (nodes nodes)
+           (case-fold-search t)
+           (end (save-excursion (search-forward "\1f" nil t) (point))))
+       (while nodes
+         (let ((nodename (car (car nodes))))
+           (or (member (downcase nodename) menu-items)
+               (re-search-forward (concat "^\\* " (regexp-quote nodename) ":")
+                                  end t)
+               (progn
+                 (insert "* " nodename "\n")
+                 (setq menu-items (cons nodename menu-item)))))
+         (setq nodes (cdr nodes))))
+      ;; Now take each node of each of the other buffers
+      ;; and merge it into the main buffer.
+      (while nodes
+       (let ((nodename (car (car nodes))))
+         (goto-char (point-min))
+         ;; Find the like-named node in the main buffer.
+         (if (re-search-forward (concat "\n\1f.*\n.*Node: "
+                                        (regexp-quote nodename)
+                                        "[,\n\t]")
+                                nil t)
+             (progn
+               (search-forward "\n\1f" nil 'move)
+               (beginning-of-line))
+           ;; If none exists, add one.
+           (goto-char (point-max))
+           (insert "\1f\nFile: dir\tnode: " nodename "\n\n* Menu:\n\n"))
+         ;; Merge the text from the other buffer's menu
+         ;; into the menu in the like-named node in the main buffer.
+         (apply 'insert-buffer-substring (cdr (car nodes)))
+         (insert "\n"))
+       (setq nodes (cdr nodes)))
+      ;; Kill all the buffers we just made.
+      (while buffers
+       (kill-buffer (car buffers))
+       (setq buffers (cdr buffers))))
+    (setq Info-dir-contents (buffer-string)))
+  (setq default-directory Info-dir-contents-directory))
+
 (defun Info-read-subfile (nodepos)
   (set-buffer (marker-buffer Info-tag-table-marker))
   (goto-char (point-min))
@@ -403,6 +538,9 @@ to read a file name from the minibuffer."
         nil
        (error (concat "Node has no " (capitalize (or errorname name))))))))
 
+;; Return the node name in the buffer following point.
+;; ALLOWEDCHARS, if non-nil, goes within [...] to make a regexp
+;; saying which chas may appear in the node name.
 (defun Info-following-node-name (&optional allowedchars)
   (skip-chars-forward " \t")
   (buffer-substring
@@ -516,10 +654,11 @@ NAME may be an abbreviation of the reference name."
       (aset str i ?\ ))
     str))
 
-(defun Info-menu-item-sequence (list)
-  (while list
-    (Info-menu-item (car list))
-    (setq list (cdr list))))
+;; No one calls this and Info-menu-item doesn't exist.
+;;(defun Info-menu-item-sequence (list)
+;;  (while list
+;;    (Info-menu-item (car list))
+;;    (setq list (cdr list))))
 
 (defun Info-menu (menu-item)
   "Go to node for menu item named (or abbreviated) NAME.
@@ -597,30 +736,13 @@ Completion is allowed, and the menu item point is on is the default."
        nil))
     (Info-extract-menu-node-name)))
 
-(defun Info-first-menu-item ()
-  "Go to the node of the first menu item."
-  (interactive)
-  (Info-goto-node (Info-extract-menu-counting 1)))
-
-(defun Info-second-menu-item ()
-  "Go to the node of the second menu item."
+(defun Info-nth-menu-item ()
+  "Go to the node of the Nth menu item.
+N is the digit argument used to invoke this command."
   (interactive)
-  (Info-goto-node (Info-extract-menu-counting 2)))
-
-(defun Info-third-menu-item ()
-  "Go to the node of the third menu item."
-  (interactive)
-  (Info-goto-node (Info-extract-menu-counting 3)))
-
-(defun Info-fourth-menu-item ()
-  "Go to the node of the fourth menu item."
-  (interactive)
-  (Info-goto-node (Info-extract-menu-counting 4)))
-
-(defun Info-fifth-menu-item ()
-  "Go to the node of the fifth menu item."
-  (interactive)
-  (Info-goto-node (Info-extract-menu-counting 5)))
+  (Info-goto-node
+   (Info-extract-menu-counting
+    (- (aref (this-command-keys) (1- (length (this-command-keys)))) ?0))))
 
 (defun Info-top-node ()
   "Go to the Top node of this file."
@@ -697,6 +819,57 @@ Completion is allowed, and the menu item point is on is the default."
   (switch-to-buffer (prog1 (other-buffer (current-buffer))
                           (bury-buffer (current-buffer)))))
 
+(defun Info-next-menu-item ()
+  (interactive)
+  (save-excursion
+    (forward-line -1)
+    (search-forward "\n* menu:" nil t)
+    (or (search-forward "\n* " nil t)
+       (error "No more items in menu"))
+    (Info-goto-node (Info-extract-menu-node-name))))
+
+(defun Info-last-menu-item ()
+  (interactive)
+  (save-excursion
+    (forward-line 1)
+    (search-backward "\n* menu:" nil t)
+    (or (search-backward "\n* " nil t)
+       (error "No previous items in menu"))
+    (Info-goto-node (Info-extract-menu-node-name))))
+
+(defmacro 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))
+       (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))
+       (t                                      (error "No previous nodes"))))
+
+(defun Info-scroll-up ()
+  "Read the next screen.  If end of buffer is visible, go to next entry."
+  (interactive)
+  (if (pos-visible-in-window-p (point-max))
+      (Info-next-preorder)
+      (scroll-up))
+  )
+
+(defun Info-scroll-down ()
+  "Read the previous screen.  If start of buffer is visible, go to last entry."
+  (interactive)
+  (if (pos-visible-in-window-p (point-min))
+      (Info-last-preorder)
+      (scroll-down))
+  )
+
 (defun Info-undefined ()
   "Make command be undefined in Info."
   (interactive)
@@ -723,8 +896,8 @@ Completion is allowed, and the menu item point is on is the default."
       (while (progn (setq flag (not (pos-visible-in-window-p (point-max))))
                    (message (if flag "Type Space to see more"
                               "Type Space to return to Info"))
-                   (if (/= ?\  (setq ch (read-char)))
-                       (progn (setq unread-command-char ch) nil)
+                   (if (not (eq ?\  (setq ch (read-event))))
+                       (progn (setq unread-command-events (list ch)) nil)
                      flag))
        (scroll-up)))))
 \f
@@ -759,18 +932,18 @@ SIG optional fourth argument, controls action on no match
             (error "No %s around position %d" errorstring pos))))))
 
 (defun Info-follow-nearest-node (click)
-  "\\<Info-mode-map>Follow a node reference near point.  Like \\[Info-menu], \\Info-follow-reference], \\[Info-next], \\[Info-previous] or \\Info-up] command.
+  "\\<Info-mode-map>Follow a node reference near point.
+Like \\[Info-menu], \\[Info-follow-reference], \\[Info-next], \\[Info-prev] or \\[Info-up] command, depending on where you click.
 At end of the node's text, moves to the next node."
-  (interactive "K")
-  (let* ((relative-coordinates (coordinates-in-window-p (mouse-coords click)
-                                                       (selected-window)))
-        (rel-x (car relative-coordinates))
-        (rel-y (cdr relative-coordinates)))
-    (move-to-window-line rel-y)
-    (move-to-column rel-x))
+  (interactive "e")
+  (let* ((start (event-start click))
+        (window (car start))
+        (pos (car (cdr start))))
+    (select-window window)
+    (goto-char pos))
   (let (node)
     (cond
-     ((setq node (Info-get-token (point) "\\*note " "\\*note \\([^:]*\\):" t))
+     ((setq node (Info-get-token (point) "\\*note[ \n]" "\\*note[ \n]\\([^:]*\\):" t))
       (Info-follow-reference node))
      ((setq node (Info-get-token (point) "\\* " "\\* \\([^:]*\\)::" t))
       (Info-goto-node node))
@@ -795,16 +968,17 @@ At end of the node's text, moves to the next node."
   (setq Info-mode-map (make-keymap))
   (suppress-keymap Info-mode-map)
   (define-key Info-mode-map "." 'beginning-of-buffer)
-  (define-key Info-mode-map " " 'scroll-up)
-  (define-key Info-mode-map "1" 'Info-first-menu-item)
-  (define-key Info-mode-map "2" 'Info-second-menu-item)
-  (define-key Info-mode-map "3" 'Info-third-menu-item)
-  (define-key Info-mode-map "4" 'Info-fourth-menu-item)
-  (define-key Info-mode-map "5" 'Info-fifth-menu-item)
-  (define-key Info-mode-map "6" 'undefined)
-  (define-key Info-mode-map "7" 'undefined)
-  (define-key Info-mode-map "8" 'undefined)
-  (define-key Info-mode-map "9" 'undefined)
+  (define-key Info-mode-map " " 'Info-scroll-up)
+  (define-key Info-mode-map "\C-m" 'Info-next-preorder)
+  (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)
+  (define-key Info-mode-map "4" 'Info-nth-menu-item)
+  (define-key Info-mode-map "5" 'Info-nth-menu-item)
+  (define-key Info-mode-map "6" 'Info-nth-menu-item)
+  (define-key Info-mode-map "7" 'Info-nth-menu-item)
+  (define-key Info-mode-map "8" 'Info-nth-menu-item)
+  (define-key Info-mode-map "9" 'Info-nth-menu-item)
   (define-key Info-mode-map "0" 'undefined)
   (define-key Info-mode-map "?" 'Info-summary)
   (define-key Info-mode-map "]" 'Info-forward-node)
@@ -824,7 +998,8 @@ At end of the node's text, moves to the next node."
   (define-key Info-mode-map "q" 'Info-exit)
   (define-key Info-mode-map "s" 'Info-search)
   (define-key Info-mode-map "u" 'Info-up)
-  (define-key Info-mode-map "\177" 'scroll-down)
+  (define-key Info-mode-map "\177" 'Info-scroll-down)
+  (define-key Info-mode-map [mouse-3] 'Info-follow-nearest-node)
   )
 \f
 ;; Info mode is suitable only for specially formatted data.
@@ -850,8 +1025,11 @@ Selecting other nodes:
 \\[Info-last]  Move to the last node you were at.
 
 Moving within a node:
-\\[scroll-up]  scroll forward a full screen.     \\[scroll-down]  scroll backward.
-\\[beginning-of-buffer]        Go to beginning of node.
+\\[scroll-up]  Normally, scroll forward a full screen.  If the end of the buffer is
+already visible, try to go to the next menu entry, or up if there is none.
+\\[scroll-down]  Normally, scroll backward.  If the beginning of the buffer is
+already visible, try to go to the previous menu entry, or up if there is none.
+\\[beginning-of-buffer]        Go to beginning of node.  
 
 Advanced commands:
 \\[Info-exit]  Quit Info: reselect previously selected buffer.
@@ -861,7 +1039,10 @@ Advanced commands:
 \\[Info-goto-node]     Move to node specified by name.
        You may include a filename as well, as (FILENAME)NODENAME.
 \\[Info-search]        Search through this Info file for specified regexp,
-       and select the node in which the next occurrence is found."
+       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."
   (kill-all-local-variables)
   (setq major-mode 'Info-mode)
   (setq mode-name "Info")
@@ -890,7 +1071,7 @@ Advanced commands:
 
 (defun Info-edit-mode ()
   "Major mode for editing the contents of an Info node.
-Like text mode with the addition of Info-cease-edit
+Like text mode with the addition of `Info-cease-edit'
 which returns to Info mode for browsing.
 \\{Info-edit-map}"
   )
@@ -978,8 +1159,6 @@ The locations are of the format used in Info-history, i.e.
                        (if (> num-matches 2) "ies" "y")
                        (if (> num-matches 2) "them" "it"))))))
       (error "Couldn't find documentation for %s." command))))
-;;;###autoload
-(define-key help-map "\C-f" 'Info-goto-emacs-command-node)
 
 ;;;###autoload
 (defun Info-goto-emacs-key-command-node (key)
@@ -988,15 +1167,13 @@ Interactively, if the binding is execute-extended-command, a command is read."
   (interactive "kFind documentation for key:")
   (let ((command (key-binding key)))
     (cond ((null command)
-          (message "%s is undefined" (key-description keys)))
+          (message "%s is undefined" (key-description key)))
          ((and (interactive-p)
                (eq command 'execute-extended-command))
           (Info-goto-emacs-command-node
            (read-command "Find documentation for command: ")))
          (t
           (Info-goto-emacs-command-node command)))))
-;;;###autoload
-(define-key help-map "\C-k" 'Info-goto-emacs-key-command-node)
 
 (provide 'info)