(Info-menu): Allow extra spaces at start of menu item.
[bpt/emacs.git] / lisp / info.el
index d2c0c66..c1fb110 100644 (file)
@@ -1,6 +1,7 @@
 ;;; info.el --- info package for Emacs.
 
-;; Copyright (C) 1985, 1986, 1992, 1993, 1994 Free Software Foundation, Inc.
+;; Copyright (C) 1985, 86, 92, 93, 94, 95, 96, 97 Free Software
+;; Foundation, Inc.
 
 ;; Maintainer: FSF
 ;; Keywords: help
 ;; GNU General Public License for more details.
 
 ;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING.  If not, write to
-;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+;; along with GNU Emacs; see the file COPYING.  If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
 
 ;;; Commentary:
 
-;;; Note that nowadays we expect info files to be made using makeinfo.
+;; Note that nowadays we expect info files to be made using makeinfo.
 
 ;;; Code:
 
+(defgroup info nil
+  "Info subsystem"
+  :group 'help
+  :group 'docs)
+
+
 (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
+(defcustom Info-enable-edit nil
   "*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.")
+because that gives you a printed manual as well."
+  :type 'boolean
+  :group 'info)
 
-(defvar Info-enable-active-nodes t
+(defvar Info-enable-active-nodes nil
   "Non-nil allows Info to execute Lisp code associated with nodes.
 The Lisp code is executed when the node is selected.")
+(put 'Info-enable-active-nodes 'risky-local-variable t)
+
+(defcustom Info-fontify t
+  "*Non-nil enables highlighting and fonts in Info nodes."
+  :type 'boolean
+  :group 'info)
+
+(defface info-node
+  '((t (:bold t :italic t)))
+  "Face for Info node names."
+  :group 'info)
 
-(defvar Info-default-directory-list nil
-  "List of default directories to search for Info documentation files.
-This value is used as the default for `Info-directory-list'.  It is set
-in paths.el.")
+(defface info-menu-5
+  '((t (:underline t)))
+  "Face for the fifth and tenth `*' in an Info menu."
+  :group 'info)
 
-(defvar Info-fontify t
-  "*Non-nil enables highlighting and fonts in Info nodes.")
+(defface info-xref
+  '((t (:bold t)))
+  "Face for Info cross-references."
+  :group 'info)
 
-(defvar Info-fontify-maximum-menu-size 30000
-  "*Maximum size of menu to fontify if `Info-fontify' is non-nil.")
+(defcustom Info-fontify-maximum-menu-size 30000
+  "*Maximum size of menu to fontify if `Info-fontify' is non-nil."
+  :type 'integer
+  :group 'info)
 
 (defvar Info-directory-list
   (let ((path (getenv "INFOPATH"))
-       (sep (if (eq system-type 'ms-dos) ";" ":"))
-       (sibling (expand-file-name "../info/" (invocation-directory))))
+       ;; This is for older Emacs versions
+       ;; which might get this info.el from the Texinfo distribution.
+       (path-separator (if (boundp 'path-separator) path-separator
+                         (if (eq system-type 'ms-dos) ";" ":")))
+       (source (expand-file-name "info/" source-directory))
+       (sibling (if installation-directory
+                    (expand-file-name "info/" installation-directory)))
+       alternative)
     (if path
        (let ((list nil)
              idx)
          (while (> (length path) 0)
-           (setq idx (or (string-match sep path) (length path))
+           (setq idx (or (string-match path-separator path) (length path))
                  list (cons (substring path 0 idx) list)
                  path (substring path (min (1+ idx)
                                            (length path)))))
          (nreverse list))
-      (if (or (member sibling Info-default-directory-list)
-             (not (file-exists-p sibling))
-             ;; On MS-DOS, we use movable executables always,
+      (if (and sibling (file-exists-p sibling))
+         (setq alternative sibling)
+       (setq alternative source))
+      (if (or (member alternative Info-default-directory-list)
+             (not (file-exists-p alternative))
+             ;; On DOS/NT, we use movable executables always,
              ;; and we must always find the Info dir at run time.
-             (if (eq system-type 'ms-dos)
+             (if (or (eq system-type 'ms-dos) (eq system-type 'windows-nt))
                  nil
                ;; Use invocation-directory for Info only if we used it for
                ;; exec-directory also.
                (not (string= exec-directory
-                             (expand-file-name "../lib-src/"
-                                               (invocation-directory))))))
+                             (expand-file-name "lib-src/"
+                                               installation-directory)))))
          Info-default-directory-list
-       (reverse (cons sibling (cdr (reverse Info-default-directory-list)))))))
+       (reverse (cons alternative
+                      (cdr (reverse 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'
@@ -91,12 +126,16 @@ source tree, the `info' directory in the source tree is used as the last
 element, in place of the installation Info directory.  This is useful
 when you run a version of Emacs without installing it.")
 
-(defvar Info-additional-directory-list nil
+(defcustom Info-additional-directory-list nil
   "List of additional directories to search for Info documentation files.
-These directories are not searched for merging the `dir' file.")
+These directories are not searched for merging the `dir' file."
+  :type '(repeat directory)
+  :group 'info)
 
 (defvar Info-current-file nil
-  "Info file that Info is now looking at, or nil.")
+  "Info file that Info is now looking at, or nil.
+This is the name that was specified in Info, not the actual file name.
+It doesn't contain directory names or file name extensions added by Info.")
 
 (defvar Info-current-subfile nil
   "Info subfile that is actually in the *info* buffer now,
@@ -105,10 +144,13 @@ or nil if current info file is not split into subfiles.")
 (defvar Info-current-node nil
   "Name of node that Info is now looking at, or nil.")
 
-(defvar Info-tag-table-marker (make-marker)
+(defvar Info-tag-table-marker nil
   "Marker pointing at beginning of current Info file's tag table.
 Marker points nowhere if file has no tag table.")
 
+(defvar Info-tag-table-buffer nil
+  "Buffer used for indirect tag tables.")
+
 (defvar Info-current-file-completions nil
   "Cached completion list for current Info file.")
 
@@ -118,28 +160,82 @@ Marker points nowhere if file has no tag table.")
 (defvar Info-standalone nil
   "Non-nil if Emacs was started solely as an Info browser.")
 
-(defvar Info-suffix-list '( (".info.Z"  . "uncompress")
-                           (".info.Y"  . "unyabba")
-                           (".info.gz" . "gunzip")
-                           (".info.z"  . "gunzip")
-                           (".info"    . nil)
-                           (".Z"       . "uncompress")
-                           (".Y"       . "unyabba")
-                           (".gz"      . "gunzip")
-                           (".z"       . "gunzip")
-                           (""         . nil))
+(defvar Info-suffix-list
+  ;; The MS-DOS list should work both when long file names are
+  ;; supported (Windows 9X), and when only 8+3 file names are available.
+  (if (eq system-type 'ms-dos)
+      '( (".gz"      . "gunzip")
+        (".z"       . "gunzip")
+        (".inz"     . "gunzip")
+        (".igz"     . "gunzip")
+        (".info.Z"  . "gunzip")
+        (".info.gz" . "gunzip")
+        ("-info.Z"  . "gunzip")
+        ("-info.gz" . "gunzip")
+        ("/index.gz". "gunzip")
+        ("/index.z" . "gunzip")
+        (".inf"     . nil)
+        (".info"    . nil)
+        ("-info"    . nil)
+        ("/index"   . nil)
+        (""         . nil))
+    '( (".info.Z".    "uncompress")
+       (".info.Y".    "unyabba")
+       (".info.gz".   "gunzip")
+       (".info.z".    "gunzip")
+       (".info".      nil)
+       ("-info.Z".   "uncompress")
+       ("-info.Y".   "unyabba")
+       ("-info.gz".  "gunzip")
+       ("-info.z".   "gunzip")
+       ("-info".     nil)
+       ("/index.Z".   "uncompress")
+       ("/index.Y".   "unyabba")
+       ("/index.gz".  "gunzip")
+       ("/index.z".   "gunzip")
+       ("/index".     nil)
+       (".Z".         "uncompress")
+       (".Y".         "unyabba")
+       (".gz".        "gunzip")
+       (".z".         "gunzip")
+       ("".           nil)))
   "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.
 Because the SUFFIXes are tried in order, the empty string should
 be last in the list.")
 
+;; Concatenate SUFFIX onto FILENAME.  SUFFIX should start with a dot.
+;; First, on ms-dos, delete some of the extension in FILENAME
+;; to make room.
+(defun info-insert-file-contents-1 (filename suffix)
+  (if (not (eq system-type 'ms-dos))
+      (concat filename suffix)
+    (let* ((sans-exts (file-name-sans-extension filename))
+          ;; How long is the extension in FILENAME (not counting the dot).
+          (ext-len (max 0 (- (length filename) (length sans-exts) 1)))
+          ext-left)
+      ;; SUFFIX starts with a dot.  If FILENAME already has one,
+      ;; get rid of the one in SUFFIX (unless suffix is empty).
+      (or (and (<= ext-len 0)
+              (not (eq (aref filename (1- (length filename))) ?.)))
+         (= (length suffix) 0)
+         (setq suffix (substring suffix 1)))
+      ;; How many chars of that extension should we keep?
+      (setq ext-left (min ext-len (max 0 (- 3 (length suffix)))))
+      ;; Get rid of the rest of the extension, and add SUFFIX.
+      (concat (substring filename 0 (- (length filename)
+                                      (- ext-len ext-left)))
+             suffix))))
+
 (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."
   (let ((tail Info-suffix-list)
        fullname decoder)
     (if (file-exists-p filename)
+       ;; FILENAME exists--see if that name contains a suffix.
+       ;; If so, set DECODE accordingly.
        (progn
          (while (and tail
                      (not (string-match
@@ -148,23 +244,33 @@ Do the right thing if the file has been compressed or zipped."
            (setq tail (cdr tail)))
          (setq fullname filename
                decoder (cdr (car tail))))
+      ;; Try adding suffixes to FILENAME and see if we can find something.
       (while (and tail
-                 (not (file-exists-p (concat filename (car (car tail))))))
+                 (not (file-exists-p (info-insert-file-contents-1
+                                      filename (car (car tail))))))
        (setq tail (cdr tail)))
-      (setq fullname (concat filename (car (car tail)))
+      ;; If we found a file with a suffix, set DECODER according to the suffix
+      ;; and set FULLNAME to the file's actual name.
+      (setq fullname (info-insert-file-contents-1 filename (car (car tail)))
            decoder (cdr (car tail)))
-      ;; check for conflict with jka-compr
-      (if (and (featurep 'jka-compr)
-              (jka-compr-installed-p)
-              (jka-compr-get-compression-info (concat filename
-                                                      (car (car tail)))))
-         (setq decoder nil))
       (or tail
-         (error "Can't find %s or any compressed version of it!" filename)))
-    (insert-file-contents fullname visit)
+         (error "Can't find %s or any compressed version of it" filename)))
+    ;; check for conflict with jka-compr
+    (if (and (featurep 'jka-compr)
+            (jka-compr-installed-p)
+            (jka-compr-get-compression-info fullname))
+       (setq decoder nil))
     (if decoder
-       (let ((buffer-read-only nil))
-         (shell-command-on-region (point-min) (point-max) decoder t)))))
+       (progn
+         (insert-file-contents-literally fullname visit)
+         (let ((buffer-read-only nil)
+               (coding-system-for-write 'no-conversion)
+               (default-directory (or (file-name-directory fullname)
+                                      default-directory)))
+           (call-process-region (point-min) (point-max) decoder t t)))
+      (insert-file-contents fullname visit))))
+
+;;;###autoload (add-hook 'same-window-buffer-names "*info*")
 
 ;;;###autoload
 (defun info (&optional file)
@@ -173,14 +279,17 @@ Optional argument FILE specifies the file to examine;
 the default is the top-level directory of Info.
 
 In interactive use, a prefix argument directs this command
-to read a file name from the minibuffer."
+to read a file name from the minibuffer.
+
+The search path for Info files is in the variable `Info-directory-list'.
+The top-level Info directory is made by combining all the files named `dir' 
+in all the directories in that path."
   (interactive (if current-prefix-arg
                   (list (read-file-name "Info file name: " nil nil t))))
+  (pop-to-buffer "*info*")
   (if file
       (Info-goto-node (concat "(" file ")"))
-    (if (get-buffer "*info*")
-       (switch-to-buffer "*info*")
-      (Info-directory))))
+    (Info-directory)))
 
 ;;;###autoload
 (defun info-standalone ()
@@ -209,16 +318,20 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
   (if filename
       (let (temp temp-downcase found)
        (setq filename (substitute-in-file-name filename))
-       (if (string= (downcase (file-name-nondirectory filename)) "dir")
+       (if (string= (downcase filename) "dir")
            (setq found t)
          (let ((dirs (if (string-match "^\\./" filename)
                          ;; If specified name starts with `./'
                          ;; then just try current directory.
                          '("./")
-                       (if Info-additional-directory-list
-                           (append Info-directory-list
-                                   Info-additional-directory-list)
-                         Info-directory-list))))
+                       (if (file-name-absolute-p filename)
+                           ;; No point in searching for an
+                           ;; absolute file name
+                           '(nil)
+                         (if Info-additional-directory-list
+                             (append Info-directory-list
+                                     Info-additional-directory-list)
+                           Info-directory-list)))))
            ;; Search the directory list for file FILENAME.
            (while (and dirs (not found))
              (setq temp (expand-file-name filename (car dirs)))
@@ -228,10 +341,12 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
              (let ((suffix-list Info-suffix-list))
                (while (and suffix-list (not found))
                  (cond ((file-exists-p
-                         (concat temp (car (car suffix-list))))
+                         (info-insert-file-contents-1
+                          temp (car (car suffix-list))))
                         (setq found temp))
                        ((file-exists-p
-                         (concat temp-downcase (car (car suffix-list))))
+                         (info-insert-file-contents-1
+                          temp-downcase (car (car suffix-list))))
                         (setq found temp-downcase)))
                  (setq suffix-list (cdr suffix-list))))
              (setq dirs (cdr dirs)))))
@@ -244,7 +359,7 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
            (cons (list Info-current-file Info-current-node (point))
                  Info-history)))
   ;; Go into info buffer.
-  (switch-to-buffer "*info*")
+  (or (eq major-mode 'Info-mode) (pop-to-buffer "*info*"))
   (buffer-disable-undo (current-buffer))
   (or (eq major-mode 'Info-mode)
       (Info-mode))
@@ -259,7 +374,6 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
              (setq Info-current-file nil
                    Info-current-subfile nil
                    Info-current-file-completions nil
-                   Info-index-alternatives nil
                    buffer-file-name nil)
              (erase-buffer)
              (if (eq filename t)
@@ -268,11 +382,12 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                (setq default-directory (file-name-directory filename)))
              (set-buffer-modified-p nil)
              ;; See whether file has a tag table.  Record the location if yes.
-             (set-marker Info-tag-table-marker nil)
              (goto-char (point-max))
              (forward-line -8)
-             (or (equal nodename "*")
-                 (not (search-forward "\^_\nEnd tag table\n" nil t))
+             ;; Use string-equal, not equal, to ignore text props.
+             (if (not (or (string-equal nodename "*")
+                          (not
+                           (search-forward "\^_\nEnd tag table\n" nil t))))
                  (let (pos)
                    ;; We have a tag table.  Find its beginning.
                    ;; Is this an indirect file?
@@ -283,20 +398,25 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                          (looking-at "(Indirect)\n"))
                        ;; It is indirect.  Copy it to another buffer
                        ;; and record that the tag table is in that buffer.
-                       (save-excursion
-                         (let ((buf (current-buffer)))
-                           (set-buffer (get-buffer-create " *info tag table*"))
+                       (let ((buf (current-buffer))
+                             (tagbuf
+                              (or Info-tag-table-buffer
+                                  (generate-new-buffer " *info tag table*"))))
+                         (setq Info-tag-table-buffer tagbuf)
+                         (save-excursion
+                           (set-buffer tagbuf)
                             (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))))
+                           (insert-buffer-substring buf))
+                         (set-marker Info-tag-table-marker
+                                     (match-end 0) tagbuf))
+                     (set-marker Info-tag-table-marker pos)))
+               (set-marker Info-tag-table-marker nil))
              (setq Info-current-file
-                   (if (eq filename t) "dir"
-                     (file-name-sans-versions buffer-file-name)))))
-       (if (equal nodename "*")
+                   (if (eq filename t) "dir" filename))))
+       ;; Use string-equal, not equal, to ignore text props.
+       (if (string-equal nodename "*")
            (progn (setq Info-current-node nodename)
                   (Info-set-mode-line))
          ;; Search file for a suitable node.
@@ -307,18 +427,28 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
            ;; 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 (re-search-forward regexp nil t)
-                     (progn
-                       (setq guesspos (read (current-buffer)))
-                       ;; If this is an indirect file,
-                       ;; determine which file really holds this node
-                       ;; and read it in.
-                       (if (not (eq (current-buffer) (get-buffer "*info*")))
-                           (setq guesspos
-                                 (Info-read-subfile guesspos))))
-                   (error "No such node: \"%s\"" nodename))))
+                 (let ((m Info-tag-table-marker)
+                       found found-mode)
+                   (save-excursion
+                     (set-buffer (marker-buffer m))
+                     (goto-char m)
+                     (beginning-of-line) ;so re-search will work.
+                     (setq found (re-search-forward regexp nil t))
+                     (if found
+                         (setq guesspos (read (current-buffer))))
+                     (setq found-mode major-mode))
+                   (if found
+                       (progn
+                         ;; If this is an indirect file, determine
+                         ;; which file really holds this node and
+                         ;; read it in.
+                         (if (not (eq found-mode 'Info-mode))
+                             ;; Note that the current buffer must be
+                             ;; the *info* buffer on entry to
+                             ;; Info-read-subfile.  Thus the hackery
+                             ;; above.
+                             (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.
@@ -400,29 +530,28 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                    (save-excursion
                      (or buffers
                          (message "Composing main Info directory..."))
-                     (set-buffer (generate-new-buffer "info dir"))
+                     (set-buffer (generate-new-buffer " info dir"))
                      (insert-file-contents file)
                      (setq buffers (cons (current-buffer) buffers)
                            Info-dir-file-attributes
                            (cons (cons file attrs)
                                  Info-dir-file-attributes))))))
+         (or (cdr dirs) (setq Info-dir-contents-directory
+                              (file-name-as-directory (car dirs))))
          (setq dirs (cdr dirs))))
       
       (or buffers
-         (error "Can't find the info directory node"))
+         (error "Can't find the Info directory node"))
       ;; 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 the entire original dir file as a start; note that we've
+      ;; already saved its default directory to use 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
@@ -455,7 +584,7 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
          (let ((nodename (car (car nodes))))
            (save-excursion
              (or (member (downcase nodename) menu-items)
-                 (re-search-forward (concat "^\\* "
+                 (re-search-forward (concat "^\\* +"
                                             (regexp-quote nodename)
                                             "::")
                                     end t)
@@ -475,14 +604,14 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                                 nil t)
              (progn
                (search-forward "\n\^_" nil 'move)
-               (beginning-of-line))
+               (beginning-of-line)
+               (insert "\n"))
            ;; If none exists, add one.
            (goto-char (point-max))
            (insert "\^_\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"))
+         (apply 'insert-buffer-substring (cdr (car nodes))))
        (setq nodes (cdr nodes)))
       ;; Kill all the buffers we just made.
       (while buffers
@@ -492,30 +621,39 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
     (setq Info-dir-contents (buffer-string)))
   (setq default-directory Info-dir-contents-directory))
 
+;; Note that on entry to this function the current-buffer must be the
+;; *info* buffer; not the info tags buffer.
 (defun Info-read-subfile (nodepos)
-  (set-buffer (marker-buffer Info-tag-table-marker))
-  (goto-char (point-min))
-  (search-forward "\n\^_")
+  ;; NODEPOS is either a position (in the Info file as a whole,
+  ;; not relative to a subfile) or the name of a subfile.
   (let (lastfilepos
        lastfilename)
-    (forward-line 2)
-    (catch 'foo
-      (while (not (looking-at "\^_"))
-       (if (not (eolp))
-           (let ((beg (point))
-                 thisfilepos thisfilename)
-             (search-forward ": ")
-             (setq thisfilename  (buffer-substring beg (- (point) 2)))
-             (setq thisfilepos (read (current-buffer)))
-             ;; read in version 19 stops at the end of number.
-             ;; Advance to the next line.
-             (forward-line 1)
-             (if (> thisfilepos nodepos)
-                 (throw 'foo t))
-             (setq lastfilename thisfilename)
-             (setq lastfilepos thisfilepos))
-         (forward-line 1))))
-    (set-buffer (get-buffer "*info*"))
+    (if (numberp nodepos)
+       (save-excursion
+         (set-buffer (marker-buffer Info-tag-table-marker))
+         (goto-char (point-min))
+         (search-forward "\n\^_")
+         (forward-line 2)
+         (catch 'foo
+           (while (not (looking-at "\^_"))
+             (if (not (eolp))
+                 (let ((beg (point))
+                       thisfilepos thisfilename)
+                   (search-forward ": ")
+                   (setq thisfilename  (buffer-substring beg (- (point) 2)))
+                   (setq thisfilepos (read (current-buffer)))
+                   ;; read in version 19 stops at the end of number.
+                   ;; Advance to the next line.
+                   (forward-line 1)
+                   (if (> thisfilepos nodepos)
+                       (throw 'foo t))
+                   (setq lastfilename thisfilename)
+                   (setq lastfilepos thisfilepos))
+               (forward-line 1)))))
+      (setq lastfilename nodepos)
+      (setq lastfilepos 0))
+    ;; Assume previous buffer is in Info-mode.
+    ;; (set-buffer (get-buffer "*info*"))
     (or (equal Info-current-subfile lastfilename)
        (let ((buffer-read-only nil))
          (setq buffer-file-name nil)
@@ -526,7 +664,8 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
          (setq Info-current-subfile lastfilename)))
     (goto-char (point-min))
     (search-forward "\n\^_")
-    (+ (- nodepos lastfilepos) (point))))
+    (if (numberp nodepos)
+       (+ (- nodepos lastfilepos) (point)))))
 
 ;; Select the info node that point is in.
 (defun Info-select-node ()
@@ -537,10 +676,10 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
    ;; Get nodename spelled as it is in the node.
    (re-search-forward "Node:[ \t]*")
    (setq Info-current-node
-        (buffer-substring (point)
-                          (progn
-                           (skip-chars-forward "^,\t\n")
-                           (point))))
+        (buffer-substring-no-properties (point)
+                                        (progn
+                                         (skip-chars-forward "^,\t\n")
+                                         (point))))
    (Info-set-mode-line)
    ;; Find the end of it, and narrow.
    (beginning-of-line)
@@ -562,7 +701,7 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
 (defun Info-set-mode-line ()
   (setq mode-line-buffer-identification
        (concat
-        "Info:  ("
+        "  Info:  ("
         (if Info-current-file
             (file-name-nondirectory Info-current-file)
           "")
@@ -586,12 +725,32 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
       (if trim (setq filename (substring filename 0 trim))))
     (let ((trim (string-match "\\s *\\'" nodename)))
       (if trim (setq nodename (substring nodename 0 trim))))
+    (if transient-mark-mode (deactivate-mark))
     (Info-find-node (if (equal filename "") nil filename)
                    (if (equal nodename "") "Top" nodename))))
 
+;; This function is used as the "completion table" while reading a node name.
+;; It does completion using the alist in completion-table
+;; unless STRING starts with an open-paren.
+(defun Info-read-node-name-1 (string predicate code)
+  (let ((no-completion (and (> (length string) 0) (eq (aref string 0) ?\())))
+    (cond ((eq code nil)
+          (if no-completion
+              string
+            (try-completion string completion-table predicate)))
+         ((eq code t)
+          (if no-completion
+              nil
+            (all-completions string completion-table predicate)))
+         ((eq code 'lambda)
+          (if no-completion
+              t
+            (assoc string completion-table))))))
+
 (defun Info-read-node-name (prompt &optional default)
   (let* ((completion-ignore-case t)
-        (nodename (completing-read prompt (Info-build-node-completions))))
+        (completion-table (Info-build-node-completions))
+        (nodename (completing-read prompt 'Info-read-node-name-1 nil t)))
     (if (equal nodename "")
        (or default
            (Info-read-node-name prompt))
@@ -603,10 +762,10 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
        (save-excursion
          (save-restriction
            (if (marker-buffer Info-tag-table-marker)
-               (progn
-                 (set-buffer (marker-buffer Info-tag-table-marker))
+               (let ((marker Info-tag-table-marker))
+                 (set-buffer (marker-buffer marker))
                  (widen)
-                 (goto-char Info-tag-table-marker)
+                 (goto-char marker)
                  (while (re-search-forward "\nNode: \\(.*\\)\177" nil t)
                    (setq compl
                          (cons (list (buffer-substring (match-beginning 1)
@@ -630,7 +789,8 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
   "If this node has been visited, restore the point value when we left."
   (while hl
     (if (and (equal (nth 0 (car hl)) Info-current-file)
-            (equal (nth 1 (car hl)) Info-current-node))
+            ;; Use string-equal, not equal, to ignore text props.
+            (string-equal (nth 1 (car hl)) Info-current-node))
        (progn
          (goto-char (nth 2 (car hl)))
          (setq hl nil))                ;terminate the while at next iter
@@ -642,6 +802,7 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
 (defun Info-search (regexp)
   "Search for REGEXP, starting from point, and select node it's found in."
   (interactive "sSearch (regexp): ")
+  (if transient-mark-mode (deactivate-mark))
   (if (equal regexp "")
       (setq regexp Info-last-search)
     (setq Info-last-search regexp))
@@ -649,6 +810,7 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
        (onode Info-current-node)
        (ofile Info-current-file)
        (opoint (point))
+       (ostart (window-start))
        (osubfile Info-current-subfile))
     (save-excursion
       (save-restriction
@@ -661,27 +823,28 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
     (if (not found) ;can only happen in subfile case -- else would have erred
        (unwind-protect
            (let ((list ()))
-             (set-buffer (marker-buffer Info-tag-table-marker))
-             (goto-char (point-min))
-             (search-forward "\n\^_\nIndirect:")
-             (save-restriction
-               (narrow-to-region (point)
-                                 (progn (search-forward "\n\^_")
-                                        (1- (point))))
+             (save-excursion
+               (set-buffer (marker-buffer Info-tag-table-marker))
                (goto-char (point-min))
-               (search-forward (concat "\n" osubfile ": "))
-               (beginning-of-line)
-               (while (not (eobp))
-                 (re-search-forward "\\(^.*\\): [0-9]+$")
-                 (goto-char (+ (match-end 1) 2))
-                 (setq list (cons (cons (read (current-buffer))
-                                        (buffer-substring (match-beginning 1)
-                                                          (match-end 1)))
-                                  list))
-                 (goto-char (1+ (match-end 0))))
-               (setq list (nreverse list)
-                     current (car (car list))
-                     list (cdr list)))
+               (search-forward "\n\^_\nIndirect:")
+               (save-restriction
+                 (narrow-to-region (point)
+                                   (progn (search-forward "\n\^_")
+                                          (1- (point))))
+                 (goto-char (point-min))
+                 (search-forward (concat "\n" osubfile ": "))
+                 (beginning-of-line)
+                 (while (not (eobp))
+                   (re-search-forward "\\(^.*\\): [0-9]+$")
+                   (goto-char (+ (match-end 1) 2))
+                   (setq list (cons (cons (read (current-buffer))
+                                          (buffer-substring
+                                           (match-beginning 1) (match-end 1)))
+                                    list))
+                   (goto-char (1+ (match-end 0))))
+                 (setq list (nreverse list)
+                       current (car (car list))
+                       list (cdr list))))
              (while list
                (message "Searching subfile %s..." (cdr (car list)))
                (Info-read-subfile (car (car list)))
@@ -693,13 +856,15 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                  (message "")
                (signal 'search-failed (list regexp))))
          (if (not found)
-             (progn (Info-read-subfile opoint)
+             (progn (Info-read-subfile osubfile)
                     (goto-char opoint)
-                    (Info-select-node)))))
+                    (Info-select-node)
+                    (set-window-start (selected-window) ostart)))))
     (widen)
     (goto-char found)
     (Info-select-node)
-    (or (and (equal onode Info-current-node)
+    ;; Use string-equal, not equal, to ignore text props.
+    (or (and (string-equal onode Info-current-node)
             (equal ofile Info-current-file))
        (setq Info-history (cons (list ofile onode opoint)
                                 Info-history)))))
@@ -717,14 +882,14 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
         (Info-following-node-name))
      (if (eq errorname t)
         nil
-       (error (concat "Node has no " (capitalize (or errorname name))))))))
+       (error "Node has no %s" (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.
+;; saying which chars may appear in the node name.
 (defun Info-following-node-name (&optional allowedchars)
   (skip-chars-forward " \t")
-  (buffer-substring
+  (buffer-substring-no-properties
    (point)
    (progn
      (while (looking-at (concat "[" (or allowedchars "^,\t\n") "]"))
@@ -805,9 +970,18 @@ NAME may be an abbreviation of the reference name."
         ;; Record as a completion and perhaps as default.
         (if (eq default t) (setq default str))
         (if (eq alt-default t) (setq alt-default str))
-        (setq completions
-              (cons (cons str nil)
-                    completions))))
+        ;; Don't add this string if it's a duplicate.
+        ;; We use a loop instead of "(assoc str completions)" because
+        ;; we want to do a case-insensitive compare.
+        (let ((tail completions)
+              (tem (downcase str)))
+          (while (and tail
+                      (not (string-equal tem (downcase (car (car tail))))))
+            (setq tail (cdr tail)))
+          (or tail
+              (setq completions
+                    (cons (cons str nil)
+                          completions))))))
      ;; If no good default was found, try an alternate.
      (or default
         (setq default alt-default))
@@ -823,7 +997,7 @@ NAME may be an abbreviation of the reference name."
           (list (if (equal input "")
                     default input)))
        (error "No cross-references in this node"))))
-  (let (target beg i (str (concat "\\*note " footnotename)))
+  (let (target beg i (str (concat "\\*note " (regexp-quote footnotename))))
     (while (setq i (string-match " " str i))
       (setq str (concat (substring str 0 i) "[ \t\n]+" (substring str (1+ i))))
       (setq i (+ i 6)))
@@ -848,11 +1022,14 @@ NAME may be an abbreviation of the reference name."
     (forward-char 1)
     (setq str
          (if (looking-at ":")
-             (buffer-substring beg (1- (point)))
+             (buffer-substring-no-properties beg (1- (point)))
            (skip-chars-forward " \t\n")
            (Info-following-node-name (if multi-line "^.,\t" "^.,\t\n"))))
     (while (setq i (string-match "\n" str i))
       (aset str i ?\ ))
+    ;; Collapse multiple spaces.
+    (while (string-match "  +" str)
+      (setq str (replace-match " " t t str)))
     str))
 
 ;; No one calls this.
@@ -865,12 +1042,13 @@ NAME may be an abbreviation of the reference name."
   (let ((case-fold-search t))
     (cond ((eq action nil)
           (let (completions
-                (pattern (concat "\n\\* \\("
+                (pattern (concat "\n\\* +\\("
                                  (regexp-quote string)
                                  "[^:\t\n]*\\):")))
             (save-excursion
               (set-buffer Info-complete-menu-buffer)
               (goto-char (point-min))
+              (search-forward "\n* Menu:")
               (while (re-search-forward pattern nil t)
                 (setq completions (cons (cons (format "%s"
                                                       (buffer-substring
@@ -881,12 +1059,13 @@ NAME may be an abbreviation of the reference name."
             (try-completion string completions predicate)))
          ((eq action t)
           (let (completions
-                (pattern (concat "\n\\* \\("
+                (pattern (concat "\n\\* +\\("
                                  (regexp-quote string)
                                  "[^:\t\n]*\\):")))
             (save-excursion
               (set-buffer Info-complete-menu-buffer)
               (goto-char (point-min))
+              (search-forward "\n* Menu:")
               (while (re-search-forward pattern nil t)
                 (setq completions (cons (cons (format "%s"
                                                       (buffer-substring
@@ -899,7 +1078,8 @@ NAME may be an abbreviation of the reference name."
           (save-excursion
             (set-buffer Info-complete-menu-buffer)
             (goto-char (point-min))
-            (re-search-forward (concat "\n\\* "
+            (search-forward "\n* Menu:")
+            (re-search-forward (concat "\n\\* +"
                                        (regexp-quote string)
                                        ":")
                                nil t))))))
@@ -913,6 +1093,7 @@ Completion is allowed, and the menu item point is on is the default."
         ;; If point is within a menu item, use that item as the default
         (default nil)
         (p (point))
+        beg
         (last nil))
      (save-excursion
        (goto-char (point-min))
@@ -923,7 +1104,7 @@ Completion is allowed, and the menu item point is on is the default."
            (save-excursion
              (goto-char p)
              (end-of-line)
-             (re-search-backward "\n\\* \\([^:\t\n]*\\):" beg t)
+             (re-search-backward "\n\\* +\\([^:\t\n]*\\):" beg t)
              (setq default (format "%s" (buffer-substring
                                          (match-beginning 1)
                                          (match-end 1)))))))
@@ -955,8 +1136,8 @@ Completion is allowed, and the menu item point is on is the default."
     (goto-char (point-min))
     (or (search-forward "\n* menu:" nil t)
        (error "No menu in this node"))
-    (or (re-search-forward (concat "\n\\* " menu-item ":") nil t)
-       (re-search-forward (concat "\n\\* " menu-item) nil t)
+    (or (re-search-forward (concat "\n\\* +" menu-item ":") nil t)
+       (re-search-forward (concat "\n\\* +" menu-item) nil t)
        (error "No such item in menu"))
     (beginning-of-line)
     (forward-char 2)
@@ -1021,7 +1202,9 @@ N is the digit argument used to invoke this command."
          (Info-next)
          t)
         ((and (save-excursion (search-backward "up:" nil t))
-             (not (equal (downcase (Info-extract-pointer "up")) "top")))
+             ;; Use string-equal, not equal, to ignore text props.
+             (not (string-equal (downcase (Info-extract-pointer "up"))
+                                "top")))
          (let ((old-node Info-current-node))
            (Info-up)
            (let (Info-history success)
@@ -1039,7 +1222,10 @@ N is the digit argument used to invoke this command."
     (cond ((and upnode (string-match "(" upnode))
           (error "First node in file"))
          ((and upnode (or (null prevnode)
-                          (equal (downcase prevnode) (downcase upnode))))
+                          ;; Use string-equal, not equal,
+                          ;; to ignore text properties.
+                          (string-equal (downcase prevnode)
+                                        (downcase upnode))))
           (Info-up))
          (prevnode
           ;; If we move back at the same level,
@@ -1057,8 +1243,7 @@ N is the digit argument used to invoke this command."
   (interactive)
   (if Info-standalone
       (save-buffers-kill-emacs)
-    (switch-to-buffer (prog1 (other-buffer (current-buffer))
-                       (bury-buffer (current-buffer))))))
+    (bury-buffer)))
 
 (defun Info-next-menu-item ()
   (interactive)
@@ -1086,21 +1271,17 @@ N is the digit argument used to invoke this command."
   (list 'condition-case nil (cons 'progn (append body '(t))) '(error nil)))
 
 (defun Info-next-preorder ()
-  "Go to the next subnode, popping up a level if there is none."
-  (interactive)
-  (cond ((Info-no-error (Info-next-menu-item)))
-       ((Info-no-error (Info-up))
-        (forward-line 1))
-       (t
-        (error "No more nodes"))))
-
-(defun Info-next-preorder-1 ()
   "Go to the next subnode or the next node, or go up a level."
   (interactive)
   (cond ((Info-no-error (Info-next-menu-item)))
        ((Info-no-error (Info-next)))
        ((Info-no-error (Info-up))
-        (forward-line 1))
+        ;; Since we have already gone thru all the items in this menu,
+        ;; go up to the end of this node.
+        (goto-char (point-max))
+        ;; Since logically we are done with the node with that menu,
+        ;; move on from it.
+        (Info-next-preorder))
        (t
         (error "No more nodes"))))
 
@@ -1111,12 +1292,39 @@ N is the digit argument used to invoke this command."
          (Info-last-menu-item)
          ;; If we go down a menu item, go to the end of the node
          ;; so we can scroll back through it.
-         (goto-char (point-max))))
-       ((Info-no-error (Info-up))              (forward-line -1))
-       (t                                      (error "No previous nodes"))))
+         (goto-char (point-max)))
+        ;; Keep going down, as long as there are nested menu nodes.
+        (while (Info-no-error
+                (Info-last-menu-item)
+                ;; If we go down a menu item, go to the end of the node
+                ;; so we can scroll back through it.
+                (goto-char (point-max))))
+        (recenter -1))
+       ((Info-no-error (Info-prev))
+        (goto-char (point-max))
+        (while (Info-no-error
+                (Info-last-menu-item)
+                ;; If we go down a menu item, go to the end of the node
+                ;; so we can scroll back through it.
+                (goto-char (point-max))))
+        (recenter -1))
+       ((Info-no-error (Info-up))
+        (goto-char (point-min))
+        (or (search-forward "\n* Menu:" nil t)
+            (goto-char (point-max))))
+       (t (error "No previous nodes"))))
 
 (defun Info-scroll-up ()
-  "Read the next screen.  If end of buffer is visible, go to next entry."
+  "Scroll one screenful forward in Info, considering all nodes as one sequence.
+Once you scroll far enough in a node that its menu appears on the screen
+but after point, the next scroll moves into its first subnode.
+
+When you scroll past the end of a node, that goes to the next node; if
+this node has no successor, it moves to the parent node's successor,
+and so on.  If point is inside the menu of a node, it moves to
+subnode indicated by the following menu item.  (That case won't
+normally result from this command, but can happen in other ways.)"
+
   (interactive)
   (if (or (< (window-start) (point-min))
          (> (window-start) (point-max)))
@@ -1132,19 +1340,27 @@ N is the digit argument used to invoke this command."
       (scroll-up))))
 
 (defun Info-scroll-down ()
-  "Read the previous screen.  If start of buffer is visible, go to last entry."
+  "Scroll one screenful back in Info, considering all nodes as one sequence.
+Within the menu of a node, this goes to its last subnode.
+When you scroll past the beginning of a node, that goes to the
+previous node or back up to the parent node."
   (interactive)
   (if (or (< (window-start) (point-min))
          (> (window-start) (point-max)))
       (set-window-start (selected-window) (point)))
-  (let ((virtual-end (save-excursion
-                      (goto-char (point-min))
-                      (search-forward "\n* Menu:" nil t))))
+  (let* ((current-point (point))
+        (virtual-end (save-excursion
+                       (beginning-of-line)
+                       (setq current-point (point))
+                       (goto-char (point-min))
+                       (search-forward "\n* Menu:"
+                                       current-point
+                                       t))))
     (if (or virtual-end (pos-visible-in-window-p (point-min)))
        (Info-last-preorder)
       (scroll-down))))
 
-(defun Info-next-reference ()
+(defun Info-next-reference (&optional recur)
   "Move cursor to the next cross-reference or menu item in the node."
   (interactive)
   (let ((pat "\\*note[ \n\t]*\\([^:]*\\):\\|^\\* .*:")
@@ -1159,9 +1375,11 @@ N is the digit argument used to invoke this command."
                (error "No cross references in this node")))))
     (goto-char (match-beginning 0))
     (if (looking-at "\\* Menu:")
-       (Info-next-reference))))
+       (if recur
+           (error "No cross references in this node")
+         (Info-next-reference t)))))
 
-(defun Info-prev-reference ()
+(defun Info-prev-reference (&optional recur)
   "Move cursor to the previous cross-reference or menu item in the node."
   (interactive)
   (let ((pat "\\*note[ \n\t]*\\([^:]*\\):\\|^\\* .*:")
@@ -1175,7 +1393,9 @@ N is the digit argument used to invoke this command."
                (error "No cross references in this node")))))
     (goto-char (match-beginning 0))
     (if (looking-at "\\* Menu:")
-       (Info-prev-reference))))
+       (if recur
+           (error "No cross references in this node")
+         (Info-prev-reference t)))))
 
 (defun Info-index (topic)
   "Look up a string in the index for this file.
@@ -1189,7 +1409,7 @@ 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]*\\)"
+       (pattern (format "\n\\* +\\([^\n:]*%s[^\n:]*\\):[ \t]*\\([^.\n]*\\)\\.[ \t]*\\([0-9]*\\)"
                         (regexp-quote topic)))
        node)
     (Info-goto-node "Top")
@@ -1198,12 +1418,17 @@ Give a blank topic name to go to the Index node itself."
     (or (re-search-forward "\n\\* \\(.*\\<Index\\>\\)" nil t)
        (error "No index"))
     (goto-char (match-beginning 1))
-    (let ((Info-keeping-history nil))
+    ;; Here, and subsequently in this function,
+    ;; we bind Info-history to nil for internal node-switches
+    ;; so that we don't put junk in the history.
+    ;; In the first Info-goto-node call, above, we do update the history
+    ;; because that is what the user's previous node choice into it.
+    (let ((Info-history nil))
       (Info-goto-node (Info-extract-menu-node-name)))
     (or (equal topic "")
        (let ((matches nil)
              (exact nil)
-             (Info-keeping-history nil)
+             (Info-history nil)
              found)
          (while
              (progn
@@ -1225,8 +1450,8 @@ Give a blank topic name to go to the Index node itself."
            (Info-goto-node node))
          (or matches
              (progn
-               (Info-last)
-               (error "No \"%s\" in index" topic)))
+               (Info-goto-node orignode)
+               (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)
@@ -1238,7 +1463,7 @@ Give a blank topic name to go to the Index node itself."
   "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"))
+      (error "No previous `i' command"))
   (while (< num 0)
     (setq num (+ num (length Info-index-alternatives))))
   (while (> num 0)
@@ -1251,24 +1476,28 @@ Give a blank topic name to go to the Index node itself."
       (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"
+      (Info-find-index-name name)))
+  (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-find-index-name (name)
+  "Move point to the place within the current node where NAME is defined."
+  (if (or (re-search-forward (format
+                             "[a-zA-Z]+: %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))))
+
 (defun Info-undefined ()
   "Make command be undefined in Info."
   (interactive)
@@ -1288,8 +1517,10 @@ Give a blank topic name to go to the Index node itself."
   (interactive)
   (save-window-excursion
     (switch-to-buffer "*Help*")
+    (setq buffer-read-only nil)
     (erase-buffer)
     (insert (documentation 'Info-mode))
+    (help-mode)
     (goto-char (point-min))
     (let (ch flag)
       (while (progn (setq flag (not (pos-visible-in-window-p (point-max))))
@@ -1307,7 +1538,7 @@ POS must be somewhere inside the token
 START is a regular expression which will match the
     beginning of the tokens delimited string
 ALL is a regular expression with a single
-    parenthized subpattern which is the token to be
+    parenthesized subpattern which is the token to be
     returned. E.g. '{\(.*\)}' would return any string
     enclosed in braces around POS.
 SIG optional fourth argument, controls action on no match
@@ -1316,7 +1547,16 @@ SIG optional fourth argument, controls action on no match
     a string: signal an error, using that string."
   (save-excursion
     (goto-char pos)
-    (re-search-backward start (max (point-min) (- pos 200)) 'yes)
+    ;; First look for a match for START that goes across POS.
+    (while (and (not (bobp)) (> (point) (- pos (length start)))
+               (not (looking-at start)))
+      (forward-char -1))
+    ;; If we did not find one, search back for START
+    ;; (this finds only matches that end at or before POS).
+    (or (looking-at start)
+       (progn
+         (goto-char pos)
+         (re-search-backward start (max (point-min) (- pos 200)) 'yes)))
     (let (found)
       (while (and (re-search-forward all (min (point-max) (+ pos 200)) 'yes)
                  (not (setq found (and (<= (match-beginning 0) pos)
@@ -1344,7 +1584,7 @@ At end of the node's text, moves to the next node, or up if none."
     (goto-char pos))
   (and (not (Info-try-follow-nearest-node))
        (save-excursion (forward-line 1) (eobp))
-       (Info-next-preorder-1)))
+       (Info-next-preorder)))
 
 (defun Info-follow-nearest-node ()
   "\\<Info-mode-map>Follow a node reference near point.
@@ -1352,7 +1592,7 @@ Like \\[Info-menu], \\[Info-follow-reference], \\[Info-next], \\[Info-prev] or \
 If no reference to follow, moves to the next node, or up if none."
   (interactive)
   (or (Info-try-follow-nearest-node)
-      (Info-next-preorder-1)))
+      (Info-next-preorder)))
 
 ;; Common subroutine.
 (defun Info-try-follow-nearest-node ()
@@ -1362,9 +1602,9 @@ If no reference to follow, moves to the next node, or up if none."
      ((setq node (Info-get-token (point) "\\*note[ \n]"
                                 "\\*note[ \n]\\([^:]*\\):"))
       (Info-follow-reference node))
-     ((setq node (Info-get-token (point) "\\* " "\\* \\([^:]*\\)::"))
+     ((setq node (Info-get-token (point) "\\* +" "\\* +\\([^:]*\\)::"))
       (Info-goto-node node))
-     ((setq node (Info-get-token (point) "\\* " "\\* \\([^:]*\\):"))
+     ((setq node (Info-get-token (point) "\\* +" "\\* +\\([^:]*\\):"))
       (Info-menu node))
      ((setq node (Info-get-token (point) "Up: " "Up: \\([^,\n\t]*\\)"))
       (Info-goto-node node))
@@ -1415,12 +1655,102 @@ If no reference to follow, moves to the next node, or up if none."
   (define-key Info-mode-map "p" 'Info-prev)
   (define-key Info-mode-map "q" 'Info-exit)
   (define-key Info-mode-map "s" 'Info-search)
+  ;; For consistency with Rmail.
+  (define-key Info-mode-map "\M-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 "," 'Info-index-next)
   (define-key Info-mode-map "\177" 'Info-scroll-down)
   (define-key Info-mode-map [mouse-2] 'Info-mouse-follow-nearest-node)
   )
+
+(defun Info-check-pointer (item)
+  ;; Non-nil if ITEM is present in this node.
+  (condition-case nil
+      (Info-extract-pointer item)
+    (error nil)))
+
+(easy-menu-define Info-mode-menu Info-mode-map
+  "Menu for info files."
+  '("Info"
+    ["Up" Info-up (Info-check-pointer "up")]
+    ["Next" Info-next (Info-check-pointer "next")]
+    ["Previous" Info-prev (Info-check-pointer "prev[ious]*")]
+    ("Menu item" ["You should never see this" report-emacs-bug t])
+    ("Reference" ["You should never see this" report-emacs-bug t])
+    ["Search..." Info-search t]
+    ["Goto node..." Info-goto-node t]
+    ["Last" Info-last Info-history]
+    ["Exit" Info-exit t]))
+
+(defvar Info-menu-last-node nil)
+;; Last node the menu was created for.
+;; Value is a list, (FILE-NAME NODE-NAME).
+
+(defun Info-menu-update ()
+  ;; Update the Info menu for the current node.
+  (condition-case nil
+      (if (or (not (eq major-mode 'Info-mode))
+             (equal (list Info-current-file Info-current-node)
+                    Info-menu-last-node))
+         ()
+       ;; Update menu menu.
+       (let* ((Info-complete-menu-buffer (current-buffer))
+              (items (nreverse (condition-case nil
+                                   (Info-complete-menu-item
+                                    "" (lambda (e) t) t)
+                                 (error nil))))
+              entries current 
+              (number 0))
+         (while (and items (< number 9))
+           (setq current (car items)
+                 items (cdr items)
+                 number (1+ number))
+           (setq entries (cons `[,current 
+                                 (Info-menu ,current)
+                                 :keys ,(format "%d" number)]
+                               entries)))
+         (if items
+             (setq entries (cons ["Other..." Info-menu t] entries)))
+         (or entries
+             (setq entries (list ["No menu" nil nil])))
+         (easy-menu-change '("Info") "Menu item" (nreverse entries)))
+       ;; Update reference menu.  Code stolen from `Info-follow-reference'.
+       (let ((items nil)
+             str i entries current 
+             (number 0))
+         (save-excursion
+           (goto-char (point-min))
+           (while (re-search-forward "\\*note[ \n\t]*\\([^:]*\\):" nil t)
+             (setq str (buffer-substring
+                        (match-beginning 1)
+                        (1- (point))))
+             (setq i 0)
+             (while (setq i (string-match "[ \n\t]+" str i))
+               (setq str (concat (substring str 0 i) " "
+                                 (substring str (match-end 0))))
+               (setq i (1+ i)))
+             (setq items
+                   (cons str items))))
+         (while (and items (< number 9))
+           (setq current (car items)
+                 items (cdr items)
+                 number (1+ number))
+           (setq entries (cons `[,current 
+                                 (Info-follow-reference ,current)
+                                 t]
+                               entries)))
+         (if items
+             (setq entries (cons ["Other..." Info-follow-reference t]
+                                 entries)))
+         (or entries
+             (setq entries (list ["No references" nil nil])))
+         (easy-menu-change '("Info") "Reference" (nreverse entries)))
+       ;; Update last seen node.
+       (setq Info-menu-last-node (list Info-current-file Info-current-node)))
+    ;; Try to avoid entering infinite beep mode in case of errors.
+    (error (ding))))
+
 \f
 ;; Info mode is suitable only for specially formatted data.
 (put 'info-mode 'mode-class 'special)
@@ -1451,9 +1781,12 @@ Selecting other nodes:
 \\[Info-index-next]    (comma) Move to the next match from a previous `i' command.
 
 Moving within a node:
-\\[Info-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.
-\\[Info-scroll-down]  Normally, scroll backward.  If the beginning of the buffer is
+\\[Info-scroll-up]     Normally, scroll forward a full screen.
+Once you scroll far enough in a node that its menu appears on the screen
+but after point, the next scroll moves into its first subnode.
+When after all menu items (or if their is no menu), move up to
+the parent node.
+\\[Info-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.  
 
@@ -1467,15 +1800,15 @@ Advanced commands:
 \\[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.
 \\[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")
+  (setq tab-width 8)
   (use-local-map Info-mode-map)
+  (make-local-hook 'activate-menubar-hook)
+  (add-hook 'activate-menubar-hook 'Info-menu-update nil t)
   (set-syntax-table text-mode-syntax-table)
   (setq local-abbrev-table text-mode-abbrev-table)
   (setq case-fold-search t)
@@ -1484,22 +1817,14 @@ Advanced commands:
   (make-local-variable 'Info-current-subfile)
   (make-local-variable 'Info-current-node)
   (make-local-variable 'Info-tag-table-marker)
+  (setq Info-tag-table-marker (make-marker))
+  (make-local-variable 'Info-tag-table-buffer)
+  (setq Info-tag-table-buffer nil)
   (make-local-variable 'Info-history)
   (make-local-variable 'Info-index-alternatives)
-  (if (eq (framep (selected-frame)) 'x)
-      (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))
+  ;; This is for the sake of the invisible text we use handling titles.
+  (make-local-variable 'line-move-ignore-invisible)
+  (setq line-move-ignore-invisible t)
   (Info-set-mode-line)
   (run-hooks 'Info-mode-hook))
 
@@ -1518,7 +1843,14 @@ Advanced commands:
 Like text mode with the addition of `Info-cease-edit'
 which returns to Info mode for browsing.
 \\{Info-edit-map}"
-  )
+  (use-local-map Info-edit-map)
+  (setq major-mode 'Info-edit-mode)
+  (setq mode-name "Info Edit")
+  (kill-local-variable 'mode-line-buffer-identification)
+  (setq buffer-read-only nil)
+  (force-mode-line-update)
+  (buffer-enable-undo (current-buffer))
+  (run-hooks 'Info-edit-mode-hook))
 
 (defun Info-edit ()
   "Edit the contents of this Info node.
@@ -1526,15 +1858,9 @@ Allowed only if variable `Info-enable-edit' is non-nil."
   (interactive)
   (or Info-enable-edit
       (error "Editing info nodes is not enabled"))
-  (use-local-map Info-edit-map)
-  (setq major-mode 'Info-edit-mode)
-  (setq mode-name "Info Edit")
-  (kill-local-variable 'mode-line-buffer-identification)
-  (setq buffer-read-only nil)
-  ;; Make mode line update.
-  (set-buffer-modified-p (buffer-modified-p))
-  (message (substitute-command-keys
-            "Editing: Type \\<Info-edit-map>\\[Info-cease-edit] to return to info")))
+  (Info-edit-mode)
+  (message "%s" (substitute-command-keys
+           "Editing: Type \\<Info-edit-map>\\[Info-cease-edit] to return to info")))
 
 (defun Info-cease-edit ()
   "Finish editing Info node; switch back to Info proper."
@@ -1548,22 +1874,53 @@ Allowed only if variable `Info-enable-edit' is non-nil."
   (setq mode-name "Info")
   (Info-set-mode-line)
   (setq buffer-read-only t)
-  ;; Make mode line update.
-  (set-buffer-modified-p (buffer-modified-p))
+  (force-mode-line-update)
   (and (marker-position Info-tag-table-marker)
        (buffer-modified-p)
        (message "Tags may have changed.  Use Info-tagify if necessary")))
 \f
+(defvar Info-file-list-for-emacs
+  '("ediff" "forms" "gnus" "info" ("mh" . "mh-e") "sc")
+  "List of Info files that describe Emacs commands.
+An element can be a file name, or a list of the form (PREFIX . FILE)
+where PREFIX is a name prefix and FILE is the file to look in.
+If the element is just a file name, the file name also serves as the prefix.")
+
 (defun Info-find-emacs-command-nodes (command)
-  "Return a list of locations documenting COMMAND in the Emacs Info manual.
+  "Return a list of locations documenting COMMAND.
+The `info-file' property of COMMAND says which Info manual to search.
+If COMMAND has no property, the variable `Info-file-list-for-emacs'
+defines heuristics for which Info manual to try.
 The locations are of the format used in Info-history, i.e.
 \(FILENAME NODENAME BUFFERPOS\)."
-  (require 'info)
   (let ((where '())
-       (cmd-desc (concat "^\\* " (regexp-quote (symbol-name command))
-                         ":\\s *\\(.*\\)\\.$")))
+       (cmd-desc (concat "^\\* +" (regexp-quote (symbol-name command))
+                         ":\\s *\\(.*\\)\\.$"))
+       (info-file "emacs"))            ;default
+    ;; Determine which info file this command is documented in.
+    (if (get command 'info-file)
+       (setq info-file (get command 'info-file))
+      ;; If it doesn't say explicitly, test its name against
+      ;; various prefixes that we know.
+      (let ((file-list Info-file-list-for-emacs))
+       (while file-list
+         (let* ((elt (car file-list))
+                (name (if (consp elt)
+                          (car elt)
+                        elt))
+                (file (if (consp elt) (cdr elt) elt))
+                (regexp (concat "\\`" (regexp-quote name)
+                                "\\(\\'\\|-\\)")))
+           (if (string-match regexp (symbol-name command))
+               (setq info-file file file-list nil))
+           (setq file-list (cdr file-list))))))
     (save-excursion
-      (Info-find-node "emacs" "Command Index")
+      (condition-case nil
+         (Info-find-node info-file "Command Index")
+       ;; Some manuals may not have a separate Command Index node,
+       ;; so try just Index instead.
+       (error
+        (Info-find-node info-file "Index")))
       ;; Take the index node off the Info history.
       (setq Info-history (cdr Info-history))
       (goto-char (point-max))
@@ -1579,7 +1936,9 @@ The locations are of the format used in Info-history, i.e.
 ;;;###autoload
 (defun Info-goto-emacs-command-node (command)
   "Go to the Info node in the Emacs manual for command COMMAND.
-The command is found by looking up in Emacs manual's Command Index."
+The command is found by looking up in Emacs manual's Command Index
+or in another manual found via COMMAND's `info-file' property or
+the variable `Info-file-list-for-emacs'."
   (interactive "CFind documentation for command: ")
   (or (commandp command)
       (signal 'wrong-type-argument (list 'commandp command)))
@@ -1589,6 +1948,8 @@ The command is found by looking up in Emacs manual's Command Index."
          ;; Get Info running, and pop to it in another window.
          (save-window-excursion
            (info))
+         ;; FIXME It would be cool if this could use a buffer other
+         ;; than *info*.
          (pop-to-buffer "*info*")
          (Info-find-node (car (car where))
                          (car (cdr (car where))))
@@ -1598,18 +1959,20 @@ The command is found by looking up in Emacs manual's Command Index."
                ;; Info-history.  Put the other nodes that were found on
                ;; the history.
                (setq Info-history (nconc (cdr where) Info-history))
-               (message (substitute-command-keys
-                         "Found %d other entr%s.  Use \\[Info-last] to see %s.")
+               (message "Found %d other entr%s.  Use %s to see %s."
                         (1- num-matches)
                         (if (> num-matches 2) "ies" "y")
+                        (substitute-command-keys "\\[Info-last]")
                         (if (> num-matches 2) "them" "it")))))
-      (error "Couldn't find documentation for %s." command))))
+      (error "Couldn't find documentation for %s" command))))
 
 ;;;###autoload
 (defun Info-goto-emacs-key-command-node (key)
   "Go to the Info node in the Emacs manual the command bound to KEY, a string.
 Interactively, if the binding is execute-extended-command, a command is read.
-The command is found by looking up in Emacs manual's Command Index."
+The command is found by looking up in Emacs manual's Command Index
+or in another manual found via COMMAND's `info-file' property or
+the variable `Info-file-list-for-emacs'."
   (interactive "kFind documentation for key:")
   (let ((command (key-binding key)))
     (cond ((null command)
@@ -1621,6 +1984,15 @@ The command is found by looking up in Emacs manual's Command Index."
          (t
           (Info-goto-emacs-command-node command)))))
 \f
+(defcustom Info-title-face-alist
+  '((?* bold underline)
+    (?= bold-italic underline)
+    (?- italic underline))
+  "*Alist of face or list of faces to use for pseudo-underlined titles.
+The alist key is the character the title is underlined with (?*, ?= or ?-)."
+  :type '(repeat (list character face face))
+  :group 'info)
+
 (defun Info-fontify-node ()
   (save-excursion
     (let ((buffer-read-only nil))
@@ -1636,6 +2008,18 @@ The command is found by looking up in Emacs manual's Command Index."
              (put-text-property (match-beginning 1) (match-end 1)
                                 'mouse-face 'highlight))))
       (goto-char (point-min))
+      (while (re-search-forward "\n\\([^ \t\n].+\\)\n\\(\\*+\\|=+\\|-+\\)$"
+                                nil t)
+       (put-text-property (match-beginning 1) (match-end 1)
+                          'face
+                          (cdr (assq (preceding-char) Info-title-face-alist)))
+       ;; This is a serious problem for trying to handle multiple
+       ;; frame types at once.  We want this text to be invisible
+       ;; on frames that can display the font above.
+       (if (memq (framep (selected-frame)) '(x pc w32))
+           (put-text-property (match-end 1) (match-end 2)
+                              'invisible t)))
+      (goto-char (point-min))
       (while (re-search-forward "\\*Note[ \n\t]+\\([^:]*\\):" nil t)
        (if (= (char-after (1- (match-beginning 0))) ?\") ; hack
            nil
@@ -1649,7 +2033,7 @@ The command is found by looking up in Emacs manual's Command Index."
               ;; Don't take time to annotate huge menus
               (< (- (point-max) (point)) Info-fontify-maximum-menu-size))
          (let ((n 0))
-           (while (re-search-forward "^\\* \\([^:\t\n]*\\):" nil t)
+           (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)
@@ -1660,6 +2044,93 @@ The command is found by looking up in Emacs manual's Command Index."
              (put-text-property (match-beginning 1) (match-end 1)
                                 'mouse-face 'highlight))))
       (set-buffer-modified-p nil))))
+\f
+
+;; When an Info buffer is killed, make sure the associated tags buffer
+;; is killed too.
+(defun Info-kill-buffer ()
+  (and (eq major-mode 'Info-mode)
+       Info-tag-table-buffer
+       (kill-buffer Info-tag-table-buffer)))
+
+(add-hook 'kill-buffer-hook 'Info-kill-buffer)
+
+;;; Speedbar support:
+;; These functions permit speedbar to display the "tags" in the
+;; current info node.
+
+(eval-when-compile (require 'speedbspec))
+
+(defvar Info-last-speedbar-node nil
+  "Last node viewed with speedbar in the form '(NODE FILE).")
+
+(defvar Info-speedbar-menu-items
+  '(["Browse Item On Line" speedbar-edit-line t])
+  "Additional menu-items to add to speedbar frame.")
+
+(defun Info-speedbar-buttons (buffer)
+  "Create a speedbar display to help navigation in an Info file.
+BUFFER is the buffer speedbar is requesting buttons for."
+  (goto-char (point-min))
+  (if (and (looking-at "<Directory>")
+          (save-excursion
+            (set-buffer buffer)
+            (and (equal (car Info-last-speedbar-node) Info-current-node)
+                 (equal (cdr Info-last-speedbar-node) Info-current-file))))
+      nil
+    (erase-buffer)
+    (speedbar-insert-button "<Directory>" 'info-xref 'highlight
+                           'Info-speedbar-button
+                           'Info-directory)
+    (speedbar-insert-button "<Top>" 'info-xref 'highlight
+                           'Info-speedbar-button
+                           'Info-top-node)
+    (speedbar-insert-button "<Last>" 'info-xref 'highlight
+                           'Info-speedbar-button
+                           'Info-last)
+    (speedbar-insert-button "<Up>" 'info-xref 'highlight
+                           'Info-speedbar-button
+                           'Info-up)
+    (speedbar-insert-button "<Next>" 'info-xref 'highlight
+                           'Info-speedbar-button
+                           'Info-next)
+    (speedbar-insert-button "<Prev>" 'info-xref 'highlight
+                           'Info-speedbar-button
+                           'Info-prev)
+    (let ((completions nil))
+      (save-excursion
+       (set-buffer buffer)
+       (setq Info-last-speedbar-node
+             (cons Info-current-node Info-current-file))
+       (goto-char (point-min))
+       ;; Always skip the first one...
+       (re-search-forward "\n\\* +\\([^:\t\n]*\\):" nil t)
+       (while (re-search-forward "\n\\* +\\([^:\t\n]*\\):" nil t)
+         (setq completions (cons (buffer-substring (match-beginning 1)
+                                                   (match-end 1))
+                                 completions))))
+      (setq completions (nreverse completions))
+      (while completions
+       (speedbar-make-tag-line nil nil nil nil
+                               (car completions) 'Info-speedbar-menu
+                               nil 'info-node 0)
+       (setq completions (cdr completions))))))
+
+(defun Info-speedbar-button (text token indent)
+  "Called when user clicks <Directory> from speedbar.
+TEXT, TOKEN, and INDENT are unused."
+  (speedbar-with-attached-buffer
+   (funcall token)
+   (setq Info-last-speedbar-node nil)
+   (speedbar-update-contents)))
+
+(defun Info-speedbar-menu (text token indent)
+  "Goto the menu node specified in TEXT.
+TOKEN and INDENT are not used."
+  (speedbar-with-attached-buffer
+   (Info-menu text)
+   (setq Info-last-speedbar-node nil)
+   (speedbar-update-contents)))
 
 (provide 'info)