Fixed up initial line
[bpt/emacs.git] / lisp / info.el
index 881aecc..ec783d1 100644 (file)
@@ -1,6 +1,6 @@
 ;;; info.el --- info package for Emacs.
 
-;; Copyright (C) 1985, 1986, 1992, 1993 Free Software Foundation, Inc.
+;; Copyright (C) 1985, 1986, 1992, 1993, 1994 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:
 
@@ -38,41 +39,73 @@ 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
+(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.")
-
-(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.")
+(put 'Info-enable-active-nodes 'risky-local-variable t)
 
 (defvar Info-fontify t
   "*Non-nil enables highlighting and fonts in Info nodes.")
 
+(defvar Info-fontify-maximum-menu-size 30000
+  "*Maximum size of menu to fontify if `Info-fontify' is non-nil.")
+
 (defvar Info-directory-list
   (let ((path (getenv "INFOPATH"))
-       (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 ":" 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)))
+      (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 (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/"
+                                               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'
-if there is no INFOPATH variable in the environment.")
+if there is no INFOPATH variable in the environment.
+The last element of `Info-default-directory-list' is the directory
+where Emacs installs the Info files that come with it.
+
+If you run the Emacs executable from the `src' directory in the Emacs
+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
+  "List of additional directories to search for Info documentation files.
+These directories are not searched for merging the `dir' file.")
 
 (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,
@@ -94,28 +127,59 @@ 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
+  (if (eq system-type 'ms-dos)
+      '( (".gz"      . "gunzip")
+        (".z"       . "gunzip")
+        (".inf"     . nil)
+        (""         . nil))
+    '( (".info.Z"  . "uncompress")
+       (".info.Y"  . "unyabba")
+       (".info.gz" . "gunzip")
+       (".info.z"  . "gunzip")
+       (".info"    . 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
@@ -124,17 +188,30 @@ 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)))
       (or tail
-         (error "Can't find %s or any compressed version of it!" filename)))
+         (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))
     (insert-file-contents fullname visit)
     (if decoder
-       (let ((buffer-read-only nil))
-         (shell-command-on-region (point-min) (point-max) decoder t)))))
+       (let ((buffer-read-only nil)
+             (default-directory (or (file-name-directory fullname)
+                                    default-directory)))
+         (call-process-region (point-min) (point-max) decoder t t)))))
+
+;;;###autoload (add-hook 'same-window-buffer-names "*info*")
 
 ;;;###autoload
 (defun info (&optional file)
@@ -143,13 +220,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))))
   (if file
       (Info-goto-node (concat "(" file ")"))
     (if (get-buffer "*info*")
-       (switch-to-buffer "*info*")
+       (pop-to-buffer "*info*")
       (Info-directory))))
 
 ;;;###autoload
@@ -179,32 +260,37 @@ 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.
                          '("./")
-                       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)))
              (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))
+             (let ((suffix-list Info-suffix-list))
+               (while (and suffix-list (not found))
+                 (cond ((file-exists-p
+                         (info-insert-file-contents-1
+                          temp (car (car suffix-list))))
+                        (setq found temp))
+                       ((file-exists-p
+                         (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)))))
        (if found
            (setq filename found)
@@ -242,7 +328,8 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
              (set-marker Info-tag-table-marker nil)
              (goto-char (point-max))
              (forward-line -8)
-             (or (equal nodename "*")
+             ;; Use string-equal, not equal, to ignore text props.
+             (or (string-equal nodename "*")
                  (not (search-forward "\^_\nEnd tag table\n" nil t))
                  (let (pos)
                    ;; We have a tag table.  Find its beginning.
@@ -265,9 +352,9 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                                        (match-end 0))))
                      (set-marker Info-tag-table-marker pos))))
              (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.
@@ -289,7 +376,7 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                        (if (not (eq (current-buffer) (get-buffer "*info*")))
                            (setq guesspos
                                  (Info-read-subfile guesspos))))
-                   (error "No such node: \"%s\"" nodename))))
+                   (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.
@@ -333,13 +420,18 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
           ;; since we used it.
           (eval (cons 'and
                       (mapcar '(lambda (elt)
-                                 (equal (cdr elt)
-                                        (file-attributes (car elt))))
+                                 (let ((curr (file-attributes (car elt))))
+                                   ;; Don't compare the access time.
+                                   (if curr (setcar (nthcdr 4 curr) 0))
+                                   (setcar (nthcdr 4 (cdr elt)) 0)
+                                   (equal (cdr elt) curr)))
                               Info-dir-file-attributes))))
       (insert Info-dir-contents)
     (let ((dirs Info-directory-list)
          buffers buffer others nodes dirs-done)
 
+      (setq Info-dir-file-attributes nil)
+
       ;; Search the directory list for the directory file.
       (while dirs
        (let ((truename (file-truename (expand-file-name (car dirs)))))
@@ -347,34 +439,35 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
              (member (directory-file-name truename) 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)))))
+             (let* (file
+                    (attrs
+                     (or
+                      (progn (setq file (expand-file-name "dir" truename))
+                             (file-attributes file))
+                      (progn (setq file (expand-file-name "DIR" truename))
+                             (file-attributes file))
+                      (progn (setq file (expand-file-name "dir.info" truename))
+                             (file-attributes file))
+                      (progn (setq file (expand-file-name "DIR.INFO" truename))
+                             (file-attributes file)))))
                (setq dirs-done
                      (cons truename
                            (cons (directory-file-name truename)
                                  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)))
-
+               (if attrs
+                   (save-excursion
+                     (or buffers
+                         (message "Composing main Info directory..."))
+                     (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))))))
+         (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.
@@ -440,19 +533,20 @@ 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"))
+           (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
        (kill-buffer (car buffers))
-       (setq buffers (cdr buffers))))
+       (setq buffers (cdr buffers)))
+      (message "Composing main Info directory...done"))
     (setq Info-dir-contents (buffer-string)))
   (setq default-directory Info-dir-contents-directory))
 
@@ -501,10 +595,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)
@@ -550,12 +644,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)))
     (if (equal nodename "")
        (or default
            (Info-read-node-name prompt))
@@ -594,7 +708,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
@@ -606,6 +721,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))
@@ -650,7 +766,7 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                (message "Searching subfile %s..." (cdr (car list)))
                (Info-read-subfile (car (car list)))
                (setq list (cdr list))
-               (goto-char (point-min))
+;;             (goto-char (point-min))
                (if (re-search-forward regexp nil t)
                    (setq found (point) list ())))
              (if found
@@ -663,7 +779,8 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
     (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)))))
@@ -681,14 +798,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.
 (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") "]"))
@@ -787,7 +904,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)))
@@ -812,19 +929,69 @@ 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 and Info-menu-item doesn't exist.
+;; No one calls this.
 ;;(defun Info-menu-item-sequence (list)
 ;;  (while list
-;;    (Info-menu-item (car list))
+;;    (Info-menu (car list))
 ;;    (setq list (cdr list))))
 
+(defun Info-complete-menu-item (string predicate action)
+  (let ((case-fold-search t))
+    (cond ((eq action nil)
+          (let (completions
+                (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
+                                                       (match-beginning 1)
+                                                       (match-end 1)))
+                                              (match-beginning 1))
+                                        completions))))
+            (try-completion string completions predicate)))
+         ((eq action t)
+          (let (completions
+                (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
+                                                       (match-beginning 1)
+                                                       (match-end 1)))
+                                              (match-beginning 1))
+                                        completions))))
+            (all-completions string completions predicate)))
+         (t
+          (save-excursion
+            (set-buffer Info-complete-menu-buffer)
+            (goto-char (point-min))
+            (search-forward "\n* Menu:")
+            (re-search-forward (concat "\n\\* "
+                                       (regexp-quote string)
+                                       ":")
+                               nil t))))))
+
+
 (defun Info-menu (menu-item)
   "Go to node for menu item named (or abbreviated) NAME.
 Completion is allowed, and the menu item point is on is the default."
@@ -833,35 +1000,30 @@ 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))
        (if (not (search-forward "\n* menu:" nil t))
           (error "No menu in this node"))
-       (while (re-search-forward
-               "\n\\* \\([^:\t\n]*\\):" nil t)
-        (if (and (null default)
-                 (prog1 (if last (< last p) nil)
-                   (setq last (match-beginning 0)))
-                 (<= p last))
-            (setq default (car (car completions))))
-        (setq completions (cons (cons (buffer-substring
-                                        (match-beginning 1)
-                                        (match-end 1))
-                                      (match-beginning 1))
-                                completions)))
-       (if (and (null default) last
-               (< last p)
-               (<= p (progn (end-of-line) (point))))
-          (setq default (car (car completions)))))
+       (setq beg (point))
+       (and (< (point) p)
+           (save-excursion
+             (goto-char p)
+             (end-of-line)
+             (re-search-backward "\n\\* \\([^:\t\n]*\\):" beg t)
+             (setq default (format "%s" (buffer-substring
+                                         (match-beginning 1)
+                                         (match-end 1)))))))
      (let ((item nil))
        (while (null item)
-        (setq item (let ((completion-ignore-case t))
+        (setq item (let ((completion-ignore-case t)
+                         (Info-complete-menu-buffer (current-buffer)))
                      (completing-read (if default
                                           (format "Menu item (default %s): "
                                                   default)
                                           "Menu item: ")
-                                      completions nil t)))
+                                      'Info-complete-menu-item nil t)))
         ;; we rely on the fact that completing-read accepts an input
         ;; of "" even when the require-match argument is true and ""
         ;; is not a valid possibility
@@ -947,7 +1109,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)
@@ -965,7 +1129,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,
@@ -999,43 +1166,84 @@ N is the digit argument used to invoke this command."
   (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))))
+    (let ((beg (save-excursion
+                (and (search-backward "\n* menu:" nil t)
+                     (point)))))
+      (or (and beg (search-backward "\n* " beg t))
+         (error "No previous items in menu")))
+    (Info-goto-node (save-excursion
+                     (goto-char (match-end 0))
+                     (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."
+  "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-up))              (forward-line 1))
-       (t                                      (error "No more nodes"))))
+  (cond ((Info-no-error (Info-next-menu-item)))
+       ((Info-no-error (Info-next)))
+       ((Info-no-error (Info-up))
+        ;; Since we have already gone thru all the items in this menu,
+        ;; go up to the end of this node.
+        (goto-char (point-max)))
+       (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"))))
+  (cond ((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))
+        (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,
+the next scroll moves into its first subnode.  When you scroll past
+the end of a node, that goes to the next node or back up to the parent node."
   (interactive)
-  (if (pos-visible-in-window-p (point-max))
-      (Info-next-preorder)
-    (scroll-up)))
+  (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))
+                      (if (search-forward "\n* Menu:" nil t)
+                          (point)
+                        (point-max)))))
+    (if (or (< virtual-end (window-start))
+           (pos-visible-in-window-p virtual-end))
+       (Info-next-preorder)
+      (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 (pos-visible-in-window-p (point-min))
-      (Info-last-preorder)
-    (scroll-down)))
-
-(defun Info-next-reference ()
+  (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))))
+    (if (or virtual-end (pos-visible-in-window-p (point-min)))
+       (Info-last-preorder)
+      (scroll-down))))
+
+(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]*\\([^:]*\\):\\|^\\* .*:")
@@ -1050,9 +1258,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]*\\([^:]*\\):\\|^\\* .*:")
@@ -1066,7 +1276,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.
@@ -1080,7 +1292,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")
@@ -1089,12 +1301,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
@@ -1116,8 +1333,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)
@@ -1142,24 +1359,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)
@@ -1181,6 +1402,7 @@ Give a blank topic name to go to the Index node itself."
     (switch-to-buffer "*Help*")
     (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))))
@@ -1198,7 +1420,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
@@ -1306,6 +1528,8 @@ 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)
@@ -1326,6 +1550,10 @@ topics.  Info has commands to follow the references and show you other nodes.
 \\[Info-help]  Invoke the Info tutorial.
 
 Selecting other nodes:
+\\[Info-mouse-follow-nearest-node]
+       Follow a node reference you click on.
+         This works with menu items, cross references, and
+         the \"next\", \"previous\" and \"up\", depending on where you click.
 \\[Info-next]  Move to the \"next\" node of this node.
 \\[Info-prev]  Move to the \"previous\" node of this node.
 \\[Info-up]    Move \"up\" from this node.
@@ -1354,9 +1582,6 @@ 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)
@@ -1373,7 +1598,7 @@ Advanced commands:
   (make-local-variable 'Info-tag-table-marker)
   (make-local-variable 'Info-history)
   (make-local-variable 'Info-index-alternatives)
-  (if (fboundp 'make-face)
+  (if (memq (framep (selected-frame)) '(x pc))
       (progn
        (make-face 'info-node)
        (make-face 'info-menu-5)
@@ -1405,7 +1630,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.
@@ -1413,15 +1645,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."
@@ -1435,22 +1661,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 *\\(.*\\)\\.$")))
+                         ":\\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))
@@ -1466,7 +1723,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)))
@@ -1485,18 +1744,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)
@@ -1508,6 +1769,13 @@ The command is found by looking up in Emacs manual's Command Index."
          (t
           (Info-goto-emacs-command-node command)))))
 \f
+(defvar 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 ?-).")
+
 (defun Info-fontify-node ()
   (save-excursion
     (let ((buffer-read-only nil))
@@ -1523,7 +1791,15 @@ 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 "\\*Note[ \n\t]*\\([^:]*\\):" nil t)
+      (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)))
+       (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
          (put-text-property (match-beginning 1) (match-end 1)
@@ -1534,7 +1810,7 @@ The command is found by looking up in Emacs manual's Command Index."
       (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)) 30000))
+              (< (- (point-max) (point)) Info-fontify-maximum-menu-size))
          (let ((n 0))
            (while (re-search-forward "^\\* \\([^:\t\n]*\\):" nil t)
              (setq n (1+ n))