🚀 go-pugleaf

RetroBBS NetNews Server

Inspired by RockSolid Light RIP Retro Guy

Article View: gnu.emacs.bug
Article #100071

info.el for multiple files

#100071
From: Hallvard B Furuseth
Date: Fri, 26 Jan 1990 11:19
904 lines
33233 bytes
Here is info-mode as a true major mode, making f.ex find-file start
Info in the file's buffer.  Each Info buffer is independent; you may
use any Info command in any Info buffer.  Find-file just provides the
starting file.

I am posting this in 4 articles:
   "info.el for multiple files"	this article
   "informat.el"		Info-validate, tagify etc
   "where-is-file & buffer-offer-save"
				should be included in "standard" emacs
				  files, or appended to info.el.
   "info patches"		Patches some other files (mostly the
				  info/info file).

**  If you have loaded the old Info, type
**   (setq-default Info-tag-table-marker nil)
**  before starting this version.

Extensions/changes:
 - New variables Info-path, info-mode-hook and Info-select-node-hook.
 - Info-find-node attempts looks for FILE.info before FILE.
 - Added Info-create-node to informat (you create a new node by
     entering info-edit-mode and going to a nonexistent node).
 - Info-validate and Info-tagify accept indirect Info files.
 - Info-validate checks for ambiguous menu items/references and a few
     other things.
 - batch-info-validate skips Info subfiles, auto save and backup files,
     and in directory arguments, files starting widh ".".
 - Added the keys  = (reselect),  S (save Info file),  K (kill Info).
 - Extracting a menu item leaves point at that menu item.
 - Info-search skips the headers of subfiles.
 - Fixed some bugs.

Incompatibilities/changes:
 - Renamed Info-mode, Info-edit-mode and the keymaps to lowercase names.
 - Vars as Info-tag-table-marker are local, global value must be nil.
 - New variable Info-subfiles.
 - Tag table buffer is " *tag table (INFO-BUFFER-NAME)*".
 - You should not use the old informat.el with the new info.el, or
     vice verca.

Suggestion:
   Info files should be named whatever.info, and .texinfo files
   should use @setfilename foo.info (not foo or foo-info).


(If you read this program, you will see that a lot of the changes are
unnecessary.  That's because this was my "learning Lisp" project.
Luckily, I got bored of changing things like (if (not XXX) YYY) to
(or XX YYY).  So some of the old code is still there.)


Hallvard Furuseth
hallvard@ifi.uio.no

;; Info mode for Emacs
;; Copyright (C) 1985, 1986 Free Software Foundation, Inc.

;; This file is part of GNU Emacs.

;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY.  No author or distributor
;; accepts responsibility to anyone for the consequences of using it
;; or for whether it serves any particular purpose or works at all,
;; unless he says so in writing.  Refer to the GNU Emacs General Public
;; License for full details.

;; Everyone is granted permission to copy, modify and redistribute
;; GNU Emacs, but only under the conditions described in the
;; GNU Emacs General Public License.   A copy of this license is
;; supposed to have been given to you along with GNU Emacs so you
;; can know your rights and responsibilities.  It should be in a
;; file named COPYING.  Among other things, the copyright notice
;; and this notice must be preserved on all copies.

(provide 'info)

(autoload 'Info-create-node "informat" nil t)
(defvar Info-node-desc nil
  "Suggested (last-file-name Node Prev Up Next) when creating a node.")


(defvar Info-history nil
  "List of info nodes user has visited.
Each element of list is a list (FILENAME NODENAME BUFFERPOS).")

(defvar Info-enable-edit nil
  "Non-nil means the \\[Info-edit] command in Info can edit the current node.")

(defvar Info-enable-active-nodes t
  "Non-nil allows Info to execute Lisp code associated with nodes.
The Lisp code is executed when the node is selected.")

(defvar Info-directory nil
  "Standard directory for Info documentation files.")

(defvar Info-path (list "../info/" Info-directory)
  "*List of directories to search for info files.
Each element is a string (directory name) or nil (try default directory).")

(defvar Info-current-file nil
  "Info file that Info is now looking at, or nil.")

(defvar Info-current-subfile nil
  "Info subfile that is actually in this info-buffer now,
or nil if current info file is not split into subfiles.")

(defvar Info-current-node nil
  "Name of node that Info is now looking at, or nil.")

(defvar Info-tag-table-marker nil
  "Marker pointing at beginning of current Info file's tag table.
Marker points nowhere if file has no tag table.
Variable is nil if Info has not been initialized in this buffer.")

(defvar Info-subfiles nil
  "Assoc list of (subfile . startpos), or 'no-subfiles,
or nil before I have searched for the tag table.")


(defun info ()
  "Enter Info, the documentation browser, in buffer *info*."
  (interactive)
  (or (prog1 (get-buffer "*info*")
	(switch-to-buffer "*info*"))
      (Info-directory)))


(defvar info-mode-map nil
  "Keymap containing Info commands.")
(if info-mode-map
    nil
  (setq info-mode-map (make-keymap))
  (suppress-keymap info-mode-map t)
  (define-key info-mode-map "." 'beginning-of-buffer)
  (define-key info-mode-map " " 'scroll-up)
  (define-key info-mode-map "1" 'Info-nth-menu-item)
  (define-key info-mode-map "2" 'Info-nth-menu-item)
  (define-key info-mode-map "3" 'Info-nth-menu-item)
  (define-key info-mode-map "4" 'Info-nth-menu-item)
  (define-key info-mode-map "5" 'Info-nth-menu-item)
  (define-key info-mode-map "?" 'Info-summary)
  (define-key info-mode-map "=" 'Info-select-node)
  (define-key info-mode-map "K" 'Info-KILL)
  (define-key info-mode-map "S" 'Info-SAVE)
  (define-key info-mode-map "b" 'beginning-of-buffer)
  (define-key info-mode-map "d" 'Info-directory)
  (define-key info-mode-map "e" 'Info-edit)
  (define-key info-mode-map "f" 'Info-follow-reference)
  (define-key info-mode-map "g" 'Info-goto-node)
  (define-key info-mode-map "h" 'Info-help)
  (define-key info-mode-map "l" 'Info-last)
  (define-key info-mode-map "m" 'Info-menu)
  (define-key info-mode-map "n" 'Info-next)
  (define-key info-mode-map "p" 'Info-prev)
  (define-key info-mode-map "q" 'Info-exit)
  (define-key info-mode-map "s" 'Info-search)
  (define-key info-mode-map "u" 'Info-up)
  (define-key info-mode-map "\177" 'scroll-down))

(defun info-mode (&optional any-mode)
  "Info mode provides commands for browsing through Info documentation trees.
Documentation in Info is divided into \"nodes\", each of which
discusses one topic and contains references to other nodes
which discuss related topics.  Info has commands to follow
the references and show you other nodes.

h	Invoke the Info tutorial.	?  Show this summary of commands.

Selecting other nodes:
n or p	Move to the \"next\" or \"previous\" node of this node.
u	Move \"up\" from this node.
m	Pick menu item specified by name (or abbreviation).
	Picking a menu item causes another node to be selected.
f	Follow a cross reference.  Reads name of reference.
l	Move to the last node you were at.

Moving within a node:
Space	Scroll forward a page.		DEL  Scroll backward.
b or .	Go to beginning of node.

Advanced commands:
q	Quit Info by reselecting previously selected buffer.
e	Edit contents of selected node.
1	Pick first item in node's menu.
2...5	Pick second ... fifth item in node's menu.
d	Move to the Directory (start) node of the Info tree: \"(DIR)Top\".
g	Move to node specified by name.
	You may include a filename as well, as (FILENAME)NODENAME.
s	Search through this Info file for specified regexp,
	and select the node in which the next occurrence is found.
=	Selects the node at point.
K	Quit Info by killing the buffer.  Do NOT use M-X kill-buffer directly!
S	Save the Info file and its tag table.

Entry to Info calls the value of info-mode-hook if that value is non-nil,
changing node calls the value of Info-select-node-hook if non-nil."
  (interactive "P")
  (Info-mode-setup (function (lambda ()
			       (setq major-mode 'info-mode
				     mode-name "Info")
			       (use-local-map info-mode-map)
			       ;; Make mode line update.
			       (set-buffer-modified-p (buffer-modified-p))))
		   any-mode))

(defun Info-mode-setup (func &optional any-mode)
  ;; Enter Info-mode using FUNC if ANY-MODE is nil or no Info-mode is entered.
  ;; Setup Info variables if necessary.
  (if Info-tag-table-marker
      (or any-mode
	  (funcall func))
    (kill-all-local-variables)
    (funcall func)
    (set-syntax-table text-mode-syntax-table)
    (setq local-abbrev-table text-mode-abbrev-table)
    (setq case-fold-search t)
    ;; Don't set buffer-read-only here, would break find-file and others.
    (make-local-variable 'Info-current-file)
    (make-local-variable 'Info-current-subfile)
    (make-local-variable 'Info-current-node)
    (make-local-variable 'Info-history)
    (make-local-variable 'Info-subfiles)
    (make-local-variable 'Info-tag-table-marker)
    (if buffer-file-name
	(setq Info-current-file (file-name-sans-versions buffer-file-name)))
    ;; This statement signals that this buffer is fully set up for Info:
    (setq Info-tag-table-marker (make-marker))
    (make-local-variable 'write-file-hooks)
    (setq write-file-hooks (cons '(lambda ()
				    (Info-tag-file-operation 'save-buffer)
				    nil)
				 write-file-hooks))
    (Info-set-mode-line)
    (run-hooks 'info-mode-hook)))

;; 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 "sGoto node: ")
  (apply 'Info-find-node (Info-split-node-name nodename nil "Top")))

;; Split "NODENAME" or "(FILENAME)NODENAME" to the list ("FILENAME" "NODENAME")
(defun Info-split-node-name (nodename &optional default-file default-node)
  (let (filename)
    (string-match "\\s *\\((\\s *\\([^\t)]*\\)\\s *)\\s *\\|\\)\\(.*\\)"
		  nodename)
    (setq filename (if (= (match-beginning 1) (match-end 1))
		       ""
		     (substring nodename (match-beginning 2) (match-end 2)))
	  nodename (substring nodename (match-beginning 3) (match-end 3)))
    (let ((trim (string-match "\\s *\\'" filename)))
      (if trim (setq filename (substring filename 0 trim))))
    (let ((trim (string-match "\\s *\\'" nodename)))
      (if trim (setq nodename (substring nodename 0 trim))))
    (list (if (string-equal filename "") default-file filename)
	  (if (string-equal nodename "") default-node nodename))))



;; 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)
  ;; Convert filename to lower case if not found as specified.
  ;; Expand it.
  (if filename
      (let (dirs)
	(setq filename	(substitute-in-file-name filename)
	      ;; Use Info-path unless the filename starts with `./'.
	      dirs	(if (string-match "^\\./" filename)
			    '(nil)
			  Info-path)
	      filename	(or (where-is-file dirs filename ".info:")
			    (where-is-file dirs (downcase filename) ".info:")
			    (error "Info file %s not found" filename)))))

  ;; If necessary, go into *info* buffer and turn on some info mode.
  (or Info-tag-table-marker
      (progn (switch-to-buffer "*info*")
	     (info-mode t)))
  (setq buffer-read-only (eq major-mode 'info-mode))

  ;; Record the node we are leaving.
  (let ((Info-next-hist (and Info-current-file (not no-going-back)
			     (Info-next-hist))))
    (unwind-protect
	(progn
	  (setq Info-current-node nil)
	  (widen)
	  ;; Switch files if necessary
	  (if filename
	      (Info-visit-mainfile filename))
	  (or (equal nodename "*")
	      (progn
		(Info-find-tag-table)
		;; Search file for a suitable node.
		;; First get advice from tag table if file has one.
		;; Also, if this is an indirect info file,
		;; read the proper subfile into this buffer.
		(let ((guesspos (point-min)))
		  (and (marker-position Info-tag-table-marker)
		       ;; The file has a tag table.  Get advice from it.
		       (save-excursion
			 (let ((marker Info-tag-table-marker))
			   (set-buffer (marker-buffer marker))
			   (goto-char marker))
			 (setq guesspos
			       (if (search-forward
				    (concat "Node: " nodename "\177") nil t)
				   (read (current-buffer)))))
		       (consp Info-subfiles)
		       ;; This is an indirect info file.  Determine which file
		       ;; really holds this node and read it in.
		       (setq guesspos (Info-read-subfile guesspos)))
		  (or (and guesspos
			   ;; Now search from our advised position (or from
			   ;; beg of buffer) to find the actual node.
			   (let ((regexp (concat "Node: *"
						 (regexp-quote nodename)
						 " *[,\t\n]")))
			     (goto-char (max (point-min) (- guesspos 1000)))
			     (catch 'foo
			       (while (search-forward "\n\^_" nil t)
				 (forward-line 1)
				 (let ((beg (point)))
				   (forward-line 1)
				   (if (re-search-backward regexp beg 'move)
				       (throw 'foo t))))
			       nil)))
		      ;; Node not found.  Create it?
		      (and (eq major-mode 'info-edit-mode)
			   (not no-going-back)
			   (y-or-n-p (format "Create node %s? " nodename))
			   (Info-create-node nodename))
		      (error "No such node: \"%s\"" nodename)))))
	  (Info-select-node nodename)
	  (Info-push-history)
	  (setq Info-next-hist nil))
      ;; If we did not finish finding the specified node,
      ;; go back to the previous one.
      (if Info-next-hist
	  (progn
	    (Info-visit-mainfile (nth 1 Info-next-hist))
	    (or (equal (nth 2 Info-next-hist) "*")
		(Info-find-tag-table))
	    (Info-back)))))
  (goto-char (point-min)))



(defun Info-visit-mainfile (mainfile)
  ;; Visit MAINFILE in this Info buffer.  If necessary, offer to save first.
  (or (equal Info-current-file mainfile)
      (progn (buffer-offer-save)
	     (Info-kill-tag-table)
	     (setq Info-current-file nil)
	     (Info-switch-file mainfile)
	     (setq Info-current-file
		   (file-name-sans-versions buffer-file-name))
	     (Info-set-mode-line))))

(defun Info-read-subfile (nodepos &optional nosearch)
  ;; Visit subfile containing NODEPOS in this Info buffer.  If necessary,
  ;; offer to save first.  If NOSEARCH, don't search for first ^_.
  (let ((list Info-subfiles)
	this last)
    (while (and list
		(>= nodepos (cdr (setq this (car list)))))
      (setq last this
	    list (cdr list)))
    (Info-visit-subfile (car last) nosearch)
    (+ (- nodepos (cdr last)) (point))))

(defun Info-visit-subfile (subfile &optional nosearch)
  (or (equal Info-current-subfile subfile)
      (progn (buffer-offer-save)
	     (Info-switch-file subfile)
	     (setq Info-current-subfile subfile)))
  (goto-char (point-min))
  (or nosearch
      (search-forward "\n\^_")))

(defun Info-switch-file (filename)
  ;; Visit another file in current Info buffer.
  (setq Info-current-node nil
	Info-current-subfile nil)
  (let (buffer-read-only)
    (setq buffer-file-name nil)
    (erase-buffer)
    (insert-file-contents (expand-file-name filename) t)
    (setq default-directory (file-name-directory buffer-file-name)
	  buffer-read-only (not (file-writable-p buffer-file-name))
	  buffer-backed-up nil)
    (auto-save-mode auto-save-default))
  (setq Info-current-node "*"))



(defun Info-find-tag-table ()
  ;; Set Info-tag-table-marker and Info-subfiles:
  ;; See whether file has a tag table.  Record the location if yes.
  (or Info-subfiles
      (let ((subfiles 'no-subfiles))
	(save-excursion
	  (goto-char (point-max))
	  (forward-line -8)
	  (if (search-forward "\^_\nEnd tag table\n" nil t)
	      (let (pos)
		;; We have a tag table.  Find its beginning.
		;; Is this an indirect file?
		(search-backward "\nTag table:\n")
		(setq pos (point))
		(forward-line 2)
		(if (looking-at "(Indirect)\n")
		    ;; It is indirect.  Copy it to another buffer
		    ;; and record that the tag table is in that buffer.
		    (let ((buf (current-buffer))
			  (marker Info-tag-table-marker))
		      (set-buffer
		       (get-buffer-create
			(concat " *tag table (" (buffer-name) ")*")))
		      (erase-buffer)
		      (insert-buffer-substring buf)
		      (set-buffer-modified-p nil)
		      (set-marker marker (match-end 0))
		      ;; Make list ((subfile . pos) ...):
		      (goto-char (point-min))
		      (search-forward "\n\^_\nIndirect:")
		      (forward-line 1)
		      (let (list)
			(while (not (looking-at "\^_"))
			  (setq list (cons (cons (buffer-substring
						  (point)
						  (progn (search-forward ": ")
							 (- (point) 2)))
						 (read (current-buffer)))
					   list)))
			(setq subfiles (nreverse list))))
		  (set-marker Info-tag-table-marker pos)))))
	(setq Info-subfiles subfiles))))

(defun Info-kill-tag-table ()
  ;; Reset Info-tag-table-marker and Info-subfiles.
  (let ((tbuf (Info-tag-file-operation 'buffer-offer-save "Save tag table? ")))
    (if tbuf
	(kill-buffer tbuf)))
  (set-marker Info-tag-table-marker nil)
  (setq Info-subfiles nil))

;; If the tag table is in a separate buffer, apply FUNC on ARGS in that buffer,
;; with buffer-file information correctly set up, and return the tag table
;; buffer.  That buffer cannot normally contain file information.  Otherwise
;; find-file might enter it, allowing the user to confuse things.
(defun Info-tag-file-operation (func &rest args)
  (let ((tbuf (marker-buffer Info-tag-table-marker)))
    (and tbuf
	 (not (eq tbuf (current-buffer)))
	 (let ((file Info-current-file)
	       (dir default-directory))
	   (save-excursion
	     (set-buffer tbuf)
	     (let* ((default-directory dir)
		    (buffer-file-name file))
	       (apply func args))
	     tbuf)))))



(defvar Info-next-hist nil)

(defun Info-next-hist ()
  (list Info-current-subfile
	Info-current-file (or Info-current-node "*") (point)))

(defun Info-push-history ()
  ;; Pushes Info-next-hist onto Info-history if the node has changed.
  ;; Returns non-nil if something was pushed.
  (and Info-next-hist
       (not (and (equal (nth 2 Info-next-hist) Info-current-node)
		 (equal (nth 1 Info-next-hist) Info-current-file)))
       (setq Info-history (cons (cdr Info-next-hist)
				Info-history))))

(defun Info-back ()
  (if (car Info-next-hist)
      (Info-visit-subfile (car Info-next-hist) t))
  (Info-select-node (nth 2 Info-next-hist) t (nth 3 Info-next-hist)))

;; Select the info node that point is in, or "*" is NODE is "*".
;; Returns node name.  If NOERROR, just return nil if there is no node here.
(defun Info-select-node (&optional nodename noerror pos)
  "Select the node that point is in."
  (interactive)
  (setq Info-current-node nil)
  (widen)
  (if pos
      (goto-char pos))
  (if (equal nodename "*")
      (progn (setq Info-current-node "*")
	     (run-hooks 'Info-select-node-hook))
    (save-excursion
      (and (search-backward "\n\^_" nil noerror);; Find beginning of node.
	   (let ((beg (point)))
	     (forward-line 3)
	     ;; Get nodename spelled as it is in the node.
	     (re-search-backward "Node:[ \t]*" beg noerror))
	   (prog2
	       (goto-char (match-end 0))
	       (setq Info-current-node
		     (buffer-substring (point)
				       (progn
					 (skip-chars-forward "^,\t\n")
					 (point))))
	     ;; 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)))
	     (run-hooks 'Info-select-node-hook))))))


(defun Info-set-mode-line ()
  (let ((basename (if Info-current-file
		      (file-name-nondirectory Info-current-file)
		    "")))
    (setq mode-line-buffer-identification
	  (list (concat "Info: (" (Info-strip-filename basename) ")")
		'Info-current-node
		(or (string-equal (buffer-name) basename)
		    "    <%b>")))))

(defun Info-strip-filename (filename)
  (if (string-match "\\.info$" filename)
      (substring filename 0 -5)
    filename))

(defvar Info-last-search nil
  "Default regexp for Info S command to search for.")

(defun Info-search (regexp)
  "Search for REGEXP, starting from point, and select node it's found in."
  (interactive "sSearch (regexp): ")
  (if (equal regexp "")
      (setq regexp Info-last-search)
    (setq Info-last-search regexp))
  (let ((found (save-excursion
		 (save-restriction
		   (widen)
		   (if (re-search-forward regexp nil Info-current-subfile)
		       (point)))))
	(Info-next-hist (Info-next-hist)))
    (or found
	;;can only happen in subfile case -- else would have erred
	(unwind-protect
	    (let ((list Info-subfiles))
	      (while (and list
			  (not (string-equal Info-current-subfile
					     (car (car list)))))
		(setq list (cdr list)))
	      (while (setq list (cdr list))
		(message "Searching subfile %s..." (car (car list)))
		(Info-visit-subfile (car (car list)))
		(if (re-search-forward regexp nil t)
		    (setq found (point)
			  list ())))
	      (if found
		  (message "")
		(signal 'search-failed (list regexp))))
	  (or found
	      (Info-back))))
    (Info-select-node nil t found)
    (Info-push-history)))

;; Get NAME: pointer, not found -> error(ERRORNAME), or nil if errorname=t
;; Optional 3rd arg is the position of the node's header line.
(defun Info-extract-pointer (name &optional errorname beg)
  (save-excursion
    (goto-char (or beg (point-min)))
    (forward-line 1)
    (cond ((re-search-backward (concat name ":") beg t)
	   (goto-char (match-end 0))
	   (Info-following-node-name))
	  ((eq errorname t)
	   nil)
	  (t
	   (error (concat "Node has no " (capitalize (or errorname name))))))))

(defun Info-following-node-name (&optional allowedchars)
  (skip-chars-forward " \t")
  (buffer-substring
   (point)
   (progn
     (while (looking-at (concat "[" (or allowedchars "^,\t\n") "]"))
       (skip-chars-forward (concat (or allowedchars "^,\t\n") "("))
       (if (looking-at "(")
	   (skip-chars-forward "^)")))
     (skip-chars-backward " \n")
     (point))))

(defun Info-next ()
  "Go to the next node of this node."
  (interactive)
  (let* ((node (Info-extract-pointer "next"))
	 (Info-node-desc (if (eq major-mode 'info-edit-mode)
			     (list Info-current-file node
				   Info-current-node
				   (Info-extract-pointer "up" t)
				   nil))))
    (Info-goto-node node)))

(defun Info-prev ()
  "Go to the previous node of this node."
  (interactive)
  (let* ((node (Info-extract-pointer "prev[ious]*" "previous"))
	 (Info-node-desc (if (eq major-mode 'info-edit-mode)
			     (list Info-current-file node
				   nil
				   (Info-extract-pointer "up" t)
				   Info-current-node))))
    (Info-goto-node node)))

(defun Info-up ()
  "Go to the superior node of this node."
  (interactive)
  (Info-goto-node (Info-extract-pointer "up")))

(defun Info-last ()
  "Go back to the last node visited."
  (interactive)
  (or Info-history
      (error "This is the first Info node you looked at"))
  (let ((this-hist (car Info-history)))
    (setq Info-history (cdr Info-history))
    (let (Info-history)
      (Info-find-node (car this-hist) (nth 1 this-hist)))
    (goto-char (nth 2 this-hist))))

(defun Info-directory ()
  "Go to the Info directory node."
  (interactive)
  (Info-find-node "dir" "top"))

(defun Info-exit ()
  "Exit Info by selecting some other buffer."
  (interactive)
  (switch-to-buffer (prog1 (other-buffer (current-buffer))
		      (bury-buffer (current-buffer)))))

(defun Info-help ()
  "Enter the Info tutorial."
  (interactive)
  (Info-find-node "info"
		  (if (< (window-height) 23)
		      "Help-Small-Screen"
		    "Help")))

(defun Info-summary ()
  "Display a brief summary of all Info commands."
  (interactive)
  (save-window-excursion
    (let ((mode major-mode))
      (switch-to-buffer "*Help*")
      (erase-buffer)
      (insert (documentation mode)))
    (goto-char (point-min))
    (let (ch flag)
      (while (progn (setq flag (not (pos-visible-in-window-p (point-max))))
		    (message (if flag "Type Space to see more"
			       "Type Space to return to Info"))
		    (if (/= ?\  (setq ch (read-char)))
			(progn (setq unread-command-char ch) nil)
		      flag))
	(scroll-up)))))

(defun Info-KILL ()
  "Quit Info by killing the buffer."
  (interactive)
  (buffer-offer-save)
  (Info-kill-tag-table)
  (kill-buffer nil))

(defun Info-SAVE ()
  "Save the Info buffer and the tags table."
  (interactive)
  (save-buffer)
  (Info-tag-file-operation 'save-buffer))

(defun Info-follow-reference (footnotename)
  "Follow cross reference named NAME to the node it refers to.
NAME may be an abbreviation of the reference name."
  (interactive
   (let ((completion-ignore-case t)
	 completions)
     (save-excursion
       (goto-char (point-min))
       (while (re-search-forward "\\*note[ \n\t]+\\([^:]*\\):" nil t)
	 (setq completions
	       (cons (cons (Info-reference-name (buffer-substring
						 (match-beginning 1)
						 (1- (point))))
			   nil)
		     completions))))
     (if completions
	 (list (completing-read "Follow reference named: " completions nil t))
       (error "No cross-references in this node"))))
  (let (i (str (concat "\\*note " (regexp-quote footnotename))))
    (while (setq i (string-match " " str i))
      (setq str (concat (substring str 0 i) "[ \t\n]+" (substring str (1+ i))))
      (setq i (+ i 6)))
    (Info-goto-node (save-excursion
		      (goto-char (point-min))
		      (or (re-search-forward str nil t)
			  (error "No cross-reference named %s" footnotename))
		      (goto-char (+ (match-beginning 0) 5))
		      (Info-reference-name
		       (Info-extract-menu-node-name
			"Bad format cross reference" "^.,\t"))))))

(defun Info-reference-name (str)
  (let (i)
    (while (setq i (string-match "[ \t\n]+" str i))
      (setq str (concat (substring str 0 i) " "
			(substring str (match-end 0))))
      (setq i (1+ i))))
  str)

(defun Info-extract-menu-node-name (&optional errmessage allowedchars)
  (skip-chars-forward " \t\n")
  (let ((beg (point))
	str i)
    (skip-chars-forward "^:")
    (forward-char 1)
    (setq str
	  (if (looking-at ":")
	      (buffer-substring beg (1- (point)))
	    (skip-chars-forward " \t\n")
	    (Info-following-node-name (or allowedchars "^.,\t\n"))))
    (while (setq i (string-match "\n" str i))
      (aset str i ?\ ))
    str))

(defun Info-menu-item-sequence (list)
  (while list
    (Info-menu-item (car list))
    (setq list (cdr list))))

(defun Info-menu (menu-item)
  "Go to node for menu item named (or abbreviated) NAME."
  (interactive
   (let ((completions '())
	 ;; If point is within a menu item, use that item as the default
	 (default nil)
	 (p (point))
	 (last nil))
     (save-excursion
       (goto-char (point-min))
       (if (not (search-forward "\n* menu:" nil t))
	   (error "No menu in this node"))
       (while (re-search-forward
		"\n\\* \\([^:\t\n]*\\):" nil t)
	 (if (and (null default)
		  (prog1 (if last (< last p) nil)
		    (setq last (match-beginning 0)))
		  (<= p last))
	     (setq default (car (car completions))))
	 (setq completions (cons (cons (buffer-substring
					 (match-beginning 1)
					 (match-end 1))
				       (match-beginning 1))
				 completions)))
       (if (and (null default) last
		(< last p)
		(<= p (progn (end-of-line) (point))))
	   (setq default (car (car completions)))))
     (let ((item nil))
       (while (null item)
	 (setq item (let ((completion-ignore-case t))
		      (completing-read (if default
					   (format "Menu item (default %s): "
						   default)
					   "Menu item: ")
				       completions nil t)))
	 ;; we rely on the bug (which RMS won't change for his own reasons)
	 ;; that completing-read accepts an input of "" even when the
	 ;; require-match argument is true and "" is not a valid possibility
	 (if (string= item "")
	     (if default
		 (setq item default)
	         ;; ask again
	         (setq item nil))))
       (list item))))
  (let* ((node (Info-extract-menu-item menu-item))
	 (Info-node-desc (if (eq major-mode 'info-edit-mode)
			     (Info-extract-menu-nodedesc node))))
    (Info-goto-node node)))

;; Leave point at menu item and extract its nodename.
(defun Info-extract-menu-item (menu-item)
  (goto-char (save-excursion
	       (goto-char (point-min))
	       (or (search-forward "\n* menu:" nil t)
		   (error "No menu in this node"))
	       (or (search-forward (concat "\n* " menu-item) nil t)
		   (error "No such item in menu"))
	       (beginning-of-line)
	       (+ (point) 2)))
  (save-excursion
    (Info-extract-menu-node-name)))

;; Leave point at menu item no. COUNT and extract its nodename.
(defun Info-extract-menu-counting (count)
  (goto-char (save-excursion
	       (goto-char (point-min))
	       (or (search-forward "\n* menu:" nil t)
		   (error "No menu in this node"))
	       (or (search-forward "\n* " nil t count)
		   (error "Too few items in menu"))
	       (point)))
  (save-excursion
    (Info-extract-menu-node-name)))

(defun Info-nth-menu-item ()
  "Go to the node of the (last-command-char)th menu item."
  (interactive)
  (let* ((node (Info-extract-menu-counting
		(string-to-int (char-to-string last-command-char))))
	 (Info-node-desc (if (eq major-mode 'info-edit-mode)
			     (Info-extract-menu-nodedesc node))))
    (Info-goto-node node)))

;; Suggest (current-file node prev up next) for menu item around point.
(defun Info-extract-menu-nodedesc (nodename)
  (save-excursion
    (list Info-current-file nodename
	  (if (progn (search-backward "\n* " nil nil 2)
		     (forward-char 3)
		     (save-excursion
		       (search-backward "\n* Menu:" nil t)))
	      (Info-extract-menu-node-name)
	    'Info-up)
	  Info-current-node
	  (if (search-forward "\n* " nil t 2)
	      (Info-extract-menu-node-name)))))

(defvar info-edit-map nil
  "Local keymap used within `e' command of Info.")
(if info-edit-map
    nil
  (setq info-edit-map (copy-keymap text-mode-map))
  (define-key info-edit-map "\C-c" (copy-keymap info-mode-map))
  (define-key info-edit-map "\C-c\C-c" 'Info-cease-edit)
  (define-key info-edit-map "\C-c\C-fs" 'Info-split)
  (define-key info-edit-map "\C-c\C-fv" 'Info-validate)
  (define-key info-edit-map "\C-c\C-ft" 'Info-tagify))

(defun info-edit-mode (&optional any-mode)
  "Info Edit mode is a mode for editing the contents of an Info node.
Like text mode with the addition of

C-c C-c	which returns to Info mode for browsing,
C-c C-f	[s/t/v] for \"informat\" functions, and
C-c	as a prefix to the usual Info commands.  A new node is created
	when an info command jumps to a nonexistent node.
\\{info-edit-map}"
  (Info-mode-setup (function (lambda ()
			       (setq major-mode 'info-edit-mode
				     mode-name "Info EDIT")
			       (use-local-map info-edit-map)
			       ;; Make mode line update.
			       (set-buffer-modified-p (buffer-modified-p))))
		   any-mode))

(defun Info-edit ()
  "Edit the contents of this Info node.
Allowed only if variable Info-enable-edit is non-nil."
  (interactive)
  (or Info-enable-edit
      (error "Editing info nodes is not enabled"))
  (info-edit-mode)
  (setq buffer-read-only nil)
  (message (substitute-command-keys
	     "Editing: Type \\[Info-cease-edit] to return to info")))

(defun Info-cease-edit ()
  "Finish editing Info node; switch back to Info proper."
  (interactive)
  (let ((bufmod (buffer-modified-p)))
    ;; Do this first, so nothing has changed if user C-g's at query.
    (buffer-offer-save)
    (info-mode)
    (setq buffer-read-only t)
    (and (marker-position Info-tag-table-marker)
	 bufmod
	 (message "Tags may have changed.  Use Info-tagify if necessary"))))
Technical Details
Message-ID: <9001261119.AA00688@nikud.uio.no>
Path: arc1-ca.pugleaf.net!gmdzi!unido!mcsun!uunet!zephyr.ens.tek.com!uw-beaver!mit-eddie!snorkelwacker!tut.cis.ohio-state.edu!IFI.UIO.NO!hallvard

Show Full Headers
Complete Article Headers
X-Google-Language: ENGLISH,ASCII-7-bit
X-Google-Thread: fb2f1,15942da9877b675a,start
X-Google-Attributes: gidfb2f1,public
X-Google-ArrivalTime: 1990-01-26 03:19:50 PST
Path: arc1-ca.pugleaf.net!gmdzi!unido!mcsun!uunet!zephyr.ens.tek.com!uw-beaver!mit-eddie!snorkelwacker!tut.cis.ohio-state.edu!IFI.UIO.NO!hallvard
From: hallvard@IFI.UIO.NO (Hallvard B Furuseth)
Newsgroups: gnu.emacs.bug
Subject: info.el for multiple files
Message-ID: <9001261119.AA00688@nikud.uio.no>
Date: 26 Jan 90 11:19:50 GMT
Sender: daemon@tut.cis.ohio-state.edu
Distribution: gnu
Organization: GNUs Not Usenet
Lines: 904
Posted: Fri Jan 26 12:19:50 1990