]> code.delx.au - gnu-emacs/blobdiff - lisp/info.el
* files.el (basic-save-buffer-1): Don't set buffer-file-coding-system-explicit.
[gnu-emacs] / lisp / info.el
index 163e0af161af3e5f57140023727fc64fc2d3a47d..c276420fb7d1803d7d99c0b68b96305c1cfc203d 100644 (file)
@@ -1,6 +1,6 @@
 ;; info.el --- info package for Emacs
 
 ;; info.el --- info package for Emacs
 
-;; Copyright (C) 1985-1986, 1992-2012 Free Software Foundation, Inc.
+;; Copyright (C) 1985-1986, 1992-2013 Free Software Foundation, Inc.
 
 ;; Maintainer: FSF
 ;; Keywords: help
 
 ;; Maintainer: FSF
 ;; Keywords: help
@@ -342,12 +342,12 @@ a tab, a carriage return (control-M), a newline, and `]+'."
 (defcustom Info-isearch-search t
   "If non-nil, isearch in Info searches through multiple nodes.
 Before leaving the initial Info node, where isearch was started,
 (defcustom Info-isearch-search t
   "If non-nil, isearch in Info searches through multiple nodes.
 Before leaving the initial Info node, where isearch was started,
-it fails once with the error message [initial node], and with
+it fails once with the error message [end of node], and with
 subsequent C-s/C-r continues through other nodes without failing
 with this error message in other nodes.  When isearch fails for
 subsequent C-s/C-r continues through other nodes without failing
 with this error message in other nodes.  When isearch fails for
-the rest of the manual, it wraps around the whole manual and
-restarts the search from the top/final node depending on
-search direction.
+the rest of the manual, it displays the error message [end of manual],
+wraps around the whole manual and restarts the search from the top/final
+node depending on search direction.
 
 Setting this option to nil restores the default isearch behavior
 with wrapping around the current Info node."
 
 Setting this option to nil restores the default isearch behavior
 with wrapping around the current Info node."
@@ -397,6 +397,10 @@ Marker points nowhere if file has no tag table.")
 (defvar Info-current-file-completions nil
   "Cached completion list for current Info file.")
 
 (defvar Info-current-file-completions nil
   "Cached completion list for current Info file.")
 
+(defvar Info-file-completions nil
+  "Cached completion alist of visited Info files.
+Each element of the alist is (FILE . COMPLETIONS)")
+
 (defvar Info-file-supports-index-cookies nil
   "Non-nil if current Info file supports index cookies.")
 
 (defvar Info-file-supports-index-cookies nil
   "Non-nil if current Info file supports index cookies.")
 
@@ -417,6 +421,21 @@ If number, the point is moved to the corresponding line.")
 (defvar Info-standalone nil
   "Non-nil if Emacs was started solely as an Info browser.")
 
 (defvar Info-standalone nil
   "Non-nil if Emacs was started solely as an Info browser.")
 
+(defvar Info-file-attributes nil
+  "Alist of file attributes of visited Info files.
+Each element is a list (FILE-NAME FILE-ATTRIBUTES...).")
+
+(defvar Info-toc-nodes nil
+  "Alist of cached parent-children node information in visited Info files.
+Each element is (FILE (NODE-NAME PARENT SECTION CHILDREN) ...)
+where PARENT is the parent node extracted from the Up pointer,
+SECTION is the section name in the Top node where this node is placed,
+CHILDREN is a list of child nodes extracted from the node menu.")
+
+(defvar Info-index-nodes nil
+  "Alist of cached index node names of visited Info files.
+Each element has the form (INFO-FILE INDEX-NODE-NAMES-LIST).")
+
 (defvar Info-virtual-files nil
   "List of definitions of virtual Info files.
 Each element of the list has the format (FILENAME (OPERATION . HANDLER) ...)
 (defvar Info-virtual-files nil
   "List of definitions of virtual Info files.
 Each element of the list has the format (FILENAME (OPERATION . HANDLER) ...)
@@ -609,7 +628,26 @@ Do the right thing if the file has been compressed or zipped."
            (apply 'call-process-region (point-min) (point-max)
                   (car decoder) t t nil (cdr decoder))))
       (let ((inhibit-null-byte-detection t)) ; Index nodes include null bytes
            (apply 'call-process-region (point-min) (point-max)
                   (car decoder) t t nil (cdr decoder))))
       (let ((inhibit-null-byte-detection t)) ; Index nodes include null bytes
-       (insert-file-contents fullname visit)))))
+       (insert-file-contents fullname visit)))
+
+    ;; Clear the caches of modified Info files.
+    (let* ((attribs-old (cdr (assoc fullname Info-file-attributes)))
+          (modtime-old (and attribs-old (nth 5 attribs-old)))
+          (attribs-new (and (stringp fullname) (file-attributes fullname)))
+          (modtime-new (and attribs-new (nth 5 attribs-new))))
+      (when (and modtime-old modtime-new
+                (> (float-time modtime-new) (float-time modtime-old)))
+       (setq Info-index-nodes (remove (assoc (or Info-current-file filename)
+                                             Info-index-nodes)
+                                      Info-index-nodes))
+       (setq Info-toc-nodes (remove (assoc (or Info-current-file filename)
+                                           Info-toc-nodes)
+                                    Info-toc-nodes)))
+      ;; Add new modtime to `Info-file-attributes'.
+      (setq Info-file-attributes
+           (cons (cons fullname attribs-new)
+                 (remove (assoc fullname Info-file-attributes)
+                         Info-file-attributes))))))
 
 (defun Info-file-supports-index-cookies (&optional file)
   "Return non-nil value if FILE supports Info index cookies.
 
 (defun Info-file-supports-index-cookies (&optional file)
   "Return non-nil value if FILE supports Info index cookies.
@@ -708,11 +746,15 @@ in `Info-file-supports-index-cookies-list'."
                  (push dir Info-directory-list)))))))
 
 ;;;###autoload
                  (push dir Info-directory-list)))))))
 
 ;;;###autoload
-(defun info-other-window (&optional file-or-node)
+(defun info-other-window (&optional file-or-node buffer)
   "Like `info' but show the Info buffer in another window."
   "Like `info' but show the Info buffer in another window."
-  (interactive (if current-prefix-arg
-                  (list (read-file-name "Info file name: " nil nil t))))
-  (info-setup file-or-node (switch-to-buffer-other-window "*info*")))
+  (interactive (list
+               (if (and current-prefix-arg (not (numberp current-prefix-arg)))
+                   (read-file-name "Info file name: " nil nil t))
+               (if (numberp current-prefix-arg)
+                   (format "*info*<%s>" current-prefix-arg))))
+  (info-setup file-or-node
+             (switch-to-buffer-other-window (or buffer "*info*"))))
 
 ;;;###autoload (put 'info 'info-file (purecopy "emacs"))
 ;;;###autoload
 
 ;;;###autoload (put 'info 'info-file (purecopy "emacs"))
 ;;;###autoload
@@ -729,8 +771,9 @@ with the top-level Info directory.
 
 In interactive use, a non-numeric prefix argument directs
 this command to read a file name from the minibuffer.
 
 In interactive use, a non-numeric prefix argument directs
 this command to read a file name from the minibuffer.
-A numeric prefix argument selects an Info buffer with the prefix number
-appended to the Info buffer name.
+
+A numeric prefix argument N selects an Info buffer named
+\"*info*<%s>\".
 
 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'
 
 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'
@@ -1634,7 +1677,9 @@ escaped (\\\",\\\\)."
                 " ("
                 (if (stringp Info-current-file)
                     (replace-regexp-in-string
                 " ("
                 (if (stringp Info-current-file)
                     (replace-regexp-in-string
-                     "%" "%%" (file-name-nondirectory Info-current-file))
+                     "%" "%%"
+                     (file-name-sans-extension
+                      (file-name-nondirectory Info-current-file)))
                   (format "*%S*" Info-current-file))
                 ") "
                 (if Info-current-node
                   (format "*%S*" Info-current-file))
                 ") "
                 (if Info-current-node
@@ -1658,7 +1703,9 @@ escaped (\\\",\\\\)."
 If NODENAME is of the form (FILENAME)NODENAME, the node is in the Info file
 FILENAME; otherwise, NODENAME should be in the current Info file (or one of
 its sub-files).
 If NODENAME is of the form (FILENAME)NODENAME, the node is in the Info file
 FILENAME; otherwise, NODENAME should be in the current Info file (or one of
 its sub-files).
-Completion is available, but only for node names in the current Info file.
+Completion is available for node names in the current Info file as well as
+in the Info file FILENAME after the closing parenthesis in (FILENAME).
+Empty NODENAME in (FILENAME) defaults to the Top node.
 If FORK is non-nil (interactively with a prefix arg), show the node in
 a new Info buffer.
 If FORK is a string, it is the name to use for the new buffer."
 If FORK is non-nil (interactively with a prefix arg), show the node in
 a new Info buffer.
 If FORK is a string, it is the name to use for the new buffer."
@@ -1695,6 +1742,7 @@ list of valid filename suffixes for Info files.  See
   (when (file-name-absolute-p string)
     (setq dirs (list (file-name-directory string))))
   (let ((names nil)
   (when (file-name-absolute-p string)
     (setq dirs (list (file-name-directory string))))
   (let ((names nil)
+       (names-sans-suffix nil)
         (suffix (concat (regexp-opt suffixes t) "\\'"))
         (string-dir (file-name-directory string)))
     (dolist (dir dirs)
         (suffix (concat (regexp-opt suffixes t) "\\'"))
         (string-dir (file-name-directory string)))
     (dolist (dir dirs)
@@ -1717,7 +1765,14 @@ list of valid filename suffixes for Info files.  See
          ;; add the unsuffixed name as a completion option.
          (when (string-match suffix file)
            (setq file (substring file 0 (match-beginning 0)))
          ;; add the unsuffixed name as a completion option.
          (when (string-match suffix file)
            (setq file (substring file 0 (match-beginning 0)))
-           (push (if string-dir (concat string-dir file) file) names)))))
+           (push (if string-dir (concat string-dir file) file)
+                 names-sans-suffix)))))
+    ;; If there is just one file, don't duplicate it with suffixes,
+    ;; so `Info-read-node-name-1' will be able to complete a single
+    ;; candidate and to add the terminating ")".
+    (if (and (= (length names) 1) (= (length names-sans-suffix) 1))
+       (setq names names-sans-suffix)
+      (setq names (append names-sans-suffix names)))
     (complete-with-action action names string pred)))
 
 (defun Info-read-node-name-1 (string predicate code)
     (complete-with-action action names string pred)))
 
 (defun Info-read-node-name-1 (string predicate code)
@@ -1735,12 +1790,23 @@ See `completing-read' for a description of arguments and usage."
      (substring string 1)
      predicate
      code))
      (substring string 1)
      predicate
      code))
-   ;; If a file name was given, then any node is fair game.
-   ((string-match "\\`(" string)
-    (cond
-     ((eq code nil) string)
-     ((eq code t) nil)
-     (t t)))
+   ;; If a file name was given, complete nodes in the file.
+   ((string-match "\\`(\\([^)]+\\))" string)
+    (let ((file0 (match-string 0 string))
+         (file1 (match-string 1 string))
+         (nodename (substring string (match-end 0))))
+      (if (and (equal nodename "") (eq code 'lambda))
+         ;; Empty node name is permitted that means "Top".
+         t
+       (completion-table-with-context
+        file0
+        (apply-partially
+         (lambda (string pred action)
+           (complete-with-action
+            action
+            (Info-build-node-completions (Info-find-file file1))
+            string pred)))
+        nodename predicate code))))
    ;; Otherwise use Info-read-node-completion-table.
    (t (complete-with-action
        code Info-read-node-completion-table string predicate))))
    ;; Otherwise use Info-read-node-completion-table.
    (t (complete-with-action
        code Info-read-node-completion-table string predicate))))
@@ -1749,7 +1815,9 @@ See `completing-read' for a description of arguments and usage."
 (defun Info-read-node-name (prompt)
   "Read an Info node name with completion, prompting with PROMPT.
 A node name can have the form \"NODENAME\", referring to a node
 (defun Info-read-node-name (prompt)
   "Read an Info node name with completion, prompting with PROMPT.
 A node name can have the form \"NODENAME\", referring to a node
-in the current Info file, or \"(FILENAME)NODENAME\"."
+in the current Info file, or \"(FILENAME)NODENAME\", referring to
+a node in FILENAME.  \"(FILENAME)\" is a short format to go to
+the Top node in FILENAME."
   (let* ((completion-ignore-case t)
         (Info-read-node-completion-table (Info-build-node-completions))
         (nodename (completing-read prompt 'Info-read-node-name-1 nil t)))
   (let* ((completion-ignore-case t)
         (Info-read-node-completion-table (Info-build-node-completions))
         (nodename (completing-read prompt 'Info-read-node-name-1 nil t)))
@@ -1757,41 +1825,54 @@ in the current Info file, or \"(FILENAME)NODENAME\"."
        (Info-read-node-name prompt)
       nodename)))
 
        (Info-read-node-name prompt)
       nodename)))
 
-(defun Info-build-node-completions ()
-  (or Info-current-file-completions
-      (let ((compl nil)
-           ;; Bind this in case the user sets it to nil.
-           (case-fold-search t)
-           (node-regexp "Node: *\\([^,\n]*\\) *[,\n\t]"))
-       (save-excursion
-         (save-restriction
-           (or Info-tag-table-marker
-               (error "No Info tags found"))
-           (if (marker-buffer Info-tag-table-marker)
-               (let ((marker Info-tag-table-marker))
-                 (set-buffer (marker-buffer marker))
-                 (widen)
-                 (goto-char marker)
-                 (while (re-search-forward "\n\\(Node\\|Ref\\): \\(.*\\)\177" nil t)
-                   (setq compl
-                         (cons (list (match-string-no-properties 2))
-                               compl))))
+(defun Info-build-node-completions (&optional filename)
+  (if filename
+      (or (cdr (assoc filename Info-file-completions))
+         (with-temp-buffer
+           (Info-mode)
+           (Info-goto-node (format "(%s)Top" filename))
+           (Info-build-node-completions-1)
+           (push (cons filename Info-current-file-completions) Info-file-completions)
+           Info-current-file-completions))
+    (or Info-current-file-completions
+       (Info-build-node-completions-1))))
+
+(defun Info-build-node-completions-1 ()
+  (let ((compl nil)
+       ;; Bind this in case the user sets it to nil.
+       (case-fold-search t)
+       (node-regexp "Node: *\\([^,\n]*\\) *[,\n\t]"))
+    (save-excursion
+      (save-restriction
+       (or Info-tag-table-marker
+           (error "No Info tags found"))
+       (if (marker-buffer Info-tag-table-marker)
+           (let ((marker Info-tag-table-marker))
+             (set-buffer (marker-buffer marker))
              (widen)
              (widen)
-             (goto-char (point-min))
-             ;; If the buffer begins with a node header, process that first.
-             (if (Info-node-at-bob-matching node-regexp)
-                 (setq compl (list (match-string-no-properties 1))))
-             ;; Now for the rest of the nodes.
-             (while (search-forward "\n\^_" nil t)
-               (forward-line 1)
-               (let ((beg (point)))
-                 (forward-line 1)
-                 (if (re-search-backward node-regexp beg t)
-                     (setq compl
-                           (cons (list (match-string-no-properties 1))
-                                 compl))))))))
-       (setq compl (cons '("*") compl))
-       (set (make-local-variable 'Info-current-file-completions) compl))))
+             (goto-char marker)
+             (while (re-search-forward "\n\\(Node\\|Ref\\): \\(.*\\)\177" nil t)
+               (setq compl
+                     (cons (list (match-string-no-properties 2))
+                           compl))))
+         (widen)
+         (goto-char (point-min))
+         ;; If the buffer begins with a node header, process that first.
+         (if (Info-node-at-bob-matching node-regexp)
+             (setq compl (list (match-string-no-properties 1))))
+         ;; Now for the rest of the nodes.
+         (while (search-forward "\n\^_" nil t)
+           (forward-line 1)
+           (let ((beg (point)))
+             (forward-line 1)
+             (if (re-search-backward node-regexp beg t)
+                 (setq compl
+                       (cons (list (match-string-no-properties 1))
+                             compl))))))))
+    (setq compl (cons '("*") (nreverse compl)))
+    (set (make-local-variable 'Info-current-file-completions) compl)
+    compl))
+
 \f
 (defun Info-restore-point (hl)
   "If this node has been visited, restore the point value when we left."
 \f
 (defun Info-restore-point (hl)
   "If this node has been visited, restore the point value when we left."
@@ -1848,9 +1929,7 @@ If DIRECTION is `backward', search in the reverse direction."
          (while (and (not give-up)
                      (or (null found)
                          (not (funcall isearch-filter-predicate beg-found found))))
          (while (and (not give-up)
                      (or (null found)
                          (not (funcall isearch-filter-predicate beg-found found))))
-           (let ((search-spaces-regexp
-                  (if (or (not isearch-mode) isearch-regexp)
-                      Info-search-whitespace-regexp)))
+           (let ((search-spaces-regexp Info-search-whitespace-regexp))
              (if (if backward
                      (re-search-backward regexp bound t)
                    (re-search-forward regexp bound t))
              (if (if backward
                      (re-search-backward regexp bound t)
                    (re-search-forward regexp bound t))
@@ -1863,16 +1942,14 @@ If DIRECTION is `backward', search in the reverse direction."
                 (not bound)
                 (or give-up (and found (not (and (> found opoint-min)
                                                  (< found opoint-max))))))
                 (not bound)
                 (or give-up (and found (not (and (> found opoint-min)
                                                  (< found opoint-max))))))
-       (signal 'search-failed (list regexp "initial node")))
+       (signal 'search-failed (list regexp "end of node")))
 
       ;; If no subfiles, give error now.
       (if give-up
          (if (null Info-current-subfile)
              (if isearch-mode
                  (signal 'search-failed (list regexp "end of manual"))
 
       ;; If no subfiles, give error now.
       (if give-up
          (if (null Info-current-subfile)
              (if isearch-mode
                  (signal 'search-failed (list regexp "end of manual"))
-               (let ((search-spaces-regexp
-                      (if (or (not isearch-mode) isearch-regexp)
-                          Info-search-whitespace-regexp)))
+               (let ((search-spaces-regexp Info-search-whitespace-regexp))
                  (if backward
                      (re-search-backward regexp)
                    (re-search-forward regexp))))
                  (if backward
                      (re-search-backward regexp)
                    (re-search-forward regexp))))
@@ -1930,9 +2007,7 @@ If DIRECTION is `backward', search in the reverse direction."
                (while (and (not give-up)
                            (or (null found)
                                (not (funcall isearch-filter-predicate beg-found found))))
                (while (and (not give-up)
                            (or (null found)
                                (not (funcall isearch-filter-predicate beg-found found))))
-                 (let ((search-spaces-regexp
-                        (if (or (not isearch-mode) isearch-regexp)
-                            Info-search-whitespace-regexp)))
+                 (let ((search-spaces-regexp Info-search-whitespace-regexp))
                    (if (if backward
                            (re-search-backward regexp nil t)
                          (re-search-forward regexp nil t))
                    (if (if backward
                            (re-search-backward regexp nil t)
                          (re-search-forward regexp nil t))
@@ -2000,21 +2075,26 @@ If DIRECTION is `backward', search in the reverse direction."
 (defun Info-isearch-search ()
   (if Info-isearch-search
       (lambda (string &optional bound noerror count)
 (defun Info-isearch-search ()
   (if Info-isearch-search
       (lambda (string &optional bound noerror count)
-       (Info-search
-        (cond
-         (isearch-word
-          ;; Lax version of word search
-          (let ((lax (not (or isearch-nonincremental
-                              (eq (length string)
-                                  (length (isearch-string-state
-                                           (car isearch-cmds))))))))
-            (if (functionp isearch-word)
-                (funcall isearch-word string lax)
-              (word-search-regexp string lax))))
-         (isearch-regexp string)
-         (t (regexp-quote string)))
-        bound noerror count
-        (unless isearch-forward 'backward))
+       (let ((Info-search-whitespace-regexp
+              (if (if isearch-regexp
+                      isearch-regexp-lax-whitespace
+                    isearch-lax-whitespace)
+                  search-whitespace-regexp)))
+         (Info-search
+          (cond
+           (isearch-word
+            ;; Lax version of word search
+            (let ((lax (not (or isearch-nonincremental
+                                (eq (length string)
+                                    (length (isearch--state-string
+                                             (car isearch-cmds))))))))
+              (if (functionp isearch-word)
+                  (funcall isearch-word string lax)
+                (word-search-regexp string lax))))
+           (isearch-regexp string)
+           (t (regexp-quote string)))
+          bound noerror count
+          (unless isearch-forward 'backward)))
        (point))
     (isearch-search-fun-default)))
 
        (point))
     (isearch-search-fun-default)))
 
@@ -2394,13 +2474,6 @@ Table of contents is created from the tree structure of menus."
       (message "")
       (nreverse nodes))))
 
       (message "")
       (nreverse nodes))))
 
-(defvar Info-toc-nodes nil
-  "Alist of cached parent-children node information in visited Info files.
-Each element is (FILE (NODE-NAME PARENT SECTION CHILDREN) ...)
-where PARENT is the parent node extracted from the Up pointer,
-SECTION is the section name in the Top node where this node is placed,
-CHILDREN is a list of child nodes extracted from the node menu.")
-
 (defun Info-toc-nodes (filename)
   "Return a node list of Info FILENAME with parent-children information.
 This information is cached in the variable `Info-toc-nodes' with the help
 (defun Info-toc-nodes (filename)
   "Return a node list of Info FILENAME with parent-children information.
 This information is cached in the variable `Info-toc-nodes' with the help
@@ -2620,6 +2693,7 @@ Because of ambiguities, this should be concatenated with something like
                     (while (re-search-forward pattern nil t)
                       (push (match-string-no-properties 1)
                             completions))
                     (while (re-search-forward pattern nil t)
                       (push (match-string-no-properties 1)
                             completions))
+                   (setq completions (delete-dups completions))
                     ;; Check subsequent nodes if applicable.
                     (or (and Info-complete-next-re
                              (setq nextnode (Info-extract-pointer "next" t))
                     ;; Check subsequent nodes if applicable.
                     (or (and Info-complete-next-re
                              (setq nextnode (Info-extract-pointer "next" t))
@@ -2854,7 +2928,7 @@ N is the digit argument used to invoke this command."
                      (Info-extract-menu-node-name)))))
 
 (defmacro Info-no-error (&rest body)
                      (Info-extract-menu-node-name)))))
 
 (defmacro Info-no-error (&rest body)
-  (list 'condition-case nil (cons 'progn (append body '(t))) '(error nil)))
+  `(condition-case nil (progn ,@body t) (error nil)))
 
 (defun Info-next-preorder ()
   "Go to the next subnode or the next node, or go up a level."
 
 (defun Info-next-preorder ()
   "Go to the next subnode or the next node, or go up a level."
@@ -2989,53 +3063,63 @@ See `Info-scroll-down'."
        (select-window (posn-window (event-start e))))
     (Info-scroll-down)))
 
        (select-window (posn-window (event-start e))))
     (Info-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]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://")
-       (old-pt (point))
-       (case-fold-search t))
-    (or (eobp) (forward-char 1))
-    (or (re-search-forward pat nil t)
-       (progn
-         (goto-char (point-min))
-         (or (re-search-forward pat nil t)
-             (progn
-               (goto-char old-pt)
-               (user-error "No cross references in this node")))))
-    (goto-char (or (match-beginning 1) (match-beginning 0)))
-    (if (looking-at "\\* Menu:")
-       (if recur
-           (user-error "No cross references in this node")
-         (Info-next-reference t))
-      (if (looking-at "^\\* ")
-         (forward-char 2)))))
-
-(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]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://")
-       (old-pt (point))
-       (case-fold-search t))
-    (or (re-search-backward pat nil t)
-       (progn
-         (goto-char (point-max))
-         (or (re-search-backward pat nil t)
-             (progn
-               (goto-char old-pt)
-               (user-error "No cross references in this node")))))
-    (goto-char (or (match-beginning 1) (match-beginning 0)))
-    (if (looking-at "\\* Menu:")
-       (if recur
-           (user-error "No cross references in this node")
-         (Info-prev-reference t))
-      (if (looking-at "^\\* ")
-         (forward-char 2)))))
+(defun Info-next-reference (&optional recur count)
+  "Move cursor to the next cross-reference or menu item in the node.
+If COUNT is non-nil (interactively with a prefix arg), jump over
+COUNT cross-references."
+  (interactive "i\np")
+  (unless count
+    (setq count 1))
+  (if (< count 0)
+      (Info-prev-reference recur (- count))
+    (while (unless (zerop count) (setq count (1- count)))
+      (let ((pat "\\*note[ \n\t]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://")
+           (old-pt (point))
+           (case-fold-search t))
+       (or (eobp) (forward-char 1))
+       (or (re-search-forward pat nil t)
+           (progn
+             (goto-char (point-min))
+             (or (re-search-forward pat nil t)
+                 (progn
+                   (goto-char old-pt)
+                   (user-error "No cross references in this node")))))
+       (goto-char (or (match-beginning 1) (match-beginning 0)))
+       (if (looking-at "\\* Menu:")
+           (if recur
+               (user-error "No cross references in this node")
+             (Info-next-reference t))
+         (if (looking-at "^\\* ")
+             (forward-char 2)))))))
+
+(defun Info-prev-reference (&optional recur count)
+  "Move cursor to the previous cross-reference or menu item in the node.
+If COUNT is non-nil (interactively with a prefix arg), jump over
+COUNT cross-references."
+  (interactive "i\np")
+  (unless count
+    (setq count 1))
+  (if (< count 0)
+      (Info-next-reference recur (- count))
+    (while (unless (zerop count) (setq count (1- count)))
+      (let ((pat "\\*note[ \n\t]+\\([^:]+\\):\\|^\\* .*:\\|[hf]t?tps?://")
+           (old-pt (point))
+           (case-fold-search t))
+       (or (re-search-backward pat nil t)
+           (progn
+             (goto-char (point-max))
+             (or (re-search-backward pat nil t)
+                 (progn
+                   (goto-char old-pt)
+                   (user-error "No cross references in this node")))))
+       (goto-char (or (match-beginning 1) (match-beginning 0)))
+       (if (looking-at "\\* Menu:")
+           (if recur
+               (user-error "No cross references in this node")
+             (Info-prev-reference t))
+         (if (looking-at "^\\* ")
+             (forward-char 2)))))))
 \f
 \f
-(defvar Info-index-nodes nil
-  "Alist of cached index node names of visited Info files.
-Each element has the form (INFO-FILE INDEX-NODE-NAMES-LIST).")
-
 (defun Info-index-nodes (&optional file)
   "Return a list of names of all index nodes in Info FILE.
 If FILE is omitted, it defaults to the current Info file.
 (defun Info-index-nodes (&optional file)
   "Return a list of names of all index nodes in Info FILE.
 If FILE is omitted, it defaults to the current Info file.
@@ -4009,7 +4093,9 @@ With a zero prefix arg, put the name inside a function call to `info'."
   (unless Info-current-node
     (user-error "No current Info node"))
   (let ((node (if (stringp Info-current-file)
   (unless Info-current-node
     (user-error "No current Info node"))
   (let ((node (if (stringp Info-current-file)
-                 (concat "(" (file-name-nondirectory Info-current-file) ") "
+                 (concat "(" (file-name-sans-extension
+                              (file-name-nondirectory Info-current-file))
+                         ") "
                          Info-current-node))))
     (if (zerop (prefix-numeric-value arg))
         (setq node (concat "(info \"" node "\")")))
                          Info-current-node))))
     (if (zerop (prefix-numeric-value arg))
         (setq node (concat "(info \"" node "\")")))
@@ -4134,8 +4220,6 @@ Advanced commands:
        'Info-isearch-push-state)
   (set (make-local-variable 'isearch-filter-predicate)
        'Info-isearch-filter)
        'Info-isearch-push-state)
   (set (make-local-variable 'isearch-filter-predicate)
        'Info-isearch-filter)
-  (set (make-local-variable 'search-whitespace-regexp)
-       Info-search-whitespace-regexp)
   (set (make-local-variable 'revert-buffer-function)
        'Info-revert-buffer-function)
   (Info-set-mode-line)
   (set (make-local-variable 'revert-buffer-function)
        'Info-revert-buffer-function)
   (Info-set-mode-line)
@@ -4243,7 +4327,7 @@ If the element is just a file name, the file name also serves as the prefix.")
 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 `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.
+The locations are of the format used in the variable `Info-history', i.e.
 \(FILENAME NODENAME BUFFERPOS), where BUFFERPOS is the line number
 in the first element of the returned list (which is treated specially in
 `Info-goto-emacs-command-node'), and 0 for the rest elements of a list."
 \(FILENAME NODENAME BUFFERPOS), where BUFFERPOS is the line number
 in the first element of the returned list (which is treated specially in
 `Info-goto-emacs-command-node'), and 0 for the rest elements of a list."
@@ -4398,7 +4482,8 @@ first line or header line, and for breadcrumb links.")
               (if (not (equal node "Top")) node
                 (format "(%s)Top"
                         (if (stringp Info-current-file)
               (if (not (equal node "Top")) node
                 (format "(%s)Top"
                         (if (stringp Info-current-file)
-                            (file-name-nondirectory Info-current-file)
+                            (file-name-sans-extension
+                             (file-name-nondirectory Info-current-file))
                           ;; Some legacy code can still use a symbol.
                           Info-current-file)))))
          (setq line (concat
                           ;; Some legacy code can still use a symbol.
                           Info-current-file)))))
          (setq line (concat
@@ -4504,7 +4589,18 @@ first line or header line, and for breadcrumb links.")
              ((not (bobp))
               ;; Hide the punctuation at the end, too.
               (skip-chars-backward " \t,")
              ((not (bobp))
               ;; Hide the punctuation at the end, too.
               (skip-chars-backward " \t,")
-              (put-text-property (point) header-end 'invisible t))))))
+              (put-text-property (point) header-end 'invisible t)
+             ;; Hide the suffix of the Info file name.
+             (beginning-of-line)
+             (if (re-search-forward
+                  (format "File: %s\\([^,\n\t]+\\),"
+                          (if (stringp Info-current-file)
+                              (file-name-sans-extension
+                               (file-name-nondirectory Info-current-file))
+                            Info-current-file))
+                  header-end t)
+                 (put-text-property (match-beginning 1) (match-end 1)
+                                    'invisible t)))))))
 
       ;; Fontify titles
       (goto-char (point-min))
 
       ;; Fontify titles
       (goto-char (point-min))
@@ -4792,6 +4888,12 @@ first line or header line, and for breadcrumb links.")
                                  mouse-face highlight
                                  help-echo "mouse-2: go to this URL"))))
 
                                  mouse-face highlight
                                  help-echo "mouse-2: go to this URL"))))
 
+      ;; Hide empty lines at the end of the node.
+      (goto-char (point-max))
+      (skip-chars-backward "\n")
+      (when (< (point) (1- (point-max)))
+       (put-text-property (point) (1- (point-max)) 'invisible t))
+
       (set-buffer-modified-p nil))))
 \f
 ;;; Speedbar support:
       (set-buffer-modified-p nil))))
 \f
 ;;; Speedbar support:
@@ -4799,6 +4901,17 @@ first line or header line, and for breadcrumb links.")
 ;; current Info node.
 (eval-when-compile (require 'speedbar))
 
 ;; current Info node.
 (eval-when-compile (require 'speedbar))
 
+(declare-function speedbar-add-expansion-list "speedbar" (new-list))
+(declare-function speedbar-center-buffer-smartly "speedbar" ())
+(declare-function speedbar-change-expand-button-char "speedbar" (char))
+(declare-function speedbar-change-initial-expansion-list "speedbar" (new-default))
+(declare-function speedbar-delete-subblock "speedbar" (indent))
+(declare-function speedbar-make-specialized-keymap "speedbar" ())
+(declare-function speedbar-make-tag-line "speedbar"
+                  (exp-button-type exp-button-char exp-button-function
+                   exp-button-data tag-button tag-button-function
+                   tag-button-data tag-button-face depth))
+
 (defvar Info-speedbar-key-map nil
   "Keymap used when in the Info display mode.")
 
 (defvar Info-speedbar-key-map nil
   "Keymap used when in the Info display mode.")
 
@@ -5020,11 +5133,19 @@ BUFFER is the buffer speedbar is requesting buttons for."
 (defun Info-bookmark-make-record ()
   "This implements the `bookmark-make-record-function' type (which see)
 for Info nodes."
 (defun Info-bookmark-make-record ()
   "This implements the `bookmark-make-record-function' type (which see)
 for Info nodes."
-  `(,Info-current-node
-    ,@(bookmark-make-record-default 'no-file)
-    (filename . ,Info-current-file)
-    (info-node . ,Info-current-node)
-    (handler . Info-bookmark-jump)))
+  (let* ((file (and (stringp Info-current-file)
+                   (file-name-sans-extension
+                    (file-name-nondirectory Info-current-file))))
+        (bookmark-name (if file
+                           (concat "(" file ") " Info-current-node)
+                         Info-current-node))
+        (defaults (delq nil (list bookmark-name file Info-current-node))))
+    `(,bookmark-name
+      ,@(bookmark-make-record-default 'no-file)
+      (filename . ,Info-current-file)
+      (info-node . ,Info-current-node)
+      (handler . Info-bookmark-jump)
+      (defaults . ,defaults))))
 
 ;;;###autoload
 (defun Info-bookmark-jump (bmk)
 
 ;;;###autoload
 (defun Info-bookmark-jump (bmk)
@@ -5042,8 +5163,16 @@ type returned by `Info-bookmark-make-record', which see."
 \f
 ;;;###autoload
 (defun info-display-manual (manual)
 \f
 ;;;###autoload
 (defun info-display-manual (manual)
-  "Go to Info buffer that displays MANUAL, creating it if none already exists."
-  (interactive "sManual name: ")
+  "Display an Info buffer displaying MANUAL.
+If there is an existing Info buffer for MANUAL, display it.
+Otherwise, visit the manual in a new Info buffer."
+  (interactive
+   (list
+    (progn
+      (info-initialize)
+      (completing-read "Manual name: "
+                      (info--manual-names)
+                      nil t))))
   (let ((blist (buffer-list))
        (manual-re (concat "\\(/\\|\\`\\)" manual "\\(\\.\\|\\'\\)"))
        (case-fold-search t)
   (let ((blist (buffer-list))
        (manual-re (concat "\\(/\\|\\`\\)" manual "\\(\\.\\|\\'\\)"))
        (case-fold-search t)
@@ -5058,7 +5187,25 @@ type returned by `Info-bookmark-make-record', which see."
     (if found
        (switch-to-buffer found)
       (info-initialize)
     (if found
        (switch-to-buffer found)
       (info-initialize)
-      (info (Info-find-file manual)))))
+      (info (Info-find-file manual)
+           (generate-new-buffer-name "*info*")))))
+
+(defun info--manual-names ()
+  (let (names)
+    (dolist (buffer (buffer-list))
+      (with-current-buffer buffer
+       (and (eq major-mode 'Info-mode)
+            (stringp Info-current-file)
+            (not (string= (substring (buffer-name) 0 1) " "))
+            (push (file-name-sans-extension
+                   (file-name-nondirectory Info-current-file))
+                  names))))
+    (delete-dups (append (nreverse names)
+                        (all-completions
+                         ""
+                         (apply-partially 'Info-read-node-name-2
+                                          Info-directory-list
+                                          (mapcar 'car Info-suffix-list)))))))
 
 (provide 'info)
 
 
 (provide 'info)