]> code.delx.au - gnu-emacs/blobdiff - lisp/info.el
(fortran-comment-line-start): Renamed from comment-line-start.
[gnu-emacs] / lisp / info.el
index 8338401fe806c533dc086712aa8179176630f636..c48e2800cf3a9085328d4cb74febd15048a3f896 100644 (file)
@@ -1,6 +1,6 @@
 ;;; info.el --- info package for Emacs.
 
-;; Copyright (C) 1985, 86, 92, 93, 94, 95, 96, 97 Free Software
+;; Copyright (C) 1985, 86, 92, 93, 94, 95, 96, 97, 98, 99 Free Software
 ;; Foundation, Inc.
 
 ;; Maintainer: FSF
@@ -29,6 +29,8 @@
 
 ;;; Code:
 
+(eval-when-compile (require 'jka-compr))
+
 (defgroup info nil
   "Info subsystem"
   :group 'help
@@ -78,42 +80,7 @@ The Lisp code is executed when the node is selected.")
   :type 'integer
   :group 'info)
 
-(defvar Info-directory-list
-  (let ((path (getenv "INFOPATH"))
-       ;; 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-separator path) (length path))
-                 list (cons (substring path 0 idx) list)
-                 path (substring path (min (1+ idx)
-                                           (length path)))))
-         (nreverse list))
-      (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 alternative
-                      (cdr (reverse Info-default-directory-list)))))))
+(defvar Info-directory-list nil
   "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'
@@ -135,7 +102,8 @@ 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.
 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.")
+It doesn't contain directory names or file name extensions added by Info.
+Can also be t when using `Info-on-current-buffer'.")
 
 (defvar Info-current-subfile nil
   "Info subfile that is actually in the *info* buffer now,
@@ -159,13 +127,14 @@ Marker points nowhere if file has no tag table.")
 
 (defvar Info-standalone nil
   "Non-nil if Emacs was started solely as an Info browser.")
-
+\f
 (defvar Info-suffix-list
   ;; The MS-DOS list should work both when long file names are
   ;; supported (Windows 9X), and when only 8+3 file names are available.
   (if (eq system-type 'ms-dos)
       '( (".gz"      . "gunzip")
         (".z"       . "gunzip")
+        (".bz2"     . "bzip2 -dc")
         (".inz"     . "gunzip")
         (".igz"     . "gunzip")
         (".info.Z"  . "gunzip")
@@ -183,21 +152,25 @@ Marker points nowhere if file has no tag table.")
        (".info.Y".    "unyabba")
        (".info.gz".   "gunzip")
        (".info.z".    "gunzip")
+       (".info.bz2" . "bzip2 -dc")
        (".info".      nil)
        ("-info.Z".   "uncompress")
        ("-info.Y".   "unyabba")
        ("-info.gz".  "gunzip")
+       ("-info.bz2" . "bzip2 -dc")
        ("-info.z".   "gunzip")
        ("-info".     nil)
        ("/index.Z".   "uncompress")
        ("/index.Y".   "unyabba")
        ("/index.gz".  "gunzip")
        ("/index.z".   "gunzip")
+       ("/index.bz2". "bzip2 -dc")
        ("/index".     nil)
        (".Z".         "uncompress")
        (".Y".         "unyabba")
        (".gz".        "gunzip")
        (".z".         "gunzip")
+       (".bz2" .      "bzip2 -dc")
        ("".           nil)))
   "List of file name suffixes and associated decoding commands.
 Each entry should be (SUFFIX . STRING); the file is given to
@@ -273,6 +246,40 @@ Do the right thing if the file has been compressed or zipped."
                                       default-directory)))
            (call-process-region (point-min) (point-max) decoder t t)))
       (insert-file-contents fullname visit))))
+\f
+;; Initialize Info-directory-list, if that hasn't been done yet.
+(defun info-initialize ()
+  (unless Info-directory-list
+    (let ((path (getenv "INFOPATH"))
+         (source (expand-file-name "info/" source-directory))
+         (sibling (if installation-directory
+                      (expand-file-name "info/" installation-directory)))
+         alternative)
+      (setq Info-directory-list
+           (if path
+               (split-string path (regexp-quote path-separator))
+             (if (and sibling (file-exists-p sibling))
+                 ;; Uninstalled, Emacs builddir != srcdir.
+                 (setq alternative sibling)
+               ;; Uninstalled, builddir == srcdir
+               (setq alternative source))
+             (if (or (member alternative Info-default-directory-list)
+                     ;; On DOS/NT, we use movable executables always,
+                     ;; and we must always find the Info dir at run time.
+                     (if (memq system-type '(ms-dos 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))))
+                     (not (file-exists-p alternative)))
+                 Info-default-directory-list
+               ;; `alternative' contains the Info files that came with this
+               ;; version, so we should look there first.  `Info-insert-dir'
+               ;; currently expects to find `alternative' first on the list.
+               (cons alternative
+                     (reverse (cdr (reverse Info-default-directory-list))))))))))
 
 ;;;###autoload
 (defun info-other-window (&optional file)
@@ -289,6 +296,8 @@ Do the right thing if the file has been compressed or zipped."
   "Enter Info, the documentation browser.
 Optional argument FILE specifies the file to examine;
 the default is the top-level directory of Info.
+Called from a program, FILE may specify an Info node of the form
+`(FILENAME)NODENAME'.
 
 In interactive use, a prefix argument directs this command
 to read a file name from the minibuffer.
@@ -299,8 +308,15 @@ in all the directories in that path."
   (interactive (if current-prefix-arg
                   (list (read-file-name "Info file name: " nil nil t))))
   (if file
-      (progn (pop-to-buffer "*info*")
-            (Info-goto-node (concat "(" file ")")))
+      (progn
+       (pop-to-buffer "*info*")
+       ;; If argument already contains parentheses, don't add another set
+       ;; since the argument will then be parsed improperly.  This also
+       ;; has the added benefit of allowing node names to be included
+       ;; following the parenthesized filename.
+       (if (and (stringp file) (string-match "(.*)" file))
+           (Info-goto-node file)
+         (Info-goto-node (concat "(" file ")"))))
     (if (get-buffer "*info*")
        (pop-to-buffer "*info*")
       (Info-directory))))
@@ -322,14 +338,35 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                                   (nth 1 err) err)))
               (save-buffers-kill-emacs)))
     (info)))
+\f
+;; See if the the accessible portion of the buffer begins with a node
+;; delimiter, and the node header line which follows matches REGEXP.
+;; Typically, this test will be followed by a loop that examines the
+;; rest of the buffer with (search-forward "\n\^_"), and it's a pity
+;; to have the overhead of this special test inside the loop.
+
+;; This function changes match-data, but supposedly the caller might
+;; want to use the results of re-search-backward.
+
+;; The return value is the value of point at the beginning of matching
+;; REGERXP, if the function succeeds, nil otherwise.
+(defun Info-node-at-bob-matching (regexp)
+  (and (bobp)                  ; are we at beginning of buffer?
+       (looking-at "\^_")      ; does it begin with node delimiter?
+       (let (beg)
+        (forward-line 1)
+        (setq beg (point))
+        (forward-line 1)       ; does the line after delimiter match REGEXP?
+        (re-search-backward regexp beg t))))
 
 ;; Go to an info node specified as separate filename and nodename.
 ;; no-going-back is non-nil if recovering from an error in this function;
 ;; it says do not attempt further (recursive) error recovery.
 (defun Info-find-node (filename nodename &optional no-going-back)
+  (info-initialize)
   ;; Convert filename to lower case if not found as specified.
   ;; Expand it.
-  (if filename
+  (if (stringp filename)
       (let (temp temp-downcase found)
         (setq filename (substitute-in-file-name filename))
         (if (string= (downcase filename) "dir")
@@ -374,13 +411,31 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                   Info-history)))
   ;; Go into info buffer.
   (or (eq major-mode 'Info-mode) (pop-to-buffer "*info*"))
+  (Info-find-node-2 filename nodename no-going-back))
+
+(defun Info-on-current-buffer (&optional nodename)
+  "Use the `Info-mode' to browse the current info buffer.
+If a prefix arg is provided, it queries for the NODENAME which
+else defaults to `Top'."
+  (interactive
+   (list (if current-prefix-arg
+            (completing-read "Node name: " (Info-build-node-completions)
+                             nil t "Top")
+          "Top")))
+  (Info-mode)
+  (set (make-local-variable 'Info-current-file) t)
+  (Info-find-node-2 nil nodename))
+
+(defun Info-find-node-2 (filename nodename &optional no-going-back)
   (buffer-disable-undo (current-buffer))
   (or (eq major-mode 'Info-mode)
       (Info-mode))
   (widen)
   (setq Info-current-node nil)
   (unwind-protect
-      (progn
+      ;; Bind case-fold-search in case the user sets it to nil.
+      (let ((case-fold-search t)
+           anchorpos)
         ;; Switch files if necessary
         (or (null filename)
             (equal Info-current-file filename)
@@ -445,28 +500,37 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
           ;; *Or* the same, but in an indirect subfile.
 
           ;; Search file for a suitable node.
-          (let ((guesspos (point-min))
-                (regexp
-                 (concat "\\(Node:\\|Ref:\\) *"
-                         (regexp-quote nodename)
-                         " *[,\t\n\177]")))
+         (let ((guesspos (point-min))
+               (regexp
+                (concat "\\(Node:\\|Ref:\\) *\\("
+                        (regexp-quote nodename)
+                        "\\) *[,\t\n\177]"))
+               (nodepos nil))
 
             ;; First, search a tag table, if any
             (if (marker-position Info-tag-table-marker)
-
-                (let (found-in-tag-table
-                      found-mode
-                      (m Info-tag-table-marker))
+               (let ((found-in-tag-table t)
+                     found-anchor
+                     found-mode
+                     (m Info-tag-table-marker))
                   (save-excursion
                     (set-buffer (marker-buffer m))
                     (goto-char m)
                     (beginning-of-line) ; so re-search will work.
 
                     ;; Search tag table
-                    (setq found-in-tag-table
-                          (re-search-forward regexp nil t))
+                   (catch 'foo
+                     (while (re-search-forward regexp nil t)
+                       (setq found-anchor
+                             (string-equal "Ref:" (match-string 1)))
+                       (or nodepos (setq nodepos (point))
+                       (if (string-equal (match-string 2) nodename)
+                           (throw 'foo t))))
+                     (if nodepos
+                         (goto-char nodepos)
+                       (setq found-in-tag-table nil)))
                     (if found-in-tag-table
-                        (setq guesspos (read (current-buffer))))
+                        (setq guesspos (1+ (read (current-buffer)))))
                     (setq found-mode major-mode))
 
                   ;; Indirect file among split files
@@ -483,29 +547,66 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                             (setq guesspos (Info-read-subfile guesspos)))))
 
                   ;; Handle anchor
-                  (if (and found-in-tag-table
-                           (string-equal "Ref:" (match-string 1)))
-                      (goto-char guesspos)
+                  (if found-anchor
+                     (goto-char (setq anchorpos guesspos))
 
                     ;; Else we may have a node, which we search for:
-                    (goto-char (max (point-min) (- guesspos 1000)))
+                   (let ((guesschar
+                          (or (byte-to-position guesspos)
+                              (if (< (position-bytes (point-max)) guesspos)
+                                  (point-max)
+                                (point-min)))))
+                     (goto-char (max (point-min)
+                                     (- guesschar 1000))))
                     ;; Now search from our advised position
                     ;; (or from beg of buffer)
                     ;; to find the actual node.
-                    (catch 'foo
-                      (while (search-forward "\n\^_" nil t)
-                        (forward-line 1)
-                        (let ((beg (point)))
-                          (forward-line 1)
-                          (if (re-search-backward regexp beg t)
-                              (progn
-                                (beginning-of-line)
-                                (throw 'foo t)))))
-                      (error
-                       "No such anchor in tag table or node in tag table or file: %s"
-                       nodename))))))
-
-          (Info-select-node)))
+                   ;; First, check whether the node is right
+                   ;; where we are, in case the buffer begins
+                   ;; with a node.
+                   (setq nodepos nil)
+                   (or (Info-node-at-bob-matching regexp)
+                       (catch 'foo
+                         (while (search-forward "\n\^_" nil t)
+                           (forward-line 1)
+                           (let ((beg (point)))
+                             (forward-line 1)
+                             (if (re-search-backward regexp beg t)
+                                 (if (string-equal (match-string 2) nodename)
+                                     (progn
+                                       (beginning-of-line)
+                                       (throw 'foo t))
+                                   (or nodepos
+                                     (setq nodepos (point)))))))
+                         (if nodepos
+                             (progn
+                               (goto-char nodepos)
+                               (beginning-of-line))
+                           (error
+                            "No such anchor in tag table or node in tag table or file: %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.
+             ;; First, check whether the node is right where we are, in case
+             ;; the buffer begins with a node.
+             (setq nodepos nil)
+             (or (Info-node-at-bob-matching regexp)
+                 (catch 'foo
+                   (while (search-forward "\n\^_" nil t)
+                     (forward-line 1)
+                     (let ((beg (point)))
+                       (forward-line 1)
+                       (if (re-search-backward regexp beg t)
+                           (if (string-equal (match-string 2) nodename)
+                               (throw 'foo t)
+                             (or nodepos
+                                 (setq nodepos (point)))))))
+                   (if nodepos
+                       (goto-char nodepos)
+                     (error "No such node: %s" nodename))))))
+          (Info-select-node)
+         (goto-char (or anchorpos (point-min)))))
     ;; If we did not finish finding the specified node,
     ;; go back to the previous one.
     (or Info-current-node no-going-back (null Info-history)
@@ -526,6 +627,8 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
 ;; constructed Info-dir-contents.
 (defvar Info-dir-file-attributes nil)
 
+(defvar Info-dir-file-name nil)
+
 ;; Construct the Info directory node by merging the files named `dir'
 ;; from various directories.  Set the *info* buffer's
 ;; default-directory to the first directory we actually get any text
@@ -536,14 +639,23 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
           ;; since we used it.
           (eval (cons 'and
                       (mapcar '(lambda (elt)
-                                 (let ((curr (file-attributes (car elt))))
+                                 (let ((curr (file-attributes 
+                                              ;; Handle symlinks
+                                              (file-truename (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)
+      (progn
+       (insert Info-dir-contents)
+       (goto-char (point-min)))
     (let ((dirs Info-directory-list)
+         ;; Bind this in case the user sets it to nil.
+         (case-fold-search t)
+         ;; This is set non-nil if we find a problem in some input files.
+         problems
          buffers buffer others nodes dirs-done)
 
       (setq Info-dir-file-attributes nil)
@@ -575,11 +687,16 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                      (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))))))
+                     (condition-case nil
+                         (progn
+                           (insert-file-contents file)
+                           (make-local-variable 'Info-dir-file-name)
+                           (setq Info-dir-file-name file)
+                           (setq buffers (cons (current-buffer) buffers)
+                                 Info-dir-file-attributes
+                                 (cons (cons file attrs)
+                                       Info-dir-file-attributes)))
+                       (error (kill-buffer (current-buffer))))))))
          (or (cdr dirs) (setq Info-dir-contents-directory
                               (file-name-as-directory (car dirs))))
          (setq dirs (cdr dirs))))
@@ -588,9 +705,10 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
          (error "Can't find the Info directory node"))
       ;; Distinguish the dir file that comes with Emacs from all the
       ;; others.  Yes, that is really what this is supposed to do.
-      ;; If it doesn't work, fix it.
-      (setq buffer (car buffers)
-           others (cdr buffers))
+      ;; The definition of `Info-directory-list' puts it first on that
+      ;; list and so last in `buffers' at this point.
+      (setq buffer (car (last buffers))
+           others (delq buffer buffers))
 
       ;; Insert the entire original dir file as a start; note that we've
       ;; already saved its default directory to use as the default
@@ -599,7 +717,8 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
 
       ;; Look at each of the other buffers one by one.
       (while others
-       (let ((other (car others)))
+       (let ((other (car others))
+             this-buffer-nodes)
          ;; In each, find all the menus.
          (save-excursion
            (set-buffer other)
@@ -609,13 +728,21 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
              (let (beg nodename end)
                (forward-line 1)
                (setq beg (point))
-               (search-backward "\n\^_")
+               (or (search-backward "\n\^_" nil 'move)
+                   (looking-at "\^_")
+                   (signal 'search-failed (list "\n\^_")))
                (search-forward "Node: ")
                (setq nodename (Info-following-node-name))
                (search-forward "\n\^_" nil 'move)
                (beginning-of-line)
                (setq end (point))
-               (setq nodes (cons (list nodename other beg end) nodes))))))
+               (setq this-buffer-nodes
+                     (cons (list nodename other beg end)
+                           this-buffer-nodes))))
+           (if (assoc-ignore-case "top" this-buffer-nodes)
+               (setq nodes (nconc this-buffer-nodes nodes))
+             (setq problems t)
+             (message "No `top' node in %s" Info-dir-file-name))))
        (setq others (cdr others)))
       ;; Add to the main menu a menu item for each other node.
       (re-search-forward "^\\* Menu:")
@@ -642,7 +769,7 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
        (let ((nodename (car (car nodes))))
          (goto-char (point-min))
          ;; Find the like-named node in the main buffer.
-         (if (re-search-forward (concat "\n\^_.*\n.*Node: "
+         (if (re-search-forward (concat "^\^_.*\n.*Node: "
                                         (regexp-quote nodename)
                                         "[,\n\t]")
                                 nil t)
@@ -661,7 +788,10 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
       (while buffers
        (kill-buffer (car buffers))
        (setq buffers (cdr buffers)))
-      (message "Composing main Info directory...done"))
+      (goto-char (point-min))
+      (if problems
+         (message "Composing main Info directory...problems encountered, see `*Messages*'")
+       (message "Composing main Info directory...done")))
     (setq Info-dir-contents (buffer-string)))
   (setq default-directory Info-dir-contents-directory))
 
@@ -676,7 +806,8 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
        (save-excursion
          (set-buffer (marker-buffer Info-tag-table-marker))
          (goto-char (point-min))
-         (search-forward "\n\^_")
+         (or (looking-at "\^_")
+             (search-forward "\n\^_"))
          (forward-line 2)
          (catch 'foo
            (while (not (looking-at "\^_"))
@@ -707,47 +838,56 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
          (set-buffer-modified-p nil)
          (setq Info-current-subfile lastfilename)))
     (goto-char (point-min))
-    (search-forward "\n\^_")
+    (if (looking-at "\^_")
+       (forward-char 1)
+      (search-forward "\n\^_"))
     (if (numberp nodepos)
        (+ (- nodepos lastfilepos) (point)))))
 
 ;; Select the info node that point is in.
 (defun Info-select-node ()
-  (save-excursion
-   ;; Find beginning of node.
-   (search-backward "\n\^_")
-   (forward-line 2)
-   ;; Get nodename spelled as it is in the node.
-   (re-search-forward "Node:[ \t]*")
-   (setq Info-current-node
-        (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)
-   (let (active-expression)
-     (narrow-to-region (point)
-                      (if (re-search-forward "\n[\^_\f]" nil t)
-                          (prog1
-                           (1- (point))
-                           (if (looking-at "[\n\^_\f]*execute: ")
-                               (progn
-                                 (goto-char (match-end 0))
-                                 (setq active-expression
-                                       (read (current-buffer))))))
-                        (point-max)))
-     (if Info-enable-active-nodes (eval active-expression))
-     (if Info-fontify (Info-fontify-node))
-     (run-hooks 'Info-selection-hook))))
+  ;; Bind this in case the user sets it to nil.
+  (let ((case-fold-search t))
+    (save-excursion
+     ;; Find beginning of node.
+     (if (search-backward "\n\^_" nil 'move)
+        (forward-line 2)
+       (if (looking-at "\^_")
+          (forward-line 1)
+        (signal 'search-failed (list "\n\^_"))))
+     ;; Get nodename spelled as it is in the node.
+     (re-search-forward "Node:[ \t]*")
+     (setq Info-current-node
+          (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)
+     (let (active-expression)
+       (narrow-to-region (point)
+                        (if (re-search-forward "\n[\^_\f]" nil t)
+                            (prog1
+                             (1- (point))
+                             (if (looking-at "[\n\^_\f]*execute: ")
+                                 (progn
+                                   (goto-char (match-end 0))
+                                   (setq active-expression
+                                         (read (current-buffer))))))
+                          (point-max)))
+       (if Info-enable-active-nodes (eval active-expression))
+       (if Info-fontify (Info-fontify-node))
+       (run-hooks 'Info-selection-hook)))))
 
 (defun Info-set-mode-line ()
   (setq mode-line-buffer-identification
        (concat
         "  Info:  ("
         (if Info-current-file
-            (file-name-nondirectory Info-current-file)
+            (file-name-nondirectory (if (stringp Info-current-file)
+                                        Info-current-file
+                                      (or buffer-file-name "")))
           "")
         ")"
         (or Info-current-node ""))))
@@ -755,9 +895,15 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
 ;; Go to an info node specified with a filename-and-nodename string
 ;; of the sort that is found in pointers in nodes.
 
-(defun Info-goto-node (nodename)
-  "Go to info node named NAME.  Give just NODENAME or (FILENAME)NODENAME."
-  (interactive (list (Info-read-node-name "Goto node: ")))
+(defun Info-goto-node (nodename &optional fork)
+  "Go to info node named NAME.  Give just NODENAME or (FILENAME)NODENAME.
+If FORK is non-nil, show the node in a new info buffer.
+If FORK is a string, it is the name to use for the new buffer."
+  (interactive (list (Info-read-node-name "Goto node: ") current-prefix-arg))
+  (info-initialize)
+  (if fork
+    (set-buffer
+     (clone-buffer (concat "*info-" (if (stringp fork) fork nodename) "*") t)))
   (let (filename)
     (string-match "\\s *\\((\\s *\\([^\t)]*\\)\\s *)\\s *\\|\\)\\(.*\\)"
                  nodename)
@@ -773,27 +919,29 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
     (Info-find-node (if (equal filename "") nil filename)
                    (if (equal nodename "") "Top" nodename))))
 
+(defvar Info-read-node-completion-table)
+
 ;; This function is used as the "completion table" while reading a node name.
-;; It does completion using the alist in completion-table
+;; It does completion using the alist in Info-read-node-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)))
+            (try-completion string Info-read-node-completion-table predicate)))
          ((eq code t)
           (if no-completion
               nil
-            (all-completions string completion-table predicate)))
+            (all-completions string Info-read-node-completion-table predicate)))
          ((eq code 'lambda)
           (if no-completion
               t
-            (assoc string completion-table))))))
+            (assoc string Info-read-node-completion-table))))))
 
 (defun Info-read-node-name (prompt &optional default)
   (let* ((completion-ignore-case t)
-        (completion-table (Info-build-node-completions))
+        (Info-read-node-completion-table (Info-build-node-completions))
         (nodename (completing-read prompt 'Info-read-node-name-1 nil t)))
     (if (equal nodename "")
        (or default
@@ -802,7 +950,10 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
 
 (defun Info-build-node-completions ()
   (or Info-current-file-completions
-      (let ((compl nil))
+      (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
            (if (marker-buffer Info-tag-table-marker)
@@ -817,16 +968,21 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
                                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 (buffer-substring (match-beginning 1)
+                                                     (match-end 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: *\\([^,\n]*\\) *[,\n\t]"
-                                         beg t)
+                 (if (re-search-backward node-regexp beg t)
                      (setq compl 
                            (cons (list (buffer-substring (match-beginning 1)
                                                          (match-end 1)))
                                  compl))))))))
+       (setq compl (cons '("*") compl))
        (setq Info-current-file-completions compl))))
 \f
 (defun Info-restore-point (hl)
@@ -850,60 +1006,61 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
   (if (equal regexp "")
       (setq regexp Info-last-search)
     (setq Info-last-search regexp))
-  (let ((found ()) current
-       (onode Info-current-node)
-       (ofile Info-current-file)
-       (opoint (point))
-       (ostart (window-start))
-       (osubfile Info-current-subfile))
-    (save-excursion
-      (save-restriction
-       (widen)
-       (if (null Info-current-subfile)
-           (progn (re-search-forward regexp) (setq found (point)))
-         (condition-case err
+  (when regexp
+    (let ((found ()) current
+         (onode Info-current-node)
+         (ofile Info-current-file)
+         (opoint (point))
+         (ostart (window-start))
+         (osubfile Info-current-subfile))
+      (save-excursion
+       (save-restriction
+         (widen)
+         (if (null Info-current-subfile)
              (progn (re-search-forward regexp) (setq found (point)))
-           (search-failed nil)))))
-    (if (not found) ;can only happen in subfile case -- else would have erred
-       (unwind-protect
-           (let ((list ()))
-             (save-excursion
-               (set-buffer (marker-buffer Info-tag-table-marker))
-               (goto-char (point-min))
-               (search-forward "\n\^_\nIndirect:")
-               (save-restriction
-                 (narrow-to-region (point)
-                                   (progn (search-forward "\n\^_")
-                                          (1- (point))))
+           (condition-case err
+               (progn (re-search-forward regexp) (setq found (point)))
+             (search-failed nil)))))
+      (if (not found) ;can only happen in subfile case -- else would have erred
+         (unwind-protect
+             (let ((list ()))
+               (save-excursion
+                 (set-buffer (marker-buffer Info-tag-table-marker))
                  (goto-char (point-min))
-                 (search-forward (concat "\n" osubfile ": "))
-                 (beginning-of-line)
-                 (while (not (eobp))
-                   (re-search-forward "\\(^.*\\): [0-9]+$")
-                   (goto-char (+ (match-end 1) 2))
-                   (setq list (cons (cons (read (current-buffer))
-                                          (buffer-substring
-                                           (match-beginning 1) (match-end 1)))
-                                    list))
-                   (goto-char (1+ (match-end 0))))
-                 (setq list (nreverse list)
-                       current (car (car list))
-                       list (cdr list))))
-             (while list
-               (message "Searching subfile %s..." (cdr (car list)))
-               (Info-read-subfile (car (car list)))
-               (setq list (cdr list))
-;;             (goto-char (point-min))
-               (if (re-search-forward regexp nil t)
-                   (setq found (point) list ())))
-             (if found
-                 (message "")
-               (signal 'search-failed (list regexp))))
-         (if (not found)
-             (progn (Info-read-subfile osubfile)
-                    (goto-char opoint)
-                    (Info-select-node)
-                    (set-window-start (selected-window) ostart)))))
+                 (search-forward "\n\^_\nIndirect:")
+                 (save-restriction
+                   (narrow-to-region (point)
+                                     (progn (search-forward "\n\^_")
+                                            (1- (point))))
+                   (goto-char (point-min))
+                   (search-forward (concat "\n" osubfile ": "))
+                   (beginning-of-line)
+                   (while (not (eobp))
+                     (re-search-forward "\\(^.*\\): [0-9]+$")
+                     (goto-char (+ (match-end 1) 2))
+                     (setq list (cons (cons (read (current-buffer))
+                                            (buffer-substring
+                                             (match-beginning 1) (match-end 1)))
+                                      list))
+                     (goto-char (1+ (match-end 0))))
+                   (setq list (nreverse list)
+                         current (car (car list))
+                         list (cdr list))))
+               (while list
+                 (message "Searching subfile %s..." (cdr (car list)))
+                 (Info-read-subfile (car (car list)))
+                 (setq list (cdr list))
+;;;            (goto-char (point-min))
+                 (if (re-search-forward regexp nil t)
+                     (setq found (point) list ())))
+               (if found
+                   (message "")
+                 (signal 'search-failed (list regexp))))
+           (if (not found)
+               (progn (Info-read-subfile osubfile)
+                      (goto-char opoint)
+                      (Info-select-node)
+                      (set-window-start (selected-window) ostart)))))
     (widen)
     (goto-char found)
     (Info-select-node)
@@ -911,22 +1068,24 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
     (or (and (string-equal onode Info-current-node)
             (equal ofile Info-current-file))
        (setq Info-history (cons (list ofile onode opoint)
-                                Info-history)))))
+                                Info-history))))))
 \f
 ;; Extract the value of the node-pointer named NAME.
 ;; If there is none, use ERRORNAME in the error message; 
 ;; if ERRORNAME is nil, just return nil.
 (defun Info-extract-pointer (name &optional errorname)
-  (save-excursion
-   (goto-char (point-min))
-   (forward-line 1)
-   (if (re-search-backward (concat name ":") nil t)
-       (progn
-        (goto-char (match-end 0))
-        (Info-following-node-name))
-     (if (eq errorname t)
-        nil
-       (error "Node has no %s" (capitalize (or errorname name)))))))
+  ;; Bind this in case the user sets it to nil.
+  (let ((case-fold-search t))
+    (save-excursion
+      (goto-char (point-min))
+      (forward-line 1)
+      (if (re-search-backward (concat name ":") nil t)
+         (progn
+           (goto-char (match-end 0))
+           (Info-following-node-name))
+       (if (eq errorname t)
+           nil
+         (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
@@ -958,7 +1117,7 @@ In standalone mode, \\<Info-mode-map>\\[Info-exit] exits Emacs itself."
 If SAME-FILE is non-nil, do not move to a different Info file."
   (interactive)
   (let ((node (Info-extract-pointer "up")))
-    (and same-file
+    (and (or same-file (not (stringp Info-current-file)))
         (string-match "^(" node)
         (error "Up node is in another Info file"))
     (Info-goto-node node))
@@ -988,6 +1147,7 @@ If SAME-FILE is non-nil, do not move to a different Info file."
 NAME may be an abbreviation of the reference name."
   (interactive
    (let ((completion-ignore-case t)
+        (case-fold-search t)
         completions default alt-default (start-point (point)) str i bol eol)
      (save-excursion
        ;; Store end and beginning of line.
@@ -1046,7 +1206,12 @@ 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 " (regexp-quote footnotename))))
+
+  (unless footnotename
+    (error "No reference was specified"))
+
+  (let (target beg i (str (concat "\\*note " (regexp-quote footnotename)))
+              (case-fold-search t))
     (while (setq i (string-match " " str i))
       (setq str (concat (substring str 0 i) "[ \t\n]+" (substring str (1+ i))))
       (setq i (+ i 6)))
@@ -1087,6 +1252,8 @@ NAME may be an abbreviation of the reference name."
 ;;    (Info-menu (car list))
 ;;    (setq list (cdr list))))
 
+(defvar Info-complete-menu-buffer)
+
 (defun Info-complete-menu-item (string predicate action)
   (let ((case-fold-search t))
     (cond ((eq action nil)
@@ -1134,7 +1301,7 @@ NAME may be an abbreviation of the reference name."
                                nil t))))))
 
 
-(defun Info-menu (menu-item)
+(defun Info-menu (menu-item &optional fork)
   "Go to node for menu item named (or abbreviated) NAME.
 Completion is allowed, and the menu item point is on is the default."
   (interactive
@@ -1153,10 +1320,10 @@ Completion is allowed, and the menu item point is on is the default."
            (save-excursion
              (goto-char p)
              (end-of-line)
-             (re-search-backward "\n\\* +\\([^:\t\n]*\\):" beg t)
-             (setq default (format "%s" (buffer-substring
-                                         (match-beginning 1)
-                                         (match-end 1)))))))
+             (if (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)
@@ -1174,36 +1341,38 @@ Completion is allowed, and the menu item point is on is the default."
                 (setq item default)
                 ;; ask again
                 (setq item nil))))
-       (list item))))
+       (list item current-prefix-arg))))
   ;; there is a problem here in that if several menu items have the same
   ;; name you can only go to the node of the first with this command.
-  (Info-goto-node (Info-extract-menu-item menu-item)))
+  (Info-goto-node (Info-extract-menu-item menu-item) (if fork menu-item)))
   
 (defun Info-extract-menu-item (menu-item)
   (setq menu-item (regexp-quote menu-item))
-  (save-excursion
-    (goto-char (point-min))
-    (or (search-forward "\n* menu:" nil t)
-       (error "No menu in this node"))
-    (or (re-search-forward (concat "\n\\* +" menu-item ":") nil t)
-       (re-search-forward (concat "\n\\* +" menu-item) nil t)
-       (error "No such item in menu"))
-    (beginning-of-line)
-    (forward-char 2)
-    (Info-extract-menu-node-name)))
+  (let ((case-fold-search t))
+    (save-excursion
+      (goto-char (point-min))
+      (or (search-forward "\n* menu:" nil t)
+         (error "No menu in this node"))
+      (or (re-search-forward (concat "\n\\* +" menu-item ":") nil t)
+         (re-search-forward (concat "\n\\* +" menu-item) nil t)
+         (error "No such item in menu"))
+      (beginning-of-line)
+      (forward-char 2)
+      (Info-extract-menu-node-name))))
 
 ;; If COUNT is nil, use the last item in the menu.
 (defun Info-extract-menu-counting (count)
-  (save-excursion
-    (goto-char (point-min))
-    (or (search-forward "\n* menu:" nil t)
-       (error "No menu in this node"))
-    (if count
-       (or (search-forward "\n* " nil t count)
-           (error "Too few items in menu"))
-      (while (search-forward "\n* " nil t)
-       nil))
-    (Info-extract-menu-node-name)))
+  (let ((case-fold-search t))
+    (save-excursion
+      (goto-char (point-min))
+      (or (search-forward "\n* menu:" nil t)
+         (error "No menu in this node"))
+      (if count
+         (or (search-forward "\n* " nil t count)
+             (error "Too few items in menu"))
+       (while (search-forward "\n* " nil t)
+         nil))
+      (Info-extract-menu-node-name))))
 
 (defun Info-nth-menu-item ()
   "Go to the node of the Nth menu item.
@@ -1296,12 +1465,14 @@ N is the digit argument used to invoke this command."
 
 (defun Info-next-menu-item ()
   (interactive)
-  (save-excursion
-    (forward-line -1)
-    (search-forward "\n* menu:" nil t)
-    (or (search-forward "\n* " nil t)
-       (error "No more items in menu"))
-    (Info-goto-node (Info-extract-menu-node-name))))
+  (let ((node
+        (save-excursion
+          (forward-line -1)
+          (search-forward "\n* menu:" nil t)
+          (and (search-forward "\n* " nil t)
+               (Info-extract-menu-node-name)))))
+    (if node (Info-goto-node node)
+      (error "No more items in menu"))))
 
 (defun Info-last-menu-item ()
   (interactive)
@@ -1415,7 +1586,8 @@ previous node or back up to the parent node."
   "Move cursor to the next cross-reference or menu item in the node."
   (interactive)
   (let ((pat "\\*note[ \n\t]*\\([^:]*\\):\\|^\\* .*:")
-       (old-pt (point)))
+       (old-pt (point))
+       (case-fold-search t))
     (or (eobp) (forward-char 1))
     (or (re-search-forward pat nil t)
        (progn
@@ -1434,7 +1606,8 @@ previous node or back up to the parent node."
   "Move cursor to the previous cross-reference or menu item in the node."
   (interactive)
   (let ((pat "\\*note[ \n\t]*\\([^:]*\\):\\|^\\* .*:")
-       (old-pt (point)))
+       (old-pt (point))
+       (case-fold-search t))
     (or (re-search-backward pat nil t)
        (progn
          (goto-char (point-max))
@@ -1462,7 +1635,8 @@ Give a blank topic name to go to the Index node itself."
        (rnode nil)
        (pattern (format "\n\\* +\\([^\n:]*%s[^\n:]*\\):[ \t]*\\([^.\n]*\\)\\.[ \t]*\\([0-9]*\\)"
                         (regexp-quote topic)))
-       node)
+       node
+       (case-fold-search t))
     (Info-goto-node "Top")
     (or (search-forward "\n* menu:" nil t)
        (error "No index"))
@@ -1537,17 +1711,18 @@ Give a blank topic name to go to the Index node itself."
 
 (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))))
+  (let ((case-fold-search t))
+    (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."
@@ -1596,32 +1771,33 @@ SIG optional fourth argument, controls action on no match
     nil: return nil
     t: beep
     a string: signal an error, using that string."
-  (save-excursion
-    (goto-char pos)
-    ;; First look for a match for START that goes across POS.
-    (while (and (not (bobp)) (> (point) (- pos (length start)))
-               (not (looking-at start)))
-      (forward-char -1))
-    ;; If we did not find one, search back for START
-    ;; (this finds only matches that end at or before POS).
-    (or (looking-at start)
-       (progn
-         (goto-char pos)
-         (re-search-backward start (max (point-min) (- pos 200)) 'yes)))
-    (let (found)
-      (while (and (re-search-forward all (min (point-max) (+ pos 200)) 'yes)
-                 (not (setq found (and (<= (match-beginning 0) pos)
-                                       (> (match-end 0) pos))))))
-      (if (and found (<= (match-beginning 0) pos)
-              (> (match-end 0) pos))
-         (buffer-substring (match-beginning 1) (match-end 1))
-       (cond ((null errorstring)
-              nil)
-             ((eq errorstring t)
-              (beep)
-              nil)
-             (t
-              (error "No %s around position %d" errorstring pos)))))))
+  (let ((case-fold-search t))
+    (save-excursion
+      (goto-char pos)
+      ;; First look for a match for START that goes across POS.
+      (while (and (not (bobp)) (> (point) (- pos (length start)))
+                 (not (looking-at start)))
+       (forward-char -1))
+      ;; If we did not find one, search back for START
+      ;; (this finds only matches that end at or before POS).
+      (or (looking-at start)
+         (progn
+           (goto-char pos)
+           (re-search-backward start (max (point-min) (- pos 200)) 'yes)))
+      (let (found)
+       (while (and (re-search-forward all (min (point-max) (+ pos 200)) 'yes)
+                   (not (setq found (and (<= (match-beginning 0) pos)
+                                         (> (match-end 0) pos))))))
+       (if (and found (<= (match-beginning 0) pos)
+                (> (match-end 0) pos))
+           (buffer-substring (match-beginning 1) (match-end 1))
+         (cond ((null errorstring)
+                nil)
+               ((eq errorstring t)
+                (beep)
+                nil)
+               (t
+                (error "No %s around position %d" errorstring pos))))))))
 
 (defun Info-mouse-follow-nearest-node (click)
   "\\<Info-mode-map>Follow a node reference near point.
@@ -1655,8 +1831,11 @@ If no reference to follow, moves to the next node, or up if none."
       (Info-follow-reference node))
      ((setq node (Info-get-token (point) "\\* +" "\\* +\\([^:]*\\)::"))
       (Info-goto-node node))
-     ((setq node (Info-get-token (point) "\\* +" "\\* +\\([^:]*\\):"))
-      (Info-menu node))
+     ((Info-get-token (point) "\\* +" "\\* +\\([^:]*\\):")
+      (beginning-of-line)
+      (forward-char 2)
+      (setq node (Info-extract-menu-node-name))
+      (Info-goto-node node))
      ((setq node (Info-get-token (point) "Up: " "Up: \\([^,\n\t]*\\)"))
       (Info-goto-node node))
      ((setq node (Info-get-token (point) "Next: " "Next: \\([^,\n\t]*\\)"))
@@ -1708,6 +1887,7 @@ If no reference to follow, moves to the next node, or up if none."
   (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 "\M-n" 'clone-buffer)
   (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)
@@ -1769,7 +1949,8 @@ If no reference to follow, moves to the next node, or up if none."
        ;; Update reference menu.  Code stolen from `Info-follow-reference'.
        (let ((items nil)
              str i entries current 
-             (number 0))
+             (number 0)
+             (case-fold-search t))
          (save-excursion
            (goto-char (point-min))
            (while (re-search-forward "\\*note[ \n\t]*\\([^:]*\\):" nil t)
@@ -1882,9 +2063,20 @@ Advanced commands:
   ;; This is for the sake of the invisible text we use handling titles.
   (make-local-variable 'line-move-ignore-invisible)
   (setq line-move-ignore-invisible t)
+  (add-hook (make-local-hook 'clone-buffer-hook) 'Info-clone-buffer-hook nil t)
   (Info-set-mode-line)
   (run-hooks 'Info-mode-hook))
 
+(defun Info-clone-buffer-hook ()
+  (when (bufferp Info-tag-table-buffer)
+    (setq Info-tag-table-buffer
+         (with-current-buffer Info-tag-table-buffer (clone-buffer)))
+    (let ((m Info-tag-table-marker))
+      (when (and (markerp m) (marker-position m))
+       (setq Info-tag-table-marker
+             (with-current-buffer Info-tag-table-buffer
+               (copy-marker (marker-position m))))))))
+
 (defvar Info-edit-map nil
   "Local keymap used within `e' command of Info.")
 (if Info-edit-map
@@ -1937,7 +2129,8 @@ Allowed only if variable `Info-enable-edit' is non-nil."
        (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")
+  '("ediff" "forms" "gnus" "info" ("mh" . "mh-e") "sc" "message"
+    ("dired" . "dired-x") ("c" . "ccmode") "viper")
   "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.
@@ -2052,7 +2245,8 @@ The alist key is the character the title is underlined with (?*, ?= or ?-)."
 
 (defun Info-fontify-node ()
   (save-excursion
-    (let ((buffer-read-only nil))
+    (let ((buffer-read-only nil)
+         (case-fold-search t))
       (goto-char (point-min))
       (when (looking-at "^File: [^,: \t]+,?[ \t]+")
        (goto-char (match-end 0))
@@ -2119,79 +2313,183 @@ The alist key is the character the title is underlined with (?*, ?= or ?-)."
 ;;; Speedbar support:
 ;; These functions permit speedbar to display the "tags" in the
 ;; current info node.
+(eval-when-compile (require 'speedbar))
 
-(eval-when-compile (require 'speedbspec))
+(defvar Info-speedbar-key-map nil
+  "Keymap used when in the info display mode.")
 
-(defvar Info-last-speedbar-node nil
-  "Last node viewed with speedbar in the form '(NODE FILE).")
+(defun Info-install-speedbar-variables ()
+  "Install those variables used by speedbar to enhance Info."
+  (if Info-speedbar-key-map
+      nil
+    (setq Info-speedbar-key-map (speedbar-make-specialized-keymap))
+
+    ;; Basic tree features
+    (define-key Info-speedbar-key-map "e" 'speedbar-edit-line)
+    (define-key Info-speedbar-key-map "\C-m" 'speedbar-edit-line)
+    (define-key Info-speedbar-key-map "+" 'speedbar-expand-line)
+    (define-key Info-speedbar-key-map "-" 'speedbar-contract-line)
+    )
+
+  (speedbar-add-expansion-list '("Info" Info-speedbar-menu-items
+                                Info-speedbar-key-map
+                                Info-speedbar-hierarchy-buttons)))
 
 (defvar Info-speedbar-menu-items
-  '(["Browse Item On Line" speedbar-edit-line t])
+  '(["Browse Node" speedbar-edit-line t]
+    ["Expand Node" speedbar-expand-line
+     (save-excursion (beginning-of-line)
+                    (looking-at "[0-9]+: *.\\+. "))]
+    ["Contract Node" speedbar-contract-line
+     (save-excursion (beginning-of-line)
+                    (looking-at "[0-9]+: *.-. "))]
+    )
   "Additional menu-items to add to speedbar frame.")
 
+;; Make sure our special speedbar major mode is loaded
+(if (featurep 'speedbar)
+    (Info-install-speedbar-variables)
+  (add-hook 'speedbar-load-hook 'Info-install-speedbar-variables))
+
+;;; Info hierarchy display method
+;;;###autoload
+(defun Info-speedbar-browser ()
+  "Initialize speedbar to display an info node browser.
+This will add a speedbar major display mode."
+  (interactive)
+  (require 'speedbar)
+  ;; Make sure that speedbar is active
+  (speedbar-frame-mode 1)
+  ;; Now, throw us into Info mode on speedbar.
+  (speedbar-change-initial-expansion-list "Info")
+  )
+
+(defun Info-speedbar-hierarchy-buttons (directory depth &optional node)
+  "Display an Info directory hierarchy in speedbar.
+DIRECTORY is the current directory in the attached frame.
+DEPTH is the current indentation depth.
+NODE is an optional argument that is used to represent the
+specific node to expand."
+  (if (and (not node)
+          (save-excursion (goto-char (point-min))
+                          (let ((case-fold-search t))
+                            (looking-at "Info Nodes:"))))
+      ;; Update our "current node" maybe?
+      nil
+    ;; We cannot use the generic list code, that depends on all leaves
+    ;; being known at creation time.
+    (if (not node)
+       (speedbar-with-writable (insert "Info Nodes:\n")))
+    (let ((completions nil)
+         (cf (selected-frame)))
+      (select-frame speedbar-attached-frame)
+      (save-window-excursion
+       (setq completions
+             (Info-speedbar-fetch-file-nodes (or node '"(dir)top"))))
+      (select-frame cf)
+      (if completions
+         (speedbar-with-writable
+          (while completions
+            (speedbar-make-tag-line 'bracket ?+ 'Info-speedbar-expand-node
+                                    (cdr (car completions))
+                                    (car (car completions))
+                                    'Info-speedbar-goto-node
+                                    (cdr (car completions))
+                                    'info-xref depth)
+            (setq completions (cdr completions)))
+          t)
+       nil))))
+  
+(defun Info-speedbar-goto-node (text node indent)
+  "When user clicks on TEXT, goto an info NODE.
+The INDENT level is ignored."
+    (select-frame speedbar-attached-frame)
+    (let* ((buff (or (get-buffer "*info*")
+                    (progn (info) (get-buffer "*info*"))))
+          (bwin (get-buffer-window buff 0)))
+      (if bwin
+         (progn
+           (select-window bwin)
+           (raise-frame (window-frame bwin)))
+       (if speedbar-power-click
+           (let ((pop-up-frames t)) (select-window (display-buffer buff)))
+         (select-frame speedbar-attached-frame)
+         (switch-to-buffer buff)))
+      (let ((junk (string-match "^(\\([^)]+\\))\\([^.]+\\)$" node))
+           (file (match-string 1 node))
+           (node (match-string 2 node)))
+       (Info-find-node file node)
+       ;; If we do a find-node, and we were in info mode, restore
+       ;; the old default method.  Once we are in info mode, it makes
+       ;; sense to return to whatever method the user was using before.
+       (if (string= speedbar-initial-expansion-list-name "Info")
+           (speedbar-change-initial-expansion-list
+            speedbar-previously-used-expansion-list-name)))))
+
+(defun Info-speedbar-expand-node (text token indent)
+  "Expand the node the user clicked on.
+TEXT is the text of the button we clicked on, a + or - item.
+TOKEN is data related to this node (NAME . FILE).
+INDENT is the current indentation depth."
+  (cond ((string-match "+" text)       ;we have to expand this file
+        (speedbar-change-expand-button-char ?-)
+        (if (speedbar-with-writable
+              (save-excursion
+                (end-of-line) (forward-char 1)
+                (Info-speedbar-hierarchy-buttons nil (1+ indent) token)))
+            (speedbar-change-expand-button-char ?-)
+          (speedbar-change-expand-button-char ??)))
+       ((string-match "-" text)        ;we have to contract this node
+        (speedbar-change-expand-button-char ?+)
+        (speedbar-delete-subblock indent))
+       (t (error "Ooops... not sure what to do")))
+  (speedbar-center-buffer-smartly))
+
+(defun Info-speedbar-fetch-file-nodes (nodespec)
+  "Fetch the subnodes from the info NODESPEC.
+NODESPEC is a string of the form: (file)node.
+Optional THISFILE represends the filename of"
+  (save-excursion
+    ;; Set up a buffer we can use to fake-out Info.
+    (set-buffer (get-buffer-create "*info-browse-tmp*"))
+    (if (not (equal major-mode 'Info-mode))
+       (Info-mode))
+    ;; Get the node into this buffer
+    (let ((junk (string-match "^(\\([^)]+\\))\\([^.]+\\)$" nodespec))
+         (file (match-string 1 nodespec))
+         (node (match-string 2 nodespec)))
+      (Info-find-node file node))
+    ;; Scan the created buffer
+    (goto-char (point-min))
+    (let ((completions nil)
+         (case-fold-search t)
+         (thisfile (progn (string-match "^(\\([^)]+\\))" nodespec)
+                          (match-string 1 nodespec))))
+      ;; Always skip the first one...
+      (re-search-forward "\n\\* \\([^:\t\n]*\\):" nil t)
+      (while (re-search-forward "\n\\* \\([^:\t\n]*\\):" nil t)
+       (let ((name (match-string 1)))
+         (if (looking-at " *\\(([^)]+)[^.\n]+\\)\\.")
+             (setq name (cons name (match-string 1)))
+           (if (looking-at " *\\(([^)]+)\\)\\.")
+               (setq name (cons name (concat (match-string 1) "Top")))
+             (if (looking-at " \\([^.]+\\).")
+                 (setq name
+                       (cons name (concat "(" thisfile ")" (match-string 1))))
+               (setq name (cons name (concat "(" thisfile ")" name))))))
+         (setq completions (cons name completions))))
+      (nreverse completions))))
+
+;;; Info mode node listing
 (defun Info-speedbar-buttons (buffer)
   "Create a speedbar display to help navigation in an Info file.
 BUFFER is the buffer speedbar is requesting buttons for."
-  (goto-char (point-min))
-  (if (and (looking-at "<Directory>")
-          (save-excursion
-            (set-buffer buffer)
-            (and (equal (car Info-last-speedbar-node) Info-current-node)
-                 (equal (cdr Info-last-speedbar-node) Info-current-file))))
-      nil
-    (erase-buffer)
-    (speedbar-insert-button "<Directory>" 'info-xref 'highlight
-                           'Info-speedbar-button
-                           'Info-directory)
-    (speedbar-insert-button "<Top>" 'info-xref 'highlight
-                           'Info-speedbar-button
-                           'Info-top-node)
-    (speedbar-insert-button "<Last>" 'info-xref 'highlight
-                           'Info-speedbar-button
-                           'Info-last)
-    (speedbar-insert-button "<Up>" 'info-xref 'highlight
-                           'Info-speedbar-button
-                           'Info-up)
-    (speedbar-insert-button "<Next>" 'info-xref 'highlight
-                           'Info-speedbar-button
-                           'Info-next)
-    (speedbar-insert-button "<Prev>" 'info-xref 'highlight
-                           'Info-speedbar-button
-                           'Info-prev)
-    (let ((completions nil))
-      (save-excursion
-       (set-buffer buffer)
-       (setq Info-last-speedbar-node
-             (cons Info-current-node Info-current-file))
-       (goto-char (point-min))
-       ;; Always skip the first one...
-       (re-search-forward "\n\\* +\\([^:\t\n]*\\):" nil t)
-       (while (re-search-forward "\n\\* +\\([^:\t\n]*\\):" nil t)
-         (setq completions (cons (buffer-substring (match-beginning 1)
-                                                   (match-end 1))
-                                 completions))))
-      (setq completions (nreverse completions))
-      (while completions
-       (speedbar-make-tag-line nil nil nil nil
-                               (car completions) 'Info-speedbar-menu
-                               nil 'info-node 0)
-       (setq completions (cdr completions))))))
-
-(defun Info-speedbar-button (text token indent)
-  "Called when user clicks <Directory> from speedbar.
-TEXT, TOKEN, and INDENT are unused."
-  (speedbar-with-attached-buffer
-   (funcall token)
-   (setq Info-last-speedbar-node nil)
-   (speedbar-update-contents)))
-
-(defun Info-speedbar-menu (text token indent)
-  "Goto the menu node specified in TEXT.
-TOKEN and INDENT are not used."
-  (speedbar-with-attached-buffer
-   (Info-menu text)
-   (setq Info-last-speedbar-node nil)
-   (speedbar-update-contents)))
+  (if (save-excursion (goto-char (point-min))
+                     (let ((case-fold-search t))
+                       (not (looking-at "Info Nodes:"))))
+      (erase-buffer))
+  (Info-speedbar-hierarchy-buttons nil 0)
+  )
 
 (provide 'info)