]> code.delx.au - gnu-emacs/blobdiff - lisp/info.el
(Info-fontify-node): New function.
[gnu-emacs] / lisp / info.el
index 772fa26ebf1c3651595ca9f2eaf07d5f09844edd..83247a93325f49d811c31c5601cf7cabb7e919f8 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).")
 
 (defvar Info-enable-edit nil
-  "Non-nil means the \\<info-mode-map>\\[Info-edit] command in Info can edit the current node.")
+  "*Non-nil means the \\<Info-mode-map>\\[Info-edit] command in Info can edit the current node.
+This is convenient if you want to write info files by hand.
+However, we recommend that you not do this.
+It is better to write a Texinfo file and generate the Info file from that,
+because that gives you a printed manual as well.")
 
 (defvar Info-enable-active-nodes t
   "Non-nil allows Info to execute Lisp code associated with nodes.
@@ -34,11 +47,25 @@ 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 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.")
@@ -54,6 +81,49 @@ 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-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.")
+
+(defun info-insert-file-contents (filename &optional visit)
+  "Insert the contents of an info file in the current buffer.
+Do the right thing if the file has been compressed or zipped."
+  (if (null (catch 'ok
+             (mapcar
+              (function
+               (lambda (x)
+                 (let ((compressed (concat filename (car x))))
+                   (if (file-exists-p compressed)
+                       (progn
+                         (insert-file-contents compressed visit)
+                         (if (cdr x)
+                             (let ((buffer-read-only nil))
+                               (shell-command-on-region
+                                (point-min) (point-max) (cdr x) t)))
+                         (throw 'ok t))))))
+              Info-suffix-list)
+             nil))
+      (error "Can't find %s or any compressed version of it!" filename)))
+
 ;;;###autoload
 (defun info (&optional file)
   "Enter Info, the documentation browser.
@@ -64,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-mode-map>\\[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.
@@ -92,27 +167,33 @@ 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.
+             (catch 'foundit
+               (mapcar
+                (function
+                 (lambda (x)
+                   (if (file-exists-p (concat temp (car x)))
+                       (progn
+                         (setq found temp)
+                         (throw 'foundit nil)))
+                   (if (file-exists-p (concat temp-downcase (car x)))
+                       (progn
+                         (setq found temp-downcase)
+                         (throw 'foundit nil)))))
+                Info-suffix-list))
+             (setq dirs (cdr dirs)))))
        (if found
            (setq filename found)
          (error "Info file %s does not exist" filename))))
@@ -123,7 +204,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)
@@ -136,11 +217,15 @@ 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)
-             (insert-file-contents filename t)
+             (if (eq filename t)
+                 (Info-insert-dir)
+               (info-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))
@@ -160,28 +245,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,
@@ -191,10 +278,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)
@@ -213,6 +299,147 @@ 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)
+
+;; 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 (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)
+
+      ;; Search the directory list for the directory file.
+      (while dirs
+       (or (member (file-truename (expand-file-name (car dirs))) dirs-done)
+           (member (directory-file-name (file-truename (expand-file-name (car dirs))))
+                   dirs-done)
+           ;; 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)))))
+             (setq dirs-done
+                   (cons (file-truename (expand-file-name (car dirs)))
+                         (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
+      ;; others.  Yes, that is really what this is supposed to do.
+      ;; If it doesn't work, fix 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-items)))))
+         (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))
@@ -242,7 +469,7 @@ to read a file name from the minibuffer."
          (setq buffer-file-name nil)
          (widen)
          (erase-buffer)
-         (insert-file-contents lastfilename)
+         (info-insert-file-contents lastfilename)
          (set-buffer-modified-p nil)
          (setq Info-current-subfile lastfilename)))
     (goto-char (point-min))
@@ -276,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
@@ -293,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)
@@ -307,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))))
 \f
 (defun Info-restore-point (hl)
   "If this node has been visited, restore the point value when we left."
@@ -317,7 +582,7 @@ to read a file name from the minibuffer."
        (Info-restore-point (cdr hl)))))
 \f
 (defvar Info-last-search nil
-  "Default regexp for \\<info-mode-map>\\[Info-search] command to search for.")
+  "Default regexp for \\<Info-mode-map>\\[Info-search] command to search for.")
 
 (defun Info-search (regexp)
   "Search for REGEXP, starting from point, and select node it's found in."
@@ -399,6 +664,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
@@ -474,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))
@@ -512,10 +782,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.
@@ -593,30 +864,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."
-  (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."
+(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 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."
@@ -650,7 +904,7 @@ Completion is allowed, and the menu item point is on is the default."
   (cond ((and (not not-down)
               (save-excursion (search-forward "\n* menu:" nil t))
              (not (string-match "\\<index\\>" Info-current-node)))
-         (Info-first-menu-item)
+        (Info-goto-node (Info-extract-menu-counting 1))
          t)
         ((save-excursion (search-backward "next:" nil t))
          (Info-next)
@@ -690,8 +944,189 @@ Completion is allowed, and the menu item point is on is the default."
 (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)
+  (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 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 ((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 ((Info-no-error (Info-last-menu-item)) )
+       ((Info-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-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
+name contains the word \"Index\", plus any immediately following
+nodes whose names also contain the word \"Index\".
+If there are no exact matches to the specified topic, this chooses
+the first match which is a case-insensitive substring of a topic.
+Use the `,' command to see the other matches.
+Give a blank topic name to go to the Index node itself."
+  (interactive "sIndex topic: ")
+  (let ((orignode Info-current-node)
+       (rnode nil)
+       (pattern (format "\n\\* \\([^\n:]*%s[^\n:]*\\):[ \t]*\\([^.\n]*\\)\\.[ t]*\\([0-9]*\\)"
+                        (regexp-quote topic)))
+       node)
+    (Info-goto-node "Top")
+    (or (search-forward "\n* menu:" nil t)
+       (error "No index"))
+    (or (re-search-forward "\n\\* \\(.*\\<Index\\>\\)" nil t)
+       (error "No index"))
+    (goto-char (match-beginning 1))
+    (let ((Info-keeping-history nil))
+      (Info-goto-node (Info-extract-menu-node-name)))
+    (or (equal topic "")
+       (let ((matches nil)
+             (exact nil)
+             (Info-keeping-history nil)
+             found)
+         (while
+             (progn
+               (goto-char (point-min))
+               (while (re-search-forward pattern nil t)
+                 (setq matches
+                       (cons (list (buffer-substring (match-beginning 1)
+                                                     (match-end 1))
+                                   (buffer-substring (match-beginning 2)
+                                                     (match-end 2))
+                                   Info-current-node
+                                   (string-to-int (concat "0"
+                                                          (buffer-substring
+                                                           (match-beginning 3)
+                                                           (match-end 3)))))
+                             matches)))
+               (and (setq node (Info-extract-pointer "next" t))
+                    (string-match "\\<Index\\>" node)))
+           (Info-goto-node node))
+         (or matches
+             (progn
+               (Info-last)
+               (error "No \"%s\" in index" topic)))
+         ;; Here it is a feature that assoc is case-sensitive.
+         (while (setq found (assoc topic matches))
+           (setq exact (cons found exact)
+                 matches (delq found matches)))
+         (setq Info-index-alternatives (nconc exact (nreverse matches)))
+         (Info-index-next 0)))))
+
+(defun Info-index-next (num)
+  "Go to the next matching index item from the last `i' command."
+  (interactive "p")
+  (or Info-index-alternatives
+      (error "No previous `i' command in this file"))
+  (while (< num 0)
+    (setq num (+ num (length Info-index-alternatives))))
+  (while (> num 0)
+    (setq Info-index-alternatives
+         (nconc (cdr Info-index-alternatives)
+                (list (car Info-index-alternatives)))
+         num (1- num)))
+  (Info-goto-node (nth 1 (car Info-index-alternatives)))
+  (if (> (nth 3 (car Info-index-alternatives)) 0)
+      (forward-line (nth 3 (car Info-index-alternatives)))
+    (forward-line 3)  ; don't search in headers
+    (let ((name (car (car Info-index-alternatives))))
+      (if (or (re-search-forward (format
+                                 "\\(Function\\|Command\\): %s\\( \\|$\\)"
+                                 (regexp-quote name)) nil t)
+             (search-forward (format "`%s'" name) nil t)
+             (and (string-match "\\`.*\\( (.*)\\)\\'" name)
+                  (search-forward
+                   (format "`%s'" (substring name 0 (match-beginning 1)))
+                   nil t))
+             (search-forward name nil t))
+         (beginning-of-line)
+       (goto-char (point-min)))))
+  (message "Found \"%s\" in %s.  %s"
+          (car (car Info-index-alternatives))
+          (nth 2 (car Info-index-alternatives))
+          (if (cdr Info-index-alternatives)
+              "(Press `,' for more)"
+            "(Only match)")))
 
 (defun Info-undefined ()
   "Make command be undefined in Info."
@@ -719,10 +1154,11 @@ 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)))))
+       (scroll-up)))
+    (bury-buffer "*Help*")))
 \f
 (defun Info-get-token (pos start all &optional errorstring)
   "Return the token around POS,
@@ -740,45 +1176,46 @@ 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)
-  "\\<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]\\([^:]*\\):"))
       (Info-follow-reference node))
-     ((setq node (Info-get-token (point) "\\* " "\\* \\([^:]*\\)::" t))
+     ((setq node (Info-get-token (point) "\\* " "\\* \\([^:]*\\)::"))
       (Info-goto-node node))
-     ((setq node (Info-get-token (point) "\\* " "\\* \\([^:]*\\):" t))
+     ((setq node (Info-get-token (point) "\\* " "\\* \\([^:]*\\):"))
       (Info-menu node))
-     ((setq node (Info-get-token (point) "Up: " "Up: \\([^,\n\t]*\\)" t))
+     ((setq node (Info-get-token (point) "Up: " "Up: \\([^,\n\t]*\\)"))
       (Info-goto-node node))
-     ((setq node (Info-get-token (point) "Next: " "Next: \\([^,\n\t]*\\)" t))
+     ((setq node (Info-get-token (point) "Next: " "Next: \\([^,\n\t]*\\)"))
       (Info-goto-node node))
-     ((setq node (Info-get-token (point) "File: " "File: \\([^,\n\t]*\\)" t))
+     ((setq node (Info-get-token (point) "File: " "File: \\([^,\n\t]*\\)"))
       (Info-goto-node "Top"))
-     ((setq node (Info-get-token (point) "Prev: " "Prev: \\([^,\n\t]*\\)" t))
+     ((setq node (Info-get-token (point) "Prev: " "Prev: \\([^,\n\t]*\\)"))
       (Info-goto-node node))
      ((save-excursion (forward-line 1) (eobp))
       (Info-next)))
@@ -791,16 +1228,19 @@ 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 "\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)
+  (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)
@@ -813,14 +1253,18 @@ At end of the node's text, moves to the next node."
   (define-key Info-mode-map "f" 'Info-follow-reference)
   (define-key Info-mode-map "g" 'Info-goto-node)
   (define-key Info-mode-map "h" 'Info-help)
+  (define-key Info-mode-map "i" 'Info-index)
   (define-key Info-mode-map "l" 'Info-last)
   (define-key Info-mode-map "m" 'Info-menu)
   (define-key Info-mode-map "n" 'Info-next)
   (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-node)
   (define-key Info-mode-map "u" 'Info-up)
-  (define-key Info-mode-map "\177" 'scroll-down)
+  (define-key Info-mode-map "," 'Info-index-next)
+  (define-key Info-mode-map "\177" 'Info-scroll-down)
+  (define-key Info-mode-map [mouse-2] 'Info-follow-nearest-node)
   )
 \f
 ;; Info mode is suitable only for specially formatted data.
@@ -837,17 +1281,22 @@ topics.  Info has commands to follow the references and show you other nodes.
 
 Selecting other nodes:
 \\[Info-next]  Move to the \"next\" node of this node.
-\\[Info-previous]      Move to the \"previous\" node of this node.
+\\[Info-prev]  Move to the \"previous\" node of this node.
 \\[Info-up]    Move \"up\" from this node.
 \\[Info-menu]  Pick menu item specified by name (or abbreviation).
        Picking a menu item causes another node to be selected.
 \\[Info-directory]     Go to the Info directory node.
 \\[Info-follow-reference]      Follow a cross reference.  Reads name of reference.
 \\[Info-last]  Move to the last node you were at.
+\\[Info-index] Look up a topic in this file's Index and move to that node.
+\\[Info-index-next]    (comma) Move to the next match from a previous `i' command.
 
 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.
@@ -856,8 +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."
+       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.
+\\[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")
@@ -871,6 +1326,21 @@ Advanced commands:
   (make-local-variable 'Info-current-node)
   (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))
 
@@ -886,7 +1356,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}"
   )
@@ -905,7 +1375,7 @@ Allowed only if variable `Info-enable-edit' is non-nil."
   ;; Make mode line update.
   (set-buffer-modified-p (buffer-modified-p))
   (message (substitute-command-keys
-            "Editing: Type \\<info-mode-map>\\[Info-cease-edit] to return to info")))
+            "Editing: Type \\<Info-mode-map>\\[Info-cease-edit] to return to info")))
 
 (defun Info-cease-edit ()
   "Finish editing Info node; switch back to Info proper."
@@ -974,8 +1444,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)
@@ -984,15 +1452,47 @@ 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)
+\f
+(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 "\\<Index\\>" 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)