home *** CD-ROM | disk | FTP | other *** search
- ;;;; info.jl -- Info browser
- ;;; Copyright (C) 1993, 1994 John Harper <jsh@ukc.ac.uk>
-
- ;;; This file is part of Jade.
-
- ;;; Jade 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 2, or (at your option)
- ;;; any later version.
-
- ;;; Jade is distributed in the hope that it will be useful, but
- ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
- ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;;; GNU General Public License for more details.
-
- ;;; You should have received a copy of the GNU General Public License
- ;;; along with Jade; see the file COPYING. If not, write to
- ;;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
-
- ;;; Limitations:
- ;;; - Depends wholly on tag tables --- does no searching for nodes just looks
- ;;; up their position.
- ;;; - No support for `*' node name.
- ;;; - No following of xrefs (yet)
- ;;; - Doesn't work 100% with info files formatted by emacs. For best results
- ;;; makeinfo has to be used.
- ;;; - No editing of nodes.
-
- (provide 'info)
-
- (defvar info-directory (if (amiga-p) "INFO:" "/usr/local/info/")
- "Directory to search for info files if they can't be found as-is.")
-
- (unless (boundp 'info-initialised)
- (put 'info-error 'error-message "Info")
- (setq
- info-keymap (make-keytab)
- info-buffer (make-buffer "*Info*")
- info-tags-buffer (make-buffer "*Info tags*")
- info-history nil ; List of `(FILE NODE POS)'
- info-file-name nil
- info-node-name nil
- info-file-modtime nil
- info-indirect-list nil ; List of `(START-OFFSET . FILE-NAME)'
- info-initialised t)
- (bind-keys info-keymap
- "space" 'next-screen
- "backspace" 'prev-screen
- "1" '(info-menu-nth 1)
- "2" '(info-menu-nth 2)
- "3" '(info-menu-nth 3)
- "4" '(info-menu-nth 4)
- "5" '(info-menu-nth 5)
- "b" 'goto-file-start
- "f" 'info-follow-ref
- "g" 'info-goto-node
- "l" 'info-last
- "m" 'info-menu
- "n" 'info-next
- "p" 'info-prev
- "q" 'info-quit
- "u" 'info-up)
- (set-buffer-special info-buffer t)
- (set-buffer-special info-tags-buffer t)
- (with-buffer info-buffer
- (setq keymap-path (cons info-keymap keymap-path))
- (set-buffer-read-only info-buffer t)))
-
- ;; Read the indirect list (if it exists) and tag table (must exist) from
- ;; the file FILENAME. Indirect list ends up in `info-indirect-list', tag
- ;; table is read into the `info-tags-buffer' buffer.
- (defun info-read-tags (filename)
- (let
- ((file (open filename "r"))
- (dir (path-name filename))
- str)
- (unless file
- (signal 'info-error (list "Can't open info file" filename)))
- (with-buffer info-tags-buffer
- (clear-buffer)
- (setq info-indirect-list nil)
- ;; Read until we find the tag table or the indirect list.
- (setq str (read-file-until file "^(Tag Table:|Indirect:) *\n$" t))
- (when (and str (regexp-match "Indirect" str t))
- ;; Parse the indirect list
- (while (and (setq str (read-line file))
- (not (string-head-eq str "\^_")))
- (setq info-indirect-list
- (cons
- (cons
- (read-from-string (regexp-expand "^.*: ([0-9]+)\n$" str "\\1"))
- (concat dir (regexp-expand "^(.*): [0-9]+\n$" str "\\1")))
- info-indirect-list)))
- (setq info-indirect-list (nreverse info-indirect-list))
- ;; Now look for the tag table
- (setq str (read-file-until file "^Tag Table: *\n$" t)))
- (if (and str (regexp-match "Tag Table" str t))
- (read-buffer file)
- (signal 'info-error '("No tag table in info file")))
- (setq
- info-file-name filename
- info-file-modtime (file-modtime filename))
- t)))
-
-
- ;; Record the file, node and cursor-position in the `info-history' list
- ;; for the `info-last' command.
- (defun info-remember ()
- (when (and info-file-name info-node-name)
- (setq info-history
- (cons (list info-file-name info-node-name (cursor-pos)) info-history))))
-
- ;; Display the node NODENAME. NODENAME can contain a file name. If no node
- ;; is specified go to `Top' node.
- ;; This depends on some magic for locating the node text. It only works 100%
- ;; with `makeinfo' generated files.
- (defun info-find-node (nodename)
- (let
- ((filename (regexp-expand "^\\((.*)\\).*$" nodename "\\1"))
- offset)
- (if filename
- ;; A filename was specified, first locate it, then if it's tags aren't
- ;; already loaded find them.
- (progn
- (cond
- ((file-exists-p filename))
- ((file-exists-p (concat filename ".info"))
- (setq filename (concat filename ".info")))
- ((file-exists-p (concat info-directory filename))
- (setq filename (concat info-directory filename)))
- ((file-exists-p (concat info-directory filename ".info"))
- (setq filename (concat info-directory filename ".info")))
- (t
- (signal 'info-error (list "Can't find file" filename))))
- (when (or (not (equal info-file-name filename))
- (> (file-modtime filename) info-file-modtime))
- (info-read-tags filename))
- ;; Get rid of the file spec.
- (unless (setq nodename (regexp-expand "^\\(.*\\)(.+)$" nodename "\\1"))
- (setq nodename "Top")))
- (when (and info-file-name (> (file-modtime info-file-name) info-file-modtime))
- (info-read-tags info-file-name)))
- (unless info-file-name
- (info-read-tags (concat info-directory "dir")))
- (let
- ((regexp (concat "^Node: " nodename ?\^?))
- subfile text)
- (if (find-next-regexp regexp (pos 1 1) info-tags-buffer t)
- (progn
- (setq offset (read (cons info-tags-buffer find-last-end-pos)))
- (if (null info-indirect-list)
- (setq
- offset (+ offset 2)
- subfile info-file-name)
- (catch 'info
- (let
- ((list info-indirect-list))
- (while (cdr list)
- (when (< offset (car (car (cdr list))))
- (setq subfile (car list))
- (throw 'info))
- (setq list (cdr list)))
- (setq subfile (car list))))
- ;; Use some magic to calculate the physical position of the
- ;; node. This seems to work?
- (if (eq subfile (car info-indirect-list))
- (setq offset (+ offset 2))
- (setq offset (+ (- offset (car subfile)) (car (car info-indirect-list)) 2)))
- (setq subfile (cdr subfile)))
- (if (setq text (read-file-from-to subfile offset ?\^_))
- (progn
- (set-buffer-read-only info-buffer nil)
- (clear-buffer)
- (insert text)
- (set-buffer-read-only info-buffer t)
- (goto-file-start)
- (setq info-node-name nodename)
- (setq mode-name (concat ?( (base-name info-file-name) ?) info-node-name)))
- (signal 'info-error (list "Can't read from file" filename))))
- (signal 'info-error (list "Can't find node" nodename))))))
-
- ;; Return a list of all node names matching START in the current tag table
- (defun info-list-nodes (start)
- (let
- ((regexp (concat "^Node: (" start ".*)\^?"))
- (list ()))
- (with-buffer info-tags-buffer
- (goto-file-start)
- (while (find-next-regexp regexp nil nil t)
- (goto find-last-end-pos)
- (setq list (cons (regexp-expand-line regexp "\\1" nil nil t) list))))
- list))
-
- ;; `prompt2' variant. LIST-FUN is a function to call the first time a list
- ;; of possible completions is required.
- (defun info-prompt (list-fun &optional title default start)
- (unless title
- (setq title "Select node"))
- (when default
- (setq title (concat title " (default: " default ")")))
- (unless start
- (setq start ""))
- (let
- (res
- prompt-list)
- (setq res (prompt2
- #'(lambda (w)
- (unless prompt-list
- (with-buffer info-buffer
- (setq prompt-list (funcall list-fun))))
- (prompt-complete-from-list w))
- title
- start))
- (if (equal res "")
- default
- res)))
-
- (defun info (&optional start-node)
- "(info [NODE-NAME])
- Start the Info viewer. If NODE-NAME is given it specifies the node to
- show, otherwise the current node is used (or `(dir)' if this is the first
- time that `info' has been called)."
- (info-remember)
- (goto-buffer info-buffer)
- (cond
- (start-node
- (info-find-node start-node))
- ((and info-file-name info-node-name))
- (t
- (info-find-node "(dir)"))))
-
- ;; Prompt for the name of a node and find it.
- (defun info-goto-node ()
- (let
- ((node (prompt "Goto node: ")))
- (when node
- (info-remember)
- (info-find-node node))))
-
- ;; Returns the node name of the menu item on the current line
- (defun info-parse-menu-line ()
- (or (regexp-expand-line "^\\* ([a-zA-Z0-9]+.*)::" "\\1")
- (regexp-expand-line "^\\* [a-zA-Z0-9]+.*: *(\\(.*\\).*)\\." "\\1")
- (regexp-expand-line "^\\* [a-zA-Z0-9]+.*: *(.*)\\." "\\1")))
-
- ;; Return a list of the names of all menu items. Starts searching from
- ;; the cursor position.
- (defun info-list-menu-items ()
- (let
- ((list ())
- (opos (cursor-pos)))
- (while (find-next-regexp "^\\* [a-zA-Z0-9]+.*:")
- (goto find-last-end-pos)
- (setq list (cons (regexp-expand-line "^\\* ([^:.]+)" "\\1") list)))
- list))
-
- ;; Position the cursor at the start of the menu.
- (defun info-goto-menu-start ()
- (when (or (find-prev-regexp "^\\* Menu: *$" nil nil t)
- (find-next-regexp "^\\* Menu: *$" nil nil t))
- (goto (next-line 1 find-last-start-pos))))
-
- ;; Goto the ITEM-INDEX'th menu item.
- (defun info-menu-nth (item-index)
- (unless (info-goto-menu-start)
- (signal 'info-error (list "Can't find menu")))
- (while (and (> item-index 0) (find-next-regexp "^\\* .*:"))
- (goto find-last-end-pos)
- (setq item-index (1- item-index)))
- (when (/= item-index 0)
- (signal 'info-error (list "Can't find menu node")))
- (goto-line-start)
- (let
- ((nodename (info-parse-menu-line)))
- (if nodename
- (progn
- (info-remember)
- (info-find-node nodename))
- (signal 'info-error (list "Menu line malformed")))))
-
- ;; Prompt for the name of a menu item (with a default) and find it's node.
- (defun info-menu ()
- (let
- ((menu-name (regexp-expand-line "^\\* ([^:.]+)" "\\1")))
- (when (info-goto-menu-start)
- (let
- ((opos (cursor-pos)))
- (setq menu-name (info-prompt 'info-list-menu-items "Menu item" menu-name))
- (goto opos)))
- (when menu-name
- (if (find-next-regexp (concat "^\\* " menu-name ?:))
- (progn
- (goto find-last-start-pos)
- (let
- ((node-name (info-parse-menu-line)))
- (if node-name
- (progn
- (info-remember)
- (info-find-node node-name))
- (signal 'info-error (list "Menu line malformed")))))
- (signal 'info-error (list "Can't find menu" menu-name))))))
-
- (defun info-follow-ref ()
- )
-
- ;; Retrace our steps one node.
- (defun info-last ()
- (if info-history
- (progn
- (let
- ((hist (car info-history)))
- (setq info-history (cdr info-history))
- (when (info-find-node (concat ?( (car hist) ?) (nth 1 hist)))
- (goto (nth 2 hist))
- t)))
- (title "No more history")
- (beep)))
-
- (defun info-next ()
- (info-find-link "Next"))
-
- (defun info-prev ()
- (info-find-link "Prev"))
-
- (defun info-up ()
- (info-find-link "Up"))
-
- (defun info-find-link (link-type)
- (let*
- ((regexp (concat link-type ": ([^,]*)(,| *$)"))
- (new-node (regexp-expand-line regexp "\\1" (pos 1 1) nil t)))
- (if new-node
- (progn
- (info-remember)
- (info-find-node new-node))
- (title (concat "No " link-type " node"))
- (beep))))
-
- (defun info-quit ()
- (kill-buffer info-buffer))
-