;;; info.el --- info package for Emacs.
-;; Copyright (C) 1985, 1986, 1992, 1993 Free Software Foundation, Inc.
+;; Copyright (C) 1985, 1986, 1992, 1993, 1994 Free Software Foundation, Inc.
;; Maintainer: FSF
;; Keywords: help
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
-;; along with GNU Emacs; see the file COPYING. If not, write to
-;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+;; along with GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+;; Boston, MA 02111-1307, USA.
;;; Commentary:
-;;; Note that nowadays we expect info files to be made using makeinfo.
+;; Note that nowadays we expect info files to be made using makeinfo.
;;; Code:
It is better to write a Texinfo file and generate the Info file from that,
because that gives you a printed manual as well.")
-(defvar Info-enable-active-nodes t
+(defvar Info-enable-active-nodes nil
"Non-nil allows Info to execute Lisp code associated with nodes.
The Lisp code is executed when the node is selected.")
-
-(defvar Info-default-directory-list nil
- "List of default directories to search for Info documentation files.
-This value is used as the default for `Info-directory-list'. It is set
-in paths.el.")
+(put 'Info-enable-active-nodes 'risky-local-variable t)
(defvar Info-fontify t
"*Non-nil enables highlighting and fonts in Info nodes.")
+(defvar Info-fontify-maximum-menu-size 30000
+ "*Maximum size of menu to fontify if `Info-fontify' is non-nil.")
+
(defvar Info-directory-list
(let ((path (getenv "INFOPATH"))
- (sibling (expand-file-name "../info/" (invocation-directory))))
+ ;; This is for older Emacs versions
+ ;; which might get this info.el from the Texinfo distribution.
+ (path-separator (if (boundp 'path-separator) path-separator
+ (if (eq system-type 'ms-dos) ";" ":")))
+ (source (expand-file-name "info/" source-directory))
+ (sibling (if installation-directory
+ (expand-file-name "info/" installation-directory)))
+ alternative)
(if path
(let ((list nil)
idx)
(while (> (length path) 0)
- (setq idx (or (string-match ":" path) (length path))
+ (setq idx (or (string-match path-separator path) (length path))
list (cons (substring path 0 idx) list)
path (substring path (min (1+ idx)
(length path)))))
(nreverse list))
- (if (or (member sibling Info-default-directory-list)
- (not (file-exists-p sibling)))
+ (if (and sibling (file-exists-p sibling))
+ (setq alternative sibling)
+ (setq alternative source))
+ (if (or (member alternative Info-default-directory-list)
+ (not (file-exists-p alternative))
+ ;; On DOS/NT, we use movable executables always,
+ ;; and we must always find the Info dir at run time.
+ (if (or (eq system-type 'ms-dos) (eq system-type 'windows-nt))
+ nil
+ ;; Use invocation-directory for Info only if we used it for
+ ;; exec-directory also.
+ (not (string= exec-directory
+ (expand-file-name "lib-src/"
+ installation-directory)))))
Info-default-directory-list
- (reverse (cons sibling (cdr (reverse Info-default-directory-list)))))))
+ (reverse (cons alternative
+ (cdr (reverse Info-default-directory-list)))))))
"List of directories to search for Info documentation files.
nil means not yet initialized. In this case, Info uses the environment
variable INFOPATH to initialize it, or `Info-default-directory-list'
-if there is no INFOPATH variable in the environment.")
+if there is no INFOPATH variable in the environment.
+The last element of `Info-default-directory-list' is the directory
+where Emacs installs the Info files that come with it.
+
+If you run the Emacs executable from the `src' directory in the Emacs
+source tree, the `info' directory in the source tree is used as the last
+element, in place of the installation Info directory. This is useful
+when you run a version of Emacs without installing it.")
+
+(defvar Info-additional-directory-list nil
+ "List of additional directories to search for Info documentation files.
+These directories are not searched for merging the `dir' file.")
(defvar Info-current-file nil
- "Info file that Info is now looking at, or nil.")
+ "Info file that Info is now looking at, or nil.
+This is the name that was specified in Info, not the actual file name.
+It doesn't contain directory names or file name extensions added by Info.")
(defvar Info-current-subfile nil
"Info subfile that is actually in the *info* buffer now,
(defvar Info-standalone nil
"Non-nil if Emacs was started solely as an Info browser.")
-(defvar Info-suffix-list '( (".info.Z" . "uncompress")
- (".info.Y" . "unyabba")
- (".info.gz" . "gunzip")
- (".info.z" . "gunzip")
- (".info" . nil)
- (".Z" . "uncompress")
- (".Y" . "unyabba")
- (".gz" . "gunzip")
- (".z" . "gunzip")
- ("" . nil))
+(defvar Info-suffix-list
+ (if (eq system-type 'ms-dos)
+ '( (".gz" . "gunzip")
+ (".z" . "gunzip")
+ (".inf" . nil)
+ ("" . nil))
+ '( (".info.Z" . "uncompress")
+ (".info.Y" . "unyabba")
+ (".info.gz" . "gunzip")
+ (".info.z" . "gunzip")
+ (".info" . nil)
+ (".Z" . "uncompress")
+ (".Y" . "unyabba")
+ (".gz" . "gunzip")
+ (".z" . "gunzip")
+ ("" . nil)))
"List of file name suffixes and associated decoding commands.
Each entry should be (SUFFIX . STRING); the file is given to
the command as standard input. If STRING is nil, no decoding is done.
Because the SUFFIXes are tried in order, the empty string should
be last in the list.")
+;; Concatenate SUFFIX onto FILENAME. SUFFIX should start with a dot.
+;; First, on ms-dos, delete some of the extension in FILENAME
+;; to make room.
+(defun info-insert-file-contents-1 (filename suffix)
+ (if (not (eq system-type 'ms-dos))
+ (concat filename suffix)
+ (let* ((sans-exts (file-name-sans-extension filename))
+ ;; How long is the extension in FILENAME (not counting the dot).
+ (ext-len (max 0 (- (length filename) (length sans-exts) 1)))
+ ext-left)
+ ;; SUFFIX starts with a dot. If FILENAME already has one,
+ ;; get rid of the one in SUFFIX (unless suffix is empty).
+ (or (and (<= ext-len 0)
+ (not (eq (aref filename (1- (length filename))) ?.)))
+ (= (length suffix) 0)
+ (setq suffix (substring suffix 1)))
+ ;; How many chars of that extension should we keep?
+ (setq ext-left (min ext-len (max 0 (- 3 (length suffix)))))
+ ;; Get rid of the rest of the extension, and add SUFFIX.
+ (concat (substring filename 0 (- (length filename)
+ (- ext-len ext-left)))
+ suffix))))
+
(defun info-insert-file-contents (filename &optional visit)
"Insert the contents of an info file in the current buffer.
Do the right thing if the file has been compressed or zipped."
(let ((tail Info-suffix-list)
fullname decoder)
(if (file-exists-p filename)
+ ;; FILENAME exists--see if that name contains a suffix.
+ ;; If so, set DECODE accordingly.
(progn
(while (and tail
(not (string-match
(setq tail (cdr tail)))
(setq fullname filename
decoder (cdr (car tail))))
+ ;; Try adding suffixes to FILENAME and see if we can find something.
(while (and tail
- (not (file-exists-p (concat filename (car (car tail))))))
+ (not (file-exists-p (info-insert-file-contents-1
+ filename (car (car tail))))))
(setq tail (cdr tail)))
- (setq fullname (concat filename (car (car tail)))
+ ;; If we found a file with a suffix, set DECODER according to the suffix
+ ;; and set FULLNAME to the file's actual name.
+ (setq fullname (info-insert-file-contents-1 filename (car (car tail)))
decoder (cdr (car tail)))
(or tail
- (error "Can't find %s or any compressed version of it!" filename)))
+ (error "Can't find %s or any compressed version of it" filename)))
+ ;; check for conflict with jka-compr
+ (if (and (featurep 'jka-compr)
+ (jka-compr-installed-p)
+ (jka-compr-get-compression-info fullname))
+ (setq decoder nil))
(insert-file-contents fullname visit)
(if decoder
- (let ((buffer-read-only nil))
- (shell-command-on-region (point-min) (point-max) decoder t)))))
+ (let ((buffer-read-only nil)
+ (default-directory (or (file-name-directory fullname)
+ default-directory)))
+ (call-process-region (point-min) (point-max) decoder t t)))))
+
+;;;###autoload (add-hook 'same-window-buffer-names "*info*")
;;;###autoload
(defun info (&optional file)
the default is the top-level directory of Info.
In interactive use, a prefix argument directs this command
-to read a file name from the minibuffer."
+to read a file name from the minibuffer.
+
+The search path for Info files is in the variable `Info-directory-list'.
+The top-level Info directory is made by combining all the files named `dir'
+in all the directories in that path."
(interactive (if current-prefix-arg
(list (read-file-name "Info file name: " nil nil t))))
(if file
(Info-goto-node (concat "(" file ")"))
(if (get-buffer "*info*")
- (switch-to-buffer "*info*")
+ (pop-to-buffer "*info*")
(Info-directory))))
;;;###autoload
(if filename
(let (temp temp-downcase found)
(setq filename (substitute-in-file-name filename))
- (if (string= (downcase (file-name-nondirectory filename)) "dir")
+ (if (string= (downcase filename) "dir")
(setq found t)
(let ((dirs (if (string-match "^\\./" filename)
;; If specified name starts with `./'
;; then just try current directory.
'("./")
- Info-directory-list)))
+ (if (file-name-absolute-p filename)
+ ;; No point in searching for an
+ ;; absolute file name
+ '(nil)
+ (if Info-additional-directory-list
+ (append Info-directory-list
+ Info-additional-directory-list)
+ Info-directory-list)))))
;; Search the directory list for file FILENAME.
(while (and dirs (not found))
(setq temp (expand-file-name filename (car dirs)))
(setq temp-downcase
(expand-file-name (downcase filename) (car dirs)))
;; Try several variants of specified name.
- (catch 'foundit
- (mapcar
- (function
- (lambda (x)
- (if (file-exists-p (concat temp (car x)))
- (progn
- (setq found temp)
- (throw 'foundit nil)))
- (if (file-exists-p (concat temp-downcase (car x)))
- (progn
- (setq found temp-downcase)
- (throw 'foundit nil)))))
- Info-suffix-list))
+ (let ((suffix-list Info-suffix-list))
+ (while (and suffix-list (not found))
+ (cond ((file-exists-p
+ (info-insert-file-contents-1
+ temp (car (car suffix-list))))
+ (setq found temp))
+ ((file-exists-p
+ (info-insert-file-contents-1
+ temp-downcase (car (car suffix-list))))
+ (setq found temp-downcase)))
+ (setq suffix-list (cdr suffix-list))))
(setq dirs (cdr dirs)))))
(if found
(setq filename found)
(set-marker Info-tag-table-marker nil)
(goto-char (point-max))
(forward-line -8)
- (or (equal nodename "*")
+ ;; Use string-equal, not equal, to ignore text props.
+ (or (string-equal nodename "*")
(not (search-forward "\^_\nEnd tag table\n" nil t))
(let (pos)
;; We have a tag table. Find its beginning.
(match-end 0))))
(set-marker Info-tag-table-marker pos))))
(setq Info-current-file
- (if (eq filename t) "dir"
- (file-name-sans-versions buffer-file-name)))))
- (if (equal nodename "*")
+ (if (eq filename t) "dir" filename))))
+ ;; Use string-equal, not equal, to ignore text props.
+ (if (string-equal nodename "*")
(progn (setq Info-current-node nodename)
(Info-set-mode-line))
;; Search file for a suitable node.
(if (not (eq (current-buffer) (get-buffer "*info*")))
(setq guesspos
(Info-read-subfile guesspos))))
- (error "No such node: \"%s\"" nodename))))
+ (error "No such node: %s" nodename))))
(goto-char (max (point-min) (- guesspos 1000)))
;; Now search from our advised position (or from beg of buffer)
;; to find the actual node.
;; since we used it.
(eval (cons 'and
(mapcar '(lambda (elt)
- (equal (cdr elt)
- (file-attributes (car elt))))
+ (let ((curr (file-attributes (car elt))))
+ ;; Don't compare the access time.
+ (if curr (setcar (nthcdr 4 curr) 0))
+ (setcar (nthcdr 4 (cdr elt)) 0)
+ (equal (cdr elt) curr)))
Info-dir-file-attributes))))
(insert Info-dir-contents)
(let ((dirs Info-directory-list)
buffers buffer others nodes dirs-done)
+ (setq Info-dir-file-attributes nil)
+
;; Search the directory list for the directory file.
(while dirs
(let ((truename (file-truename (expand-file-name (car dirs)))))
(member (directory-file-name truename) dirs-done)
;; Try several variants of specified name.
;; Try upcasing, appending `.info', or both.
- (let* (temp
- (buffer
- (cond
- ((progn (setq temp (expand-file-name "DIR" (car dirs)))
- (file-exists-p temp))
- (find-file-noselect temp))
- ((progn (setq temp (expand-file-name "dir" (car dirs)))
- (file-exists-p temp))
- (find-file-noselect temp))
- ((progn (setq temp (expand-file-name "DIR.INFO" (car dirs)))
- (file-exists-p temp))
- (find-file-noselect temp))
- ((progn (setq temp (expand-file-name "dir.info" (car dirs)))
- (file-exists-p temp))
- (find-file-noselect temp)))))
+ (let* (file
+ (attrs
+ (or
+ (progn (setq file (expand-file-name "dir" truename))
+ (file-attributes file))
+ (progn (setq file (expand-file-name "DIR" truename))
+ (file-attributes file))
+ (progn (setq file (expand-file-name "dir.info" truename))
+ (file-attributes file))
+ (progn (setq file (expand-file-name "DIR.INFO" truename))
+ (file-attributes file)))))
(setq dirs-done
(cons truename
(cons (directory-file-name truename)
dirs-done)))
- (if buffer (setq buffers (cons buffer buffers)
- Info-dir-file-attributes
- (cons (cons (buffer-file-name buffer)
- (file-attributes (buffer-file-name buffer)))
- Info-dir-file-attributes))))))
- (setq dirs (cdr dirs)))
-
+ (if attrs
+ (save-excursion
+ (or buffers
+ (message "Composing main Info directory..."))
+ (set-buffer (generate-new-buffer "info dir"))
+ (insert-file-contents file)
+ (setq buffers (cons (current-buffer) buffers)
+ Info-dir-file-attributes
+ (cons (cons file attrs)
+ Info-dir-file-attributes))))))
+ (setq dirs (cdr dirs))))
+
(or buffers
- (error "Can't find the info directory node"))
+ (error "Can't find the Info directory node"))
;; Distinguish the dir file that comes with Emacs from all the
;; others. Yes, that is really what this is supposed to do.
;; If it doesn't work, fix it.
nil t)
(progn
(search-forward "\n\^_" nil 'move)
- (beginning-of-line))
+ (beginning-of-line)
+ (insert "\n"))
;; If none exists, add one.
(goto-char (point-max))
- (insert "\^_\nFile: dir\tnode: " nodename "\n\n* Menu:\n\n"))
+ (insert "\^_\nFile: dir\tNode: " nodename "\n\n* Menu:\n\n"))
;; Merge the text from the other buffer's menu
;; into the menu in the like-named node in the main buffer.
- (apply 'insert-buffer-substring (cdr (car nodes)))
- (insert "\n"))
+ (apply 'insert-buffer-substring (cdr (car nodes))))
(setq nodes (cdr nodes)))
;; Kill all the buffers we just made.
(while buffers
(kill-buffer (car buffers))
- (setq buffers (cdr buffers))))
+ (setq buffers (cdr buffers)))
+ (message "Composing main Info directory...done"))
(setq Info-dir-contents (buffer-string)))
(setq default-directory Info-dir-contents-directory))
;; Get nodename spelled as it is in the node.
(re-search-forward "Node:[ \t]*")
(setq Info-current-node
- (buffer-substring (point)
- (progn
- (skip-chars-forward "^,\t\n")
- (point))))
+ (buffer-substring-no-properties (point)
+ (progn
+ (skip-chars-forward "^,\t\n")
+ (point))))
(Info-set-mode-line)
;; Find the end of it, and narrow.
(beginning-of-line)
(if trim (setq filename (substring filename 0 trim))))
(let ((trim (string-match "\\s *\\'" nodename)))
(if trim (setq nodename (substring nodename 0 trim))))
+ (if transient-mark-mode (deactivate-mark))
(Info-find-node (if (equal filename "") nil filename)
(if (equal nodename "") "Top" nodename))))
+;; This function is used as the "completion table" while reading a node name.
+;; It does completion using the alist in completion-table
+;; unless STRING starts with an open-paren.
+(defun Info-read-node-name-1 (string predicate code)
+ (let ((no-completion (and (> (length string) 0) (eq (aref string 0) ?\())))
+ (cond ((eq code nil)
+ (if no-completion
+ string
+ (try-completion string completion-table predicate)))
+ ((eq code t)
+ (if no-completion
+ nil
+ (all-completions string completion-table predicate)))
+ ((eq code 'lambda)
+ (if no-completion
+ t
+ (assoc string completion-table))))))
+
(defun Info-read-node-name (prompt &optional default)
(let* ((completion-ignore-case t)
- (nodename (completing-read prompt (Info-build-node-completions))))
+ (completion-table (Info-build-node-completions))
+ (nodename (completing-read prompt 'Info-read-node-name-1)))
(if (equal nodename "")
(or default
(Info-read-node-name prompt))
"If this node has been visited, restore the point value when we left."
(while hl
(if (and (equal (nth 0 (car hl)) Info-current-file)
- (equal (nth 1 (car hl)) Info-current-node))
+ ;; Use string-equal, not equal, to ignore text props.
+ (string-equal (nth 1 (car hl)) Info-current-node))
(progn
(goto-char (nth 2 (car hl)))
(setq hl nil)) ;terminate the while at next iter
(defun Info-search (regexp)
"Search for REGEXP, starting from point, and select node it's found in."
(interactive "sSearch (regexp): ")
+ (if transient-mark-mode (deactivate-mark))
(if (equal regexp "")
(setq regexp Info-last-search)
(setq Info-last-search regexp))
(message "Searching subfile %s..." (cdr (car list)))
(Info-read-subfile (car (car list)))
(setq list (cdr list))
- (goto-char (point-min))
+;; (goto-char (point-min))
(if (re-search-forward regexp nil t)
(setq found (point) list ())))
(if found
(widen)
(goto-char found)
(Info-select-node)
- (or (and (equal onode Info-current-node)
+ ;; Use string-equal, not equal, to ignore text props.
+ (or (and (string-equal onode Info-current-node)
(equal ofile Info-current-file))
(setq Info-history (cons (list ofile onode opoint)
Info-history)))))
(Info-following-node-name))
(if (eq errorname t)
nil
- (error (concat "Node has no " (capitalize (or errorname name))))))))
+ (error "Node has no %s" (capitalize (or errorname name)))))))
;; Return the node name in the buffer following point.
;; ALLOWEDCHARS, if non-nil, goes within [...] to make a regexp
;; saying which chas may appear in the node name.
(defun Info-following-node-name (&optional allowedchars)
(skip-chars-forward " \t")
- (buffer-substring
+ (buffer-substring-no-properties
(point)
(progn
(while (looking-at (concat "[" (or allowedchars "^,\t\n") "]"))
(list (if (equal input "")
default input)))
(error "No cross-references in this node"))))
- (let (target beg i (str (concat "\\*note " footnotename)))
+ (let (target beg i (str (concat "\\*note " (regexp-quote footnotename))))
(while (setq i (string-match " " str i))
(setq str (concat (substring str 0 i) "[ \t\n]+" (substring str (1+ i))))
(setq i (+ i 6)))
(forward-char 1)
(setq str
(if (looking-at ":")
- (buffer-substring beg (1- (point)))
+ (buffer-substring-no-properties beg (1- (point)))
(skip-chars-forward " \t\n")
(Info-following-node-name (if multi-line "^.,\t" "^.,\t\n"))))
(while (setq i (string-match "\n" str i))
(aset str i ?\ ))
+ ;; Collapse multiple spaces.
+ (while (string-match " +" str)
+ (setq str (replace-match " " t t str)))
str))
-;; No one calls this and Info-menu-item doesn't exist.
+;; No one calls this.
;;(defun Info-menu-item-sequence (list)
;; (while list
-;; (Info-menu-item (car list))
+;; (Info-menu (car list))
;; (setq list (cdr list))))
+(defun Info-complete-menu-item (string predicate action)
+ (let ((case-fold-search t))
+ (cond ((eq action nil)
+ (let (completions
+ (pattern (concat "\n\\* \\("
+ (regexp-quote string)
+ "[^:\t\n]*\\):")))
+ (save-excursion
+ (set-buffer Info-complete-menu-buffer)
+ (goto-char (point-min))
+ (search-forward "\n* Menu:")
+ (while (re-search-forward pattern nil t)
+ (setq completions (cons (cons (format "%s"
+ (buffer-substring
+ (match-beginning 1)
+ (match-end 1)))
+ (match-beginning 1))
+ completions))))
+ (try-completion string completions predicate)))
+ ((eq action t)
+ (let (completions
+ (pattern (concat "\n\\* \\("
+ (regexp-quote string)
+ "[^:\t\n]*\\):")))
+ (save-excursion
+ (set-buffer Info-complete-menu-buffer)
+ (goto-char (point-min))
+ (search-forward "\n* Menu:")
+ (while (re-search-forward pattern nil t)
+ (setq completions (cons (cons (format "%s"
+ (buffer-substring
+ (match-beginning 1)
+ (match-end 1)))
+ (match-beginning 1))
+ completions))))
+ (all-completions string completions predicate)))
+ (t
+ (save-excursion
+ (set-buffer Info-complete-menu-buffer)
+ (goto-char (point-min))
+ (search-forward "\n* Menu:")
+ (re-search-forward (concat "\n\\* "
+ (regexp-quote string)
+ ":")
+ nil t))))))
+
+
(defun Info-menu (menu-item)
"Go to node for menu item named (or abbreviated) NAME.
Completion is allowed, and the menu item point is on is the default."
;; If point is within a menu item, use that item as the default
(default nil)
(p (point))
+ beg
(last nil))
(save-excursion
(goto-char (point-min))
(if (not (search-forward "\n* menu:" nil t))
(error "No menu in this node"))
- (while (re-search-forward
- "\n\\* \\([^:\t\n]*\\):" nil t)
- (if (and (null default)
- (prog1 (if last (< last p) nil)
- (setq last (match-beginning 0)))
- (<= p last))
- (setq default (car (car completions))))
- (setq completions (cons (cons (buffer-substring
- (match-beginning 1)
- (match-end 1))
- (match-beginning 1))
- completions)))
- (if (and (null default) last
- (< last p)
- (<= p (progn (end-of-line) (point))))
- (setq default (car (car completions)))))
+ (setq beg (point))
+ (and (< (point) p)
+ (save-excursion
+ (goto-char p)
+ (end-of-line)
+ (re-search-backward "\n\\* \\([^:\t\n]*\\):" beg t)
+ (setq default (format "%s" (buffer-substring
+ (match-beginning 1)
+ (match-end 1)))))))
(let ((item nil))
(while (null item)
- (setq item (let ((completion-ignore-case t))
+ (setq item (let ((completion-ignore-case t)
+ (Info-complete-menu-buffer (current-buffer)))
(completing-read (if default
(format "Menu item (default %s): "
default)
"Menu item: ")
- completions nil t)))
+ 'Info-complete-menu-item nil t)))
;; we rely on the fact that completing-read accepts an input
;; of "" even when the require-match argument is true and ""
;; is not a valid possibility
(Info-next)
t)
((and (save-excursion (search-backward "up:" nil t))
- (not (equal (downcase (Info-extract-pointer "up")) "top")))
+ ;; Use string-equal, not equal, to ignore text props.
+ (not (string-equal (downcase (Info-extract-pointer "up"))
+ "top")))
(let ((old-node Info-current-node))
(Info-up)
(let (Info-history success)
(cond ((and upnode (string-match "(" upnode))
(error "First node in file"))
((and upnode (or (null prevnode)
- (equal (downcase prevnode) (downcase upnode))))
+ ;; Use string-equal, not equal,
+ ;; to ignore text properties.
+ (string-equal (downcase prevnode)
+ (downcase upnode))))
(Info-up))
(prevnode
;; If we move back at the same level,
(interactive)
(save-excursion
(forward-line 1)
- (search-backward "\n* menu:" nil t)
- (or (search-backward "\n* " nil t)
- (error "No previous items in menu"))
- (Info-goto-node (Info-extract-menu-node-name))))
+ (let ((beg (save-excursion
+ (and (search-backward "\n* menu:" nil t)
+ (point)))))
+ (or (and beg (search-backward "\n* " beg t))
+ (error "No previous items in menu")))
+ (Info-goto-node (save-excursion
+ (goto-char (match-end 0))
+ (Info-extract-menu-node-name)))))
(defmacro Info-no-error (&rest body)
(list 'condition-case nil (cons 'progn (append body '(t))) '(error nil)))
(defun Info-next-preorder ()
- "Go to the next node, popping up a level if there is none."
+ "Go to the next subnode or the next node, or go up a level."
(interactive)
- (cond ((Info-no-error (Info-next-menu-item)) )
- ((Info-no-error (Info-up)) (forward-line 1))
- (t (error "No more nodes"))))
+ (cond ((Info-no-error (Info-next-menu-item)))
+ ((Info-no-error (Info-next)))
+ ((Info-no-error (Info-up))
+ ;; Since we have already gone thru all the items in this menu,
+ ;; go up to the end of this node.
+ (goto-char (point-max)))
+ (t
+ (error "No more nodes"))))
(defun Info-last-preorder ()
"Go to the last node, popping up a level if there is none."
(interactive)
- (cond ((Info-no-error (Info-last-menu-item)) )
- ((Info-no-error (Info-up)) (forward-line -1))
- (t (error "No previous nodes"))))
+ (cond ((Info-no-error
+ (Info-last-menu-item)
+ ;; If we go down a menu item, go to the end of the node
+ ;; so we can scroll back through it.
+ (goto-char (point-max)))
+ (recenter -1))
+ ((Info-no-error (Info-prev))
+ (goto-char (point-max))
+ (recenter -1))
+ ((Info-no-error (Info-up))
+ (goto-char (point-min))
+ (or (search-forward "\n* Menu:" nil t)
+ (goto-char (point-max))))
+ (t (error "No previous nodes"))))
(defun Info-scroll-up ()
- "Read the next screen. If end of buffer is visible, go to next entry."
+ "Scroll one screenful forward in Info, considering all nodes as one sequence.
+Once you scroll far enough in a node that its menu appears on the screen,
+the next scroll moves into its first subnode. When you scroll past
+the end of a node, that goes to the next node or back up to the parent node."
(interactive)
- (if (pos-visible-in-window-p (point-max))
- (Info-next-preorder)
- (scroll-up)))
+ (if (or (< (window-start) (point-min))
+ (> (window-start) (point-max)))
+ (set-window-start (selected-window) (point)))
+ (let ((virtual-end (save-excursion
+ (goto-char (point-min))
+ (if (search-forward "\n* Menu:" nil t)
+ (point)
+ (point-max)))))
+ (if (or (< virtual-end (window-start))
+ (pos-visible-in-window-p virtual-end))
+ (Info-next-preorder)
+ (scroll-up))))
(defun Info-scroll-down ()
- "Read the previous screen. If start of buffer is visible, go to last entry."
+ "Scroll one screenful back in Info, considering all nodes as one sequence.
+Within the menu of a node, this goes to its last subnode.
+When you scroll past the beginning of a node, that goes to the
+previous node or back up to the parent node."
(interactive)
- (if (pos-visible-in-window-p (point-min))
- (Info-last-preorder)
- (scroll-down)))
-
-(defun Info-next-reference ()
+ (if (or (< (window-start) (point-min))
+ (> (window-start) (point-max)))
+ (set-window-start (selected-window) (point)))
+ (let ((virtual-end (save-excursion
+ (goto-char (point-min))
+ (search-forward "\n* Menu:" nil t))))
+ (if (or virtual-end (pos-visible-in-window-p (point-min)))
+ (Info-last-preorder)
+ (scroll-down))))
+
+(defun Info-next-reference (&optional recur)
"Move cursor to the next cross-reference or menu item in the node."
(interactive)
(let ((pat "\\*note[ \n\t]*\\([^:]*\\):\\|^\\* .*:")
(error "No cross references in this node")))))
(goto-char (match-beginning 0))
(if (looking-at "\\* Menu:")
- (Info-next-reference))))
+ (if recur
+ (error "No cross references in this node")
+ (Info-next-reference t)))))
-(defun Info-prev-reference ()
+(defun Info-prev-reference (&optional recur)
"Move cursor to the previous cross-reference or menu item in the node."
(interactive)
(let ((pat "\\*note[ \n\t]*\\([^:]*\\):\\|^\\* .*:")
(error "No cross references in this node")))))
(goto-char (match-beginning 0))
(if (looking-at "\\* Menu:")
- (Info-prev-reference))))
+ (if recur
+ (error "No cross references in this node")
+ (Info-prev-reference t)))))
(defun Info-index (topic)
"Look up a string in the index for this file.
(interactive "sIndex topic: ")
(let ((orignode Info-current-node)
(rnode nil)
- (pattern (format "\n\\* \\([^\n:]*%s[^\n:]*\\):[ \t]*\\([^.\n]*\\)\\.[ t]*\\([0-9]*\\)"
+ (pattern (format "\n\\* \\([^\n:]*%s[^\n:]*\\):[ \t]*\\([^.\n]*\\)\\.[ \t]*\\([0-9]*\\)"
(regexp-quote topic)))
node)
(Info-goto-node "Top")
(or (re-search-forward "\n\\* \\(.*\\<Index\\>\\)" nil t)
(error "No index"))
(goto-char (match-beginning 1))
- (let ((Info-keeping-history nil))
+ ;; Here, and subsequently in this function,
+ ;; we bind Info-history to nil for internal node-switches
+ ;; so that we don't put junk in the history.
+ ;; In the first Info-goto-node call, above, we do update the history
+ ;; because that is what the user's previous node choice into it.
+ (let ((Info-history nil))
(Info-goto-node (Info-extract-menu-node-name)))
(or (equal topic "")
(let ((matches nil)
(exact nil)
- (Info-keeping-history nil)
+ (Info-history nil)
found)
(while
(progn
(Info-goto-node node))
(or matches
(progn
- (Info-last)
- (error "No \"%s\" in index" topic)))
+ (Info-goto-node orignode)
+ (error "No `%s' in index" topic)))
;; Here it is a feature that assoc is case-sensitive.
(while (setq found (assoc topic matches))
(setq exact (cons found exact)
(forward-line (nth 3 (car Info-index-alternatives)))
(forward-line 3) ; don't search in headers
(let ((name (car (car Info-index-alternatives))))
- (if (or (re-search-forward (format
- "\\(Function\\|Command\\): %s\\( \\|$\\)"
- (regexp-quote name)) nil t)
- (search-forward (format "`%s'" name) nil t)
- (and (string-match "\\`.*\\( (.*)\\)\\'" name)
- (search-forward
- (format "`%s'" (substring name 0 (match-beginning 1)))
- nil t))
- (search-forward name nil t))
- (beginning-of-line)
- (goto-char (point-min)))))
- (message "Found \"%s\" in %s. %s"
+ (Info-find-index-name name)))
+ (message "Found `%s' in %s. %s"
(car (car Info-index-alternatives))
(nth 2 (car Info-index-alternatives))
(if (cdr Info-index-alternatives)
"(Press `,' for more)"
"(Only match)")))
+(defun Info-find-index-name (name)
+ "Move point to the place within the current node where NAME is defined."
+ (if (or (re-search-forward (format
+ "[a-zA-Z]+: %s\\( \\|$\\)"
+ (regexp-quote name)) nil t)
+ (search-forward (format "`%s'" name) nil t)
+ (and (string-match "\\`.*\\( (.*)\\)\\'" name)
+ (search-forward
+ (format "`%s'" (substring name 0 (match-beginning 1)))
+ nil t))
+ (search-forward name nil t))
+ (beginning-of-line)
+ (goto-char (point-min))))
+
(defun Info-undefined ()
"Make command be undefined in Info."
(interactive)
(switch-to-buffer "*Help*")
(erase-buffer)
(insert (documentation 'Info-mode))
+ (help-mode)
(goto-char (point-min))
(let (ch flag)
(while (progn (setq flag (not (pos-visible-in-window-p (point-max))))
START is a regular expression which will match the
beginning of the tokens delimited string
ALL is a regular expression with a single
- parenthized subpattern which is the token to be
+ parenthesized subpattern which is the token to be
returned. E.g. '{\(.*\)}' would return any string
enclosed in braces around POS.
SIG optional fourth argument, controls action on no match
(define-key Info-mode-map "p" 'Info-prev)
(define-key Info-mode-map "q" 'Info-exit)
(define-key Info-mode-map "s" 'Info-search)
+ ;; For consistency with Rmail.
+ (define-key Info-mode-map "\M-s" 'Info-search)
(define-key Info-mode-map "t" 'Info-top-node)
(define-key Info-mode-map "u" 'Info-up)
(define-key Info-mode-map "," 'Info-index-next)
\\[Info-help] Invoke the Info tutorial.
Selecting other nodes:
+\\[Info-mouse-follow-nearest-node]
+ Follow a node reference you click on.
+ This works with menu items, cross references, and
+ the \"next\", \"previous\" and \"up\", depending on where you click.
\\[Info-next] Move to the \"next\" node of this node.
\\[Info-prev] Move to the \"previous\" node of this node.
\\[Info-up] Move \"up\" from this node.
\\[universal-argument] \\[info] Move to new Info file with completion.
\\[Info-search] Search through this Info file for specified regexp,
and select the node in which the next occurrence is found.
-\\[Info-next-preorder] Next-preorder; that is, try to go to the next menu item,
- and if that fails try to move up, and if that fails, tell user
- he/she is done reading.
\\[Info-next-reference] Move cursor to next cross-reference or menu item.
\\[Info-prev-reference] Move cursor to previous cross-reference or menu item."
(kill-all-local-variables)
(make-local-variable 'Info-tag-table-marker)
(make-local-variable 'Info-history)
(make-local-variable 'Info-index-alternatives)
- (if (fboundp 'make-face)
+ (if (memq (framep (selected-frame)) '(x pc))
(progn
(make-face 'info-node)
(make-face 'info-menu-5)
Like text mode with the addition of `Info-cease-edit'
which returns to Info mode for browsing.
\\{Info-edit-map}"
- )
+ (use-local-map Info-edit-map)
+ (setq major-mode 'Info-edit-mode)
+ (setq mode-name "Info Edit")
+ (kill-local-variable 'mode-line-buffer-identification)
+ (setq buffer-read-only nil)
+ (force-mode-line-update)
+ (buffer-enable-undo (current-buffer))
+ (run-hooks 'Info-edit-mode-hook))
(defun Info-edit ()
"Edit the contents of this Info node.
(interactive)
(or Info-enable-edit
(error "Editing info nodes is not enabled"))
- (use-local-map Info-edit-map)
- (setq major-mode 'Info-edit-mode)
- (setq mode-name "Info Edit")
- (kill-local-variable 'mode-line-buffer-identification)
- (setq buffer-read-only nil)
- ;; Make mode line update.
- (set-buffer-modified-p (buffer-modified-p))
- (message (substitute-command-keys
- "Editing: Type \\<Info-edit-map>\\[Info-cease-edit] to return to info")))
+ (Info-edit-mode)
+ (message "%s" (substitute-command-keys
+ "Editing: Type \\<Info-edit-map>\\[Info-cease-edit] to return to info")))
(defun Info-cease-edit ()
"Finish editing Info node; switch back to Info proper."
(setq mode-name "Info")
(Info-set-mode-line)
(setq buffer-read-only t)
- ;; Make mode line update.
- (set-buffer-modified-p (buffer-modified-p))
+ (force-mode-line-update)
(and (marker-position Info-tag-table-marker)
(buffer-modified-p)
(message "Tags may have changed. Use Info-tagify if necessary")))
\f
+(defvar Info-file-list-for-emacs
+ '("ediff" "forms" "gnus" "info" ("mh" . "mh-e") "sc")
+ "List of Info files that describe Emacs commands.
+An element can be a file name, or a list of the form (PREFIX . FILE)
+where PREFIX is a name prefix and FILE is the file to look in.
+If the element is just a file name, the file name also serves as the prefix.")
+
(defun Info-find-emacs-command-nodes (command)
- "Return a list of locations documenting COMMAND in the Emacs Info manual.
+ "Return a list of locations documenting COMMAND.
+The `info-file' property of COMMAND says which Info manual to search.
+If COMMAND has no property, the variable `Info-file-list-for-emacs'
+defines heuristics for which Info manual to try.
The locations are of the format used in Info-history, i.e.
\(FILENAME NODENAME BUFFERPOS\)."
- (require 'info)
(let ((where '())
(cmd-desc (concat "^\\* " (regexp-quote (symbol-name command))
- ":\\s *\\(.*\\)\\.$")))
+ ":\\s *\\(.*\\)\\.$"))
+ (info-file "emacs")) ;default
+ ;; Determine which info file this command is documented in.
+ (if (get command 'info-file)
+ (setq info-file (get command 'info-file))
+ ;; If it doesn't say explicitly, test its name against
+ ;; various prefixes that we know.
+ (let ((file-list Info-file-list-for-emacs))
+ (while file-list
+ (let* ((elt (car file-list))
+ (name (if (consp elt)
+ (car elt)
+ elt))
+ (file (if (consp elt) (cdr elt) elt))
+ (regexp (concat "\\`" (regexp-quote name)
+ "\\(\\'\\|-\\)")))
+ (if (string-match regexp (symbol-name command))
+ (setq info-file file file-list nil))
+ (setq file-list (cdr file-list))))))
(save-excursion
- (Info-find-node "emacs" "Command Index")
+ (condition-case nil
+ (Info-find-node info-file "Command Index")
+ ;; Some manuals may not have a separate Command Index node,
+ ;; so try just Index instead.
+ (error
+ (Info-find-node info-file "Index")))
;; Take the index node off the Info history.
(setq Info-history (cdr Info-history))
(goto-char (point-max))
;;;###autoload
(defun Info-goto-emacs-command-node (command)
"Go to the Info node in the Emacs manual for command COMMAND.
-The command is found by looking up in Emacs manual's Command Index."
+The command is found by looking up in Emacs manual's Command Index
+or in another manual found via COMMAND's `info-file' property or
+the variable `Info-file-list-for-emacs'."
(interactive "CFind documentation for command: ")
(or (commandp command)
(signal 'wrong-type-argument (list 'commandp command)))
;; Info-history. Put the other nodes that were found on
;; the history.
(setq Info-history (nconc (cdr where) Info-history))
- (message (substitute-command-keys
- "Found %d other entr%s. Use \\[Info-last] to see %s.")
+ (message "Found %d other entr%s. Use %s to see %s."
(1- num-matches)
(if (> num-matches 2) "ies" "y")
+ (substitute-command-keys "\\[Info-last]")
(if (> num-matches 2) "them" "it")))))
- (error "Couldn't find documentation for %s." command))))
+ (error "Couldn't find documentation for %s" command))))
;;;###autoload
(defun Info-goto-emacs-key-command-node (key)
"Go to the Info node in the Emacs manual the command bound to KEY, a string.
Interactively, if the binding is execute-extended-command, a command is read.
-The command is found by looking up in Emacs manual's Command Index."
+The command is found by looking up in Emacs manual's Command Index
+or in another manual found via COMMAND's `info-file' property or
+the variable `Info-file-list-for-emacs'."
(interactive "kFind documentation for key:")
(let ((command (key-binding key)))
(cond ((null command)
(t
(Info-goto-emacs-command-node command)))))
\f
+(defvar Info-title-face-alist
+ '((?* bold underline)
+ (?= bold-italic underline)
+ (?- italic underline))
+ "*Alist of face or list of faces to use for pseudo-underlined titles.
+The alist key is the character the title is underlined with (?*, ?= or ?-).")
+
(defun Info-fontify-node ()
(save-excursion
(let ((buffer-read-only nil))
(put-text-property (match-beginning 1) (match-end 1)
'mouse-face 'highlight))))
(goto-char (point-min))
- (while (re-search-forward "\\*Note[ \n\t]*\\([^:]*\\):" nil t)
+ (while (re-search-forward "\n\\([^ \t\n].+\\)\n\\(\\*+\\|=+\\|-+\\)$"
+ nil t)
+ (put-text-property (match-beginning 1) (match-end 1)
+ 'face
+ (cdr (assq (preceding-char) Info-title-face-alist)))
+ (put-text-property (match-end 1) (match-end 2)
+ 'invisible t))
+ (goto-char (point-min))
+ (while (re-search-forward "\\*Note[ \n\t]+\\([^:]*\\):" nil t)
(if (= (char-after (1- (match-beginning 0))) ?\") ; hack
nil
(put-text-property (match-beginning 1) (match-end 1)
(if (and (search-forward "\n* Menu:" nil t)
(not (string-match "\\<Index\\>" Info-current-node))
;; Don't take time to annotate huge menus
- (< (- (point-max) (point)) 30000))
+ (< (- (point-max) (point)) Info-fontify-maximum-menu-size))
(let ((n 0))
(while (re-search-forward "^\\* \\([^:\t\n]*\\):" nil t)
(setq n (1+ n))