]> code.delx.au - gnu-emacs/blobdiff - lisp/info.el
* net/tramp.el (tramp-ssh-controlmaster-template): Make it a
[gnu-emacs] / lisp / info.el
index 93046bd13d63c3517a30c87ab956dab5e78c5619..3792857d47afc2b6c60698835ab111c4eab72d37 100644 (file)
@@ -1,6 +1,6 @@
 ;; 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
@@ -59,15 +59,6 @@ to the user."
   :group 'info
   :version "24.1")
 
-(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."
-  :type 'boolean
-  :group 'info)
-
 (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.")
@@ -375,6 +366,9 @@ with wrapping around the current Info node."
 (defvar Info-edit-mode-hook nil
   "Hooks run when `Info-edit-mode' is called.")
 
+(make-obsolete-variable 'Info-edit-mode-hook
+                       "editing Info nodes by hand is not recommended." "24.4")
+
 (defvar Info-current-file 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.
@@ -397,6 +391,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-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.")
 
@@ -417,6 +415,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-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) ...)
@@ -609,7 +622,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
-       (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.
@@ -708,11 +740,15 @@ in `Info-file-supports-index-cookies-list'."
                  (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."
-  (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
@@ -729,8 +765,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.
-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'
@@ -1634,7 +1671,9 @@ escaped (\\\",\\\\)."
                 " ("
                 (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
@@ -1658,7 +1697,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).
-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."
@@ -1695,6 +1736,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)
+       (names-sans-suffix nil)
         (suffix (concat (regexp-opt suffixes t) "\\'"))
         (string-dir (file-name-directory string)))
     (dolist (dir dirs)
@@ -1717,7 +1759,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)))
-           (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)
@@ -1735,12 +1784,23 @@ See `completing-read' for a description of arguments and usage."
      (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))))
@@ -1749,7 +1809,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
-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)))
@@ -1757,41 +1819,54 @@ in the current Info file, or \"(FILENAME)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)
-             (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."
@@ -1848,9 +1923,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))))
-           (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))
@@ -1870,9 +1943,7 @@ If DIRECTION is `backward', search in the reverse direction."
          (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))))
@@ -1930,9 +2001,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))))
-                 (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))
@@ -2000,21 +2069,26 @@ If DIRECTION is `backward', search in the reverse direction."
 (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--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))
+       (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)))
 
@@ -2071,7 +2145,7 @@ and is not in the header line or a tag table."
     (let ((backward (< found beg-found)))
       (not
        (or
-       (and (not (eq search-invisible t))
+       (and (not search-invisible)
             (if backward
                 (or (text-property-not-all found beg-found 'invisible nil)
                     (text-property-not-all found beg-found 'display nil))
@@ -2394,13 +2468,6 @@ Table of contents is created from the tree structure of menus."
       (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
@@ -2620,6 +2687,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))
+                   (setq completions (delete-dups completions))
                     ;; Check subsequent nodes if applicable.
                     (or (and Info-complete-next-re
                              (setq nextnode (Info-extract-pointer "next" t))
@@ -2989,53 +3057,63 @@ See `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
-(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.
@@ -3790,6 +3868,7 @@ If FORK is non-nil, it is passed to `Info-goto-node'."
     (suppress-keymap map)
     (define-key map "." 'beginning-of-buffer)
     (define-key map " " 'Info-scroll-up)
+    (define-key map [?\S-\ ] 'Info-scroll-down)
     (define-key map "\C-m" 'Info-follow-nearest-node)
     (define-key map "\t" 'Info-next-reference)
     (define-key map "\e\t" 'Info-prev-reference)
@@ -4009,7 +4088,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)
-                 (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 "\")")))
@@ -4134,8 +4215,6 @@ Advanced commands:
        '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)
@@ -4168,6 +4247,10 @@ Advanced commands:
                        map)
   "Local keymap used within `e' command of Info.")
 
+(make-obsolete-variable 'Info-edit-map
+                       "editing Info nodes by hand is not recommended."
+                       "24.4")
+
 ;; Info-edit mode is suitable only for specially formatted data.
 (put 'Info-edit-mode 'mode-class 'special)
 
@@ -4185,16 +4268,22 @@ which returns to Info mode for browsing.
   (buffer-enable-undo (current-buffer))
   (run-mode-hooks 'Info-edit-mode-hook))
 
+(make-obsolete 'Info-edit-mode
+              "editing Info nodes by hand is not recommended." "24.4")
+
 (defun Info-edit ()
-  "Edit the contents of this Info node.
-Allowed only if variable `Info-enable-edit' is non-nil."
+  "Edit the contents of this Info node."
   (interactive)
-  (or Info-enable-edit
-      (error "Editing Info nodes is not enabled"))
   (Info-edit-mode)
   (message "%s" (substitute-command-keys
                 "Editing: Type \\<Info-edit-map>\\[Info-cease-edit] to return to info")))
 
+(put 'Info-edit 'disabled "Editing Info nodes by hand is not recommended.
+This feature will be removed in future.")
+
+(make-obsolete 'Info-edit
+              "editing Info nodes by hand is not recommended." "24.4")
+
 (defun Info-cease-edit ()
   "Finish editing Info node; switch back to Info proper."
   (interactive)
@@ -4211,6 +4300,9 @@ Allowed only if variable `Info-enable-edit' is non-nil."
   (and (marker-position Info-tag-table-marker)
        (buffer-modified-p)
        (message "Tags may have changed.  Use Info-tagify if necessary")))
+
+(make-obsolete 'Info-cease-edit
+              "editing Info nodes by hand is not recommended." "24.4")
 \f
 (defvar Info-file-list-for-emacs
   '("ediff" "eudc" "forms" "gnus" "info" ("Info" . "info") ("mh" . "mh-e")
@@ -4243,7 +4335,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 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."
@@ -4398,7 +4490,8 @@ first line or header line, and for breadcrumb links.")
               (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
@@ -4504,7 +4597,18 @@ first line or header line, and for breadcrumb links.")
              ((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))
@@ -4792,6 +4896,12 @@ first line or header line, and for breadcrumb links.")
                                  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:
@@ -4799,6 +4909,17 @@ first line or header line, and for breadcrumb links.")
 ;; 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.")
 
@@ -5020,11 +5141,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."
-  `(,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)
@@ -5042,8 +5171,16 @@ type returned by `Info-bookmark-make-record', which see."
 \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)
@@ -5058,7 +5195,25 @@ type returned by `Info-bookmark-make-record', which see."
     (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)