-;;; info.el --- info package for Emacs -- could use a "create node" feature.
+;;; info.el --- info package for Emacs.
-;; Copyright (C) 1985, 1986 Free Software Foundation, Inc.
+;; Copyright (C) 1985, 1986, 1992 Free Software Foundation, Inc.
+
+;; Maintainer: FSF
+;; Keywords: help
;; This file is part of GNU Emacs.
;; GNU Emacs is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
-;; the Free Software Foundation; either version 1, or (at your option)
+;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; along with GNU Emacs; see the file COPYING. If not, write to
;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
+;;; Commentary:
+
+;;; Note that nowadays we expect info files to be made using makeinfo.
+
+;;; Code:
+
(defvar Info-history nil
"List of info nodes user has visited.
Each element of list is a list (FILENAME NODENAME BUFFERPOS).")
(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 INFODIR to initialize it, or `Info-default-directory-list'
-if there is no INFODIR variable in the environment.")
+variable INFOPATH to initialize it, or `Info-default-directory-list'
+if there is no INFOPATH variable in the environment.")
(defvar Info-current-file nil
"Info file that Info is now looking at, or nil.")
(if filename
(let (temp temp-downcase found)
(setq filename (substitute-in-file-name filename))
- (let ((dirs (if (string-match "^\\./" filename)
- ;; If specified name starts with `./'
- ;; then just try current directory.
- '("./")
- 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.
- ;; Try downcasing, appending `.info', or both.
- (cond ((file-exists-p temp)
- (setq found temp))
- ((file-exists-p temp-downcase)
- (setq found temp-downcase))
- ((file-exists-p (concat temp ".info"))
- (setq found (concat temp ".info")))
- ((file-exists-p (concat temp-downcase ".info"))
- (setq found (concat temp-downcase ".info"))))
- (setq dirs (cdr dirs))))
+ (if (string= (downcase (file-name-nondirectory filename)) "dir")
+ (setq found t)
+ (let ((dirs (if (string-match "^\\./" filename)
+ ;; If specified name starts with `./'
+ ;; then just try current directory.
+ '("./")
+ 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.
+ ;; Try downcasing, appending `.info', or both.
+ (cond ((file-exists-p temp)
+ (setq found temp))
+ ((file-exists-p temp-downcase)
+ (setq found temp-downcase))
+ ((file-exists-p (concat temp ".info"))
+ (setq found (concat temp ".info")))
+ ((file-exists-p (concat temp-downcase ".info"))
+ (setq found (concat temp-downcase ".info"))))
+ (setq dirs (cdr dirs)))))
(if found
(setq filename found)
(error "Info file %s does not exist" filename))))
Info-history)))
;; Go into info buffer.
(switch-to-buffer "*info*")
- (buffer-flush-undo (current-buffer))
+ (buffer-disable-undo (current-buffer))
(or (eq major-mode 'Info-mode)
(Info-mode))
(widen)
Info-current-subfile nil
buffer-file-name nil)
(erase-buffer)
- (insert-file-contents filename t)
+ (if (eq filename t)
+ (Info-insert-dir)
+ (insert-file-contents filename t)
+ (setq default-directory (file-name-directory filename)))
(set-buffer-modified-p nil)
- (setq default-directory (file-name-directory filename))
;; See whether file has a tag table. Record the location if yes.
(set-marker Info-tag-table-marker nil)
(goto-char (point-max))
(save-excursion
(let ((buf (current-buffer)))
(set-buffer (get-buffer-create " *info tag table*"))
- (buffer-flush-undo (current-buffer))
+ (buffer-disable-undo (current-buffer))
(setq case-fold-search t)
(erase-buffer)
(insert-buffer-substring buf)
(set-marker Info-tag-table-marker
(match-end 0))))
- (set-marker Info-tag-table-marker pos))))
+ (set-marker Info-tag-table-marker pos))))
(setq Info-current-file
- (file-name-sans-versions buffer-file-name))))
+ (if (eq filename t) "dir"
+ (file-name-sans-versions buffer-file-name)))))
(if (equal nodename "*")
(progn (setq Info-current-node nodename)
(Info-set-mode-line))
;; 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)))
+ (let ((guesspos (point-min))
+ (regexp (concat "Node: *" (regexp-quote nodename) " *[,\t\n\177]")))
+ ;; 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.
(if (marker-position Info-tag-table-marker)
(save-excursion
(set-buffer (marker-buffer Info-tag-table-marker))
(goto-char Info-tag-table-marker)
- (if (search-forward (concat "Node: " nodename "\177") nil t)
+ (if (re-search-forward regexp nil t)
(progn
(setq guesspos (read (current-buffer)))
;; If this is an indirect file,
(setq guesspos
(Info-read-subfile guesspos))))
(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.
- (let ((regexp (concat "Node: *" (regexp-quote nodename) " *[,\t\n]")))
+ (goto-char (max (point-min) (- guesspos 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)
(goto-char (nth 2 hist)))))
(goto-char (point-min)))
+;; Cache the contents of the (virtual) dir file, once we have merged
+;; it for the first time, so we can save time subsequently.
+(defvar Info-dir-contents nil)
+
+;; Cache for the directory we decided to use for the default-directory
+;; of the merged dir text.
+(defvar Info-dir-contents-directory 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
+;; from.
+(defun Info-insert-dir ()
+ (if Info-dir-contents
+ (insert Info-dir-contents)
+ (let ((dirs Info-directory-list)
+ buffers buffer others nodes)
+
+ ;; Search the directory list for the directory file.
+ (while dirs
+ ;; 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)))))
+ (if buffer (setq buffers (cons buffer buffers)))
+ (setq dirs (cdr dirs))))
+
+ ;; Distinguish the dir file that comes with Emacs from all the
+ ;; others. [This sounds like baloney - who knows what order
+ ;; Info-directory-list is in, especially after checking the
+ ;; INFOPATH variable, and why should Emacs's dir be special? If
+ ;; you understand what this comment should have said, please
+ ;; change it.]
+ (setq buffer (car buffers)
+ others (cdr buffers))
+
+ ;; Insert the entire original dir file as a start; use its
+ ;; default directory as the default directory for the whole
+ ;; concatenation.
+ (insert-buffer buffer)
+ (setq Info-dir-contents-directory (save-excursion
+ (set-buffer buffer)
+ default-directory))
+
+ ;; Look at each of the other buffers one by one.
+ (while others
+ (let ((other (car others)))
+ ;; In each, find all the menus.
+ (save-excursion
+ (set-buffer other)
+ (goto-char (point-min))
+ ;; Find each menu, and add an elt to NODES for it.
+ (while (re-search-forward "^\\* Menu:" nil t)
+ (let (beg nodename end)
+ (forward-line 1)
+ (setq beg (point))
+ (search-backward "\n\1f")
+ (search-forward "Node: ")
+ (setq nodename (Info-following-node-name))
+ (search-forward "\n\1f" nil 'move)
+ (beginning-of-line)
+ (setq end (point))
+ (setq nodes (cons (list nodename other beg end) nodes))))))
+ (setq others (cdr others)))
+ ;; Add to the main menu a menu item for each other node.
+ (re-search-forward "^\\* Menu:")
+ (forward-line 1)
+ (let ((menu-items '("top"))
+ (nodes nodes)
+ (case-fold-search t)
+ (end (save-excursion (search-forward "\1f" nil t) (point))))
+ (while nodes
+ (let ((nodename (car (car nodes))))
+ (or (member (downcase nodename) menu-items)
+ (re-search-forward (concat "^\\* " (regexp-quote nodename) ":")
+ end t)
+ (progn
+ (insert "* " nodename "\n")
+ (setq menu-items (cons nodename menu-item)))))
+ (setq nodes (cdr nodes))))
+ ;; Now take each node of each of the other buffers
+ ;; and merge it into the main buffer.
+ (while nodes
+ (let ((nodename (car (car nodes))))
+ (goto-char (point-min))
+ ;; Find the like-named node in the main buffer.
+ (if (re-search-forward (concat "\n\1f.*\n.*Node: "
+ (regexp-quote nodename)
+ "[,\n\t]")
+ nil t)
+ (progn
+ (search-forward "\n\1f" nil 'move)
+ (beginning-of-line))
+ ;; If none exists, add one.
+ (goto-char (point-max))
+ (insert "\1f\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"))
+ (setq nodes (cdr nodes)))
+ ;; Kill all the buffers we just made.
+ (while buffers
+ (kill-buffer (car buffers))
+ (setq buffers (cdr buffers))))
+ (setq Info-dir-contents (buffer-string)))
+ (setq default-directory Info-dir-contents-directory))
+
(defun Info-read-subfile (nodepos)
(set-buffer (marker-buffer Info-tag-table-marker))
(goto-char (point-min))
nil
(error (concat "Node has no " (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
(aset str i ?\ ))
str))
-(defun Info-menu-item-sequence (list)
- (while list
- (Info-menu-item (car list))
- (setq list (cdr list))))
+;; No one calls this and Info-menu-item doesn't exist.
+;;(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.
nil))
(Info-extract-menu-node-name)))
-(defun Info-first-menu-item ()
- "Go to the node of the first menu item."
- (interactive)
- (Info-goto-node (Info-extract-menu-counting 1)))
-
-(defun Info-second-menu-item ()
- "Go to the node of the second menu item."
- (interactive)
- (Info-goto-node (Info-extract-menu-counting 2)))
-
-(defun Info-third-menu-item ()
- "Go to the node of the third menu item."
- (interactive)
- (Info-goto-node (Info-extract-menu-counting 3)))
-
-(defun Info-fourth-menu-item ()
- "Go to the node of the fourth menu item."
+(defun Info-nth-menu-item ()
+ "Go to the node of the Nth menu item.
+N is the digit argument used to invoke this command."
(interactive)
- (Info-goto-node (Info-extract-menu-counting 4)))
-
-(defun Info-fifth-menu-item ()
- "Go to the node of the fifth menu item."
- (interactive)
- (Info-goto-node (Info-extract-menu-counting 5)))
+ (Info-goto-node
+ (Info-extract-menu-counting
+ (- (aref (this-command-keys) (1- (length (this-command-keys)))) ?0))))
(defun Info-top-node ()
"Go to the Top node of this file."
(switch-to-buffer (prog1 (other-buffer (current-buffer))
(bury-buffer (current-buffer)))))
+(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))))
+
+(defun Info-last-menu-item ()
+ (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))))
+
+(defmacro 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."
+ (interactive)
+ (cond ((no-error (Info-next-menu-item)) )
+ ((no-error (Info-up)) (forward-line 1))
+ (t (error "No more nodes"))))
+
+(defun Info-last-preorder ()
+ "Go to the last node, popping up a level if there is none."
+ (interactive)
+ (cond ((no-error (Info-last-menu-item)) )
+ ((no-error (Info-up)) (forward-line -1))
+ (t (error "No previous nodes"))))
+
+(defun Info-scroll-up ()
+ "Read the next screen. If end of buffer is visible, go to next entry."
+ (interactive)
+ (if (pos-visible-in-window-p (point-max))
+ (Info-next-preorder)
+ (scroll-up))
+ )
+
+(defun Info-scroll-down ()
+ "Read the previous screen. If start of buffer is visible, go to last entry."
+ (interactive)
+ (if (pos-visible-in-window-p (point-min))
+ (Info-last-preorder)
+ (scroll-down))
+ )
+
(defun Info-undefined ()
"Make command be undefined in Info."
(interactive)
(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)
+ (if (not (eq ?\ (setq ch (read-event))))
+ (progn (setq unread-command-events (list ch)) nil)
flag))
(scroll-up)))))
\f
(error "No %s around position %d" errorstring pos))))))
(defun Info-follow-nearest-node (click)
- "\\<Info-mode-map>Follow a node reference near point. Like \\[Info-menu], \\Info-follow-reference], \\[Info-next], \\[Info-previous] or \\Info-up] command.
+ "\\<Info-mode-map>Follow a node reference near point.
+Like \\[Info-menu], \\[Info-follow-reference], \\[Info-next], \\[Info-prev] or \\[Info-up] command, depending on where you click.
At end of the node's text, moves to the next node."
- (interactive "K")
- (let* ((relative-coordinates (coordinates-in-window-p (mouse-coords click)
- (selected-window)))
- (rel-x (car relative-coordinates))
- (rel-y (cdr relative-coordinates)))
- (move-to-window-line rel-y)
- (move-to-column rel-x))
+ (interactive "e")
+ (let* ((start (event-start click))
+ (window (car start))
+ (pos (car (cdr start))))
+ (select-window window)
+ (goto-char pos))
(let (node)
(cond
- ((setq node (Info-get-token (point) "\\*note " "\\*note \\([^:]*\\):" t))
+ ((setq node (Info-get-token (point) "\\*note[ \n]" "\\*note[ \n]\\([^:]*\\):" t))
(Info-follow-reference node))
((setq node (Info-get-token (point) "\\* " "\\* \\([^:]*\\)::" t))
(Info-goto-node node))
(setq Info-mode-map (make-keymap))
(suppress-keymap Info-mode-map)
(define-key Info-mode-map "." 'beginning-of-buffer)
- (define-key Info-mode-map " " 'scroll-up)
- (define-key Info-mode-map "1" 'Info-first-menu-item)
- (define-key Info-mode-map "2" 'Info-second-menu-item)
- (define-key Info-mode-map "3" 'Info-third-menu-item)
- (define-key Info-mode-map "4" 'Info-fourth-menu-item)
- (define-key Info-mode-map "5" 'Info-fifth-menu-item)
- (define-key Info-mode-map "6" 'undefined)
- (define-key Info-mode-map "7" 'undefined)
- (define-key Info-mode-map "8" 'undefined)
- (define-key Info-mode-map "9" 'undefined)
+ (define-key Info-mode-map " " 'Info-scroll-up)
+ (define-key Info-mode-map "\C-m" 'Info-next-preorder)
+ (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 "6" 'Info-nth-menu-item)
+ (define-key Info-mode-map "7" 'Info-nth-menu-item)
+ (define-key Info-mode-map "8" 'Info-nth-menu-item)
+ (define-key Info-mode-map "9" 'Info-nth-menu-item)
(define-key Info-mode-map "0" 'undefined)
(define-key Info-mode-map "?" 'Info-summary)
(define-key Info-mode-map "]" 'Info-forward-node)
(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)
+ (define-key Info-mode-map "\177" 'Info-scroll-down)
+ (define-key Info-mode-map [mouse-3] 'Info-follow-nearest-node)
)
\f
;; Info mode is suitable only for specially formatted data.
\\[Info-last] Move to the last node you were at.
Moving within a node:
-\\[scroll-up] scroll forward a full screen. \\[scroll-down] scroll backward.
-\\[beginning-of-buffer] Go to beginning of node.
+\\[scroll-up] Normally, scroll forward a full screen. If the end of the buffer is
+already visible, try to go to the next menu entry, or up if there is none.
+\\[scroll-down] Normally, scroll backward. If the beginning of the buffer is
+already visible, try to go to the previous menu entry, or up if there is none.
+\\[beginning-of-buffer] Go to beginning of node.
Advanced commands:
\\[Info-exit] Quit Info: reselect previously selected buffer.
\\[Info-goto-node] Move to node specified by name.
You may include a filename as well, as (FILENAME)NODENAME.
\\[Info-search] Search through this Info file for specified regexp,
- and select the node in which the next occurrence is found."
+ 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."
(kill-all-local-variables)
(setq major-mode 'Info-mode)
(setq mode-name "Info")
(defun Info-edit-mode ()
"Major mode for editing the contents of an Info node.
-Like text mode with the addition of Info-cease-edit
+Like text mode with the addition of `Info-cease-edit'
which returns to Info mode for browsing.
\\{Info-edit-map}"
)
(interactive "kFind documentation for key:")
(let ((command (key-binding key)))
(cond ((null command)
- (message "%s is undefined" (key-description keys)))
+ (message "%s is undefined" (key-description key)))
((and (interactive-p)
(eq command 'execute-extended-command))
(Info-goto-emacs-command-node