mirror of
https://git.savannah.gnu.org/git/emacs.git
synced 2024-12-05 08:53:45 +00:00
1730 lines
64 KiB
EmacsLisp
1730 lines
64 KiB
EmacsLisp
;;; texnfo-upd.el --- a utility for updating nodes and menus in Texinfo files.
|
||
|
||
;;;; Copyright 1989, 1990, 1992 Free Software Foundation
|
||
|
||
;; Author: Bob Chassell <bob@gnu.ai.mit.edu>
|
||
;; Version: 2.00
|
||
;; Keywords: maint, tex, docs
|
||
|
||
;; 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 2, or (at your option)
|
||
;; any later version.
|
||
|
||
;; GNU Emacs 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 GNU Emacs; see the file COPYING. If not, write to
|
||
;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
|
||
|
||
;;; Commentary:
|
||
|
||
;;;; Summary
|
||
|
||
; (Much of the following commentary ought eventually be incorporated
|
||
; into the Texinfo Manual.)
|
||
|
||
; The node and menu updating functions automatically
|
||
|
||
; * insert missing `@node' lines,
|
||
; * insert the `Next', `Previous' and `Up' pointers of a node,
|
||
; * insert or update the menu for a section,
|
||
; * create a master menu for a Texinfo source file.
|
||
|
||
; Passed an argument, the `texinfo-update-node' and
|
||
; `texinfo-make-menu' functions do their jobs in the region.
|
||
|
||
; These functions replace doing these jobs by hand.
|
||
; You may find them helpful.
|
||
|
||
; In brief, the functions for creating or updating nodes and menus, are:
|
||
;
|
||
; texinfo-update-node (&optional region-p)
|
||
; texinfo-every-node-update ()
|
||
; texinfo-sequential-node-update (&optional region-p)
|
||
;
|
||
; texinfo-make-menu (&optional region-p)
|
||
; texinfo-all-menus-update ()
|
||
; texinfo-master-menu ()
|
||
;
|
||
; texinfo-insert-node-lines (&optional title-p)
|
||
;
|
||
; texinfo-indent-menu-description (column &optional region-p)
|
||
|
||
; The `texinfo-column-for-description' variable specifies the column to
|
||
; which menu descriptions are indented.
|
||
|
||
; Texinfo file structure
|
||
; ----------------------
|
||
|
||
; To use the updating commands, you must structure your Texinfo file
|
||
; hierarchically. Each `@node' line, with the exception of the top
|
||
; node, must be accompanied by some kind of section line, such as an
|
||
; `@chapter' or `@section' line. Each node-line/section-line
|
||
; combination must look like this:
|
||
|
||
; @node Lists and Tables, Cross References, Structuring, Top
|
||
; @comment node-name, next, previous, up
|
||
; @chapter Making Lists and Tables
|
||
|
||
; or like this (without the `@comment' line):
|
||
|
||
; @node Lists and Tables, Cross References, Structuring, Top
|
||
; @chapter Making Lists and Tables
|
||
|
||
; If the file has a `top' node, it must be called `top' or `Top' and
|
||
; be the first node in the file.
|
||
|
||
|
||
;;;; The updating functions in detail
|
||
; --------------------------------
|
||
|
||
; The `texinfo-update-node' function without an argument inserts
|
||
; the correct next, previous and up pointers for the node in which
|
||
; point is located (i.e., for the node preceding point).
|
||
|
||
; With an argument, the `texinfo-update-node' function inserts the
|
||
; correct next, previous and up pointers for the nodes inside the
|
||
; region.
|
||
|
||
; It does not matter whether the `@node' line has pre-existing
|
||
; `Next', `Previous', or `Up' pointers in it. They are removed.
|
||
|
||
; The `texinfo-every-node-update' function runs `texinfo-update-node'
|
||
; on the whole buffer.
|
||
|
||
; The `texinfo-update-node' function inserts the immediately following
|
||
; and preceding node into the `Next' or `Previous' pointers regardless
|
||
; of their hierarchical level. This is only useful for certain kinds
|
||
; of text, like a novel, which you go through sequentially.
|
||
|
||
; The `texinfo-make-menu' function without an argument creates or
|
||
; updates a menu for the section encompassing the node that follows
|
||
; point. With an argument, it makes or updates menus for the nodes
|
||
; within or part of the marked region.
|
||
|
||
; Whenever an existing menu is updated, the descriptions from
|
||
; that menu are incorporated into the new menu. This is done by copying
|
||
; descriptions from the existing menu to the entries in the new menu
|
||
; that have the same node names. If the node names are different, the
|
||
; descriptions are not copied to the new menu.
|
||
|
||
; Menu entries that refer to other Info files are removed since they
|
||
; are not a node within current buffer. This is a deficiency.
|
||
|
||
; The `texinfo-all-menus-update' function runs `texinfo-make-menu'
|
||
; on the whole buffer.
|
||
|
||
; The `texinfo-master-menu' function creates an extended menu located
|
||
; after the top node. (The file must have a top node.) The function
|
||
; first updates all the regular menus in the buffer (incorporating the
|
||
; descriptions from pre-existing menus), and then constructs a master
|
||
; menu that includes every entry from every other menu. (However, the
|
||
; function cannot update an already existing master menu; if one
|
||
; exists, it must be removed before calling the function.)
|
||
|
||
; The `texinfo-indent-menu-description' function indents every
|
||
; description in the menu following point, to the specified column.
|
||
; Non-nil argument (prefix, if interactive) means indent every
|
||
; description in every menu in the region. This function does not
|
||
; indent second and subsequent lines of a multi-line description.
|
||
|
||
; The `texinfo-insert-node-lines' function inserts `@node' before the
|
||
; `@chapter', `@section', and such like lines of a region in a Texinfo
|
||
; file where the `@node' lines are missing.
|
||
;
|
||
; With a non-nil argument (prefix, if interactive), the function not
|
||
; only inserts `@node' lines but also inserts the chapter or section
|
||
; titles as the names of the corresponding nodes; and inserts titles
|
||
; as node names in pre-existing `@node' lines that lack names.
|
||
;
|
||
; Since node names should be more concise than section or chapter
|
||
; titles, node names so inserted will need to be edited manually.
|
||
|
||
;;; Code:
|
||
|
||
;;;; Menu Making Functions
|
||
|
||
(defun texinfo-make-menu (&optional region-p)
|
||
"Without any prefix argument, make or update a menu.
|
||
Make the menu for the section enclosing the node found following point.
|
||
|
||
Non-nil argument (prefix, if interactive) means make or update menus
|
||
for nodes within or part of the marked region.
|
||
|
||
Whenever a menu exists, and is being updated, the descriptions that
|
||
are associated with node names in the pre-existing menu are
|
||
incorporated into the new menu. Otherwise, the nodes' section titles
|
||
are inserted as descriptions."
|
||
|
||
(interactive "P")
|
||
(if (not region-p)
|
||
(let ((level (texinfo-hierarchic-level)))
|
||
(texinfo-make-one-menu level)
|
||
(message "Done...updated the menu. You may save the buffer."))
|
||
;; else
|
||
(message "Making or updating menus... ")
|
||
(let ((beginning (region-beginning))
|
||
(region-end (region-end))
|
||
(level (progn ; find section type following point
|
||
(goto-char (region-beginning))
|
||
(texinfo-hierarchic-level))))
|
||
(if (= region-end beginning)
|
||
(error "Please mark a region!"))
|
||
(save-excursion
|
||
(save-restriction
|
||
(widen)
|
||
|
||
(while (texinfo-find-lower-level-node level region-end)
|
||
(setq level (texinfo-hierarchic-level)) ; new, lower level
|
||
(texinfo-make-one-menu level))
|
||
|
||
(while (and (< (point) region-end)
|
||
(texinfo-find-higher-level-node level region-end))
|
||
(setq level (texinfo-hierarchic-level))
|
||
(while (texinfo-find-lower-level-node level region-end)
|
||
(setq level (texinfo-hierarchic-level)) ; new, lower level
|
||
(texinfo-make-one-menu level))))))
|
||
(message "Done...updated menus. You may save the buffer.")))
|
||
|
||
(defun texinfo-make-one-menu (level)
|
||
"Make a menu of all the appropriate nodes in this section.
|
||
`Appropriate nodes' are those associated with sections that are
|
||
at the level specified by LEVEL. Point is left at the end of menu."
|
||
(let*
|
||
((case-fold-search t)
|
||
(beginning
|
||
(save-excursion
|
||
(goto-char (texinfo-update-menu-region-beginning level))
|
||
(end-of-line)
|
||
(point)))
|
||
(end (texinfo-update-menu-region-end level))
|
||
(first (texinfo-menu-first-node beginning end))
|
||
(node-name (progn
|
||
(goto-char beginning)
|
||
(texinfo-copy-node-name)))
|
||
(new-menu-list (texinfo-make-menu-list beginning end level)))
|
||
(if (texinfo-old-menu-p beginning first)
|
||
(progn
|
||
(texinfo-incorporate-descriptions new-menu-list)
|
||
(texinfo-delete-old-menu beginning first)))
|
||
(texinfo-insert-menu new-menu-list node-name)))
|
||
|
||
(defun texinfo-all-menus-update (&optional update-all-nodes-p)
|
||
"Update every regular menu in a Texinfo file.
|
||
You must remove the detailed part of a pre-existing master menu before
|
||
running this command, lest it be partly duplicated.
|
||
|
||
If called with a non-nil argument, this function first updates all the
|
||
nodes in the buffer before updating the menus."
|
||
(interactive "P")
|
||
(save-excursion
|
||
(mark-whole-buffer)
|
||
(message "Checking for a master menu... ")
|
||
(save-excursion
|
||
(if (re-search-forward texinfo-master-menu-header nil t)
|
||
(error
|
||
"Please remove existing master menu, lest it be partly duplicated!")))
|
||
|
||
(if update-all-nodes-p
|
||
(progn
|
||
(message "First updating all nodes... ")
|
||
(sleep-for 2)
|
||
(mark-whole-buffer)
|
||
(texinfo-update-node t)))
|
||
|
||
(message "Updating all menus... ")
|
||
(sleep-for 2)
|
||
(texinfo-make-menu t)
|
||
(message "Done...updated all the menus. You may save the buffer.")))
|
||
|
||
(defun texinfo-find-lower-level-node (level region-end)
|
||
"Search forward from point for node at any level lower than LEVEL.
|
||
Search is limited to the end of the marked region, REGION-END,
|
||
and to the end of the menu region for the level.
|
||
|
||
Return t if the node is found, else nil. Leave point at the beginning
|
||
of the node if one is found; else do not move point."
|
||
|
||
(if (and (< (point) region-end)
|
||
(re-search-forward
|
||
(concat
|
||
"\\(^@node\\).*\n" ; match node line
|
||
"\\(\\(\\(^@c\\).*\n\\)" ; match comment line, if any
|
||
"\\|" ; or
|
||
"\\(^@ifinfo[ ]*\n\\)\\)?" ; ifinfo line, if any
|
||
(eval (cdr (assoc level texinfo-update-menu-lower-regexps))))
|
||
;; the next higher level node marks the end of this
|
||
;; section, and no lower level node will be found beyond
|
||
;; this position even if region-end is farther off
|
||
(texinfo-update-menu-region-end level)
|
||
t))
|
||
(goto-char (match-beginning 1))))
|
||
|
||
(defun texinfo-find-higher-level-node (level region-end)
|
||
"Search forward from point for node at any higher level than argument LEVEL.
|
||
Search is limited to the end of the marked region, REGION-END.
|
||
|
||
Return t if the node is found, else nil. Leave point at the beginning
|
||
of the node if one is found; else do not move point."
|
||
|
||
(if (and (< (point) region-end)
|
||
(re-search-forward
|
||
(concat
|
||
"\\(^@node\\).*\n" ; match node line
|
||
"\\(\\(\\(^@c\\).*\n\\)" ; match comment line, if any
|
||
"\\|" ; or
|
||
"\\(^@ifinfo[ ]*\n\\)\\)?" ; ifinfo line, if any
|
||
(eval ; (won't ever find a `top' node)
|
||
(cdr (assoc level texinfo-update-menu-higher-regexps))))
|
||
nil
|
||
t))
|
||
(goto-char (match-beginning 1))))
|
||
|
||
|
||
;;;; Making the list of new menu entries
|
||
|
||
(defun texinfo-make-menu-list (beginning end level)
|
||
"Make a list of node names and their descriptions.
|
||
Point is left at the end of the menu region, but the menu is not inserted.
|
||
|
||
First argument is position from which to start making menu list;
|
||
second argument is end of region in which to try to locate entries;
|
||
third argument is the level of the nodes that are the entries.
|
||
|
||
Node names and descriptions are dotted pairs of strings. Each pair is
|
||
an element of the list. If the description does not exist, the
|
||
element consists only of the node name."
|
||
(goto-char beginning)
|
||
(let (new-menu-list)
|
||
(while (texinfo-menu-locate-entry-p level end)
|
||
(setq new-menu-list
|
||
(cons (cons
|
||
(texinfo-copy-node-name)
|
||
(texinfo-copy-section-title))
|
||
new-menu-list)))
|
||
(reverse new-menu-list)))
|
||
|
||
(defun texinfo-menu-locate-entry-p (level search-end)
|
||
"Find a node that will be part of menu for this section.
|
||
First argument is a string such as \"section\" specifying the general
|
||
hierarchical level of the menu; second argument is a postion
|
||
specifying the end of the search.
|
||
|
||
The function returns t if the node is found, else nil. It searches
|
||
forward from point, and leaves point at the beginning of the node.
|
||
|
||
The function finds entries of the same type. Thus `subsections' and
|
||
`unnumberedsubsecs' will appear in the same menu."
|
||
(if (re-search-forward
|
||
(concat
|
||
"\\(^@node\\).*\n" ; match node line
|
||
"\\(\\(\\(^@c\\).*\n\\)" ; match comment line, if any
|
||
"\\|" ; or
|
||
"\\(^@ifinfo[ ]*\n\\)\\)?" ; ifinfo line, if any
|
||
(eval
|
||
(cdr (assoc level texinfo-update-menu-same-level-regexps))))
|
||
search-end
|
||
t)
|
||
(goto-char (match-beginning 1))))
|
||
|
||
(defun texinfo-copy-node-name ()
|
||
"Return the node name as a string.
|
||
|
||
Start with point at the beginning of the node line; copy the text
|
||
after the node command up to the first comma on the line, if any, and
|
||
return the text as a string. Leaves point at the beginning of the
|
||
line. If there is no node name, returns an empty string."
|
||
|
||
(save-excursion
|
||
(buffer-substring
|
||
(progn (forward-word 1) ; skip over node command
|
||
(skip-chars-forward " \t") ; and over spaces
|
||
(point))
|
||
(if (search-forward
|
||
","
|
||
(save-excursion (end-of-line) (point)) t) ; bound search
|
||
(1- (point))
|
||
(end-of-line) (point)))))
|
||
|
||
(defun texinfo-copy-section-title ()
|
||
"Return the title of the section as a string.
|
||
The title is used as a description line in the menu when one does not
|
||
already exist.
|
||
|
||
Move point to the beginning of the appropriate section line by going
|
||
to the start of the text matched by last regexp searched for, which
|
||
must have been done by `texinfo-menu-locate-entry-p'."
|
||
|
||
;; could use the same re-search as in `texinfo-menu-locate-entry-p'
|
||
;; instead of using `match-beginning'; such a variation would be
|
||
;; more general, but would waste information already collected
|
||
|
||
(goto-char (match-beginning 7)) ; match section name
|
||
|
||
(buffer-substring
|
||
(progn (forward-word 1) ; skip over section type
|
||
(skip-chars-forward " \t") ; and over spaces
|
||
(point))
|
||
(progn (end-of-line) (point))))
|
||
|
||
|
||
;;;; Handling the old menu
|
||
|
||
(defun texinfo-old-menu-p (beginning first)
|
||
"Move point to the beginning of the menu for this section, if any.
|
||
Otherwise move point to the end of the first node of this section.
|
||
Return t if a menu is found, nil otherwise.
|
||
|
||
First argument is the position of the beginning of the section in which
|
||
the menu will be located; second argument is the position of the first
|
||
node within the section.
|
||
|
||
If no menu is found, the function inserts two newlines just before the
|
||
end of the section, and leaves point there where a menu ought to be."
|
||
(goto-char beginning)
|
||
(if (not (re-search-forward "^@menu" first 'goto-end))
|
||
(progn (insert "\n\n") (forward-line -2) nil)
|
||
t))
|
||
|
||
(defun texinfo-incorporate-descriptions (new-menu-list)
|
||
"Copy the old menu line descriptions that exist to the new menu.
|
||
|
||
Point must be at beginning of old menu.
|
||
|
||
If the node-name of the new menu entry cannot be found in the old
|
||
menu, use the new section title for the description, but if the
|
||
node-name of the new menu is found in the old menu, replace the
|
||
section title with the old description, whatever it may be.
|
||
|
||
For this function, the new menu is a list made up of lists of dotted
|
||
pairs in which the first element of the pair is the node name and the
|
||
second element the description. The new menu is changed destructively.
|
||
The old menu is the menu as it appears in the texinfo file."
|
||
|
||
(let ((new-menu-list-pointer new-menu-list)
|
||
(end-of-menu (texinfo-menu-end)))
|
||
(while new-menu-list
|
||
(save-excursion ; keep point at beginning of menu
|
||
(if (search-forward
|
||
(concat "\* " ; so only menu entries are found
|
||
(car (car new-menu-list))
|
||
":") ; so only complete entries are found
|
||
end-of-menu
|
||
t)
|
||
(setcdr (car new-menu-list)
|
||
(texinfo-menu-copy-old-description end-of-menu))))
|
||
(setq new-menu-list (cdr new-menu-list)))
|
||
(setq new-menu-list new-menu-list-pointer)))
|
||
|
||
(defun texinfo-menu-copy-old-description (end-of-menu)
|
||
"Return description field of old menu line as string.
|
||
Point must be located just after the node name. Point left before description.
|
||
Single argument, END-OF-MENU, is position limiting search."
|
||
(skip-chars-forward "[:.,\t\n ]+")
|
||
;; don't copy a carriage return at line beginning with asterisk!
|
||
;; do copy a description that begins with an `@'!
|
||
(if (and (looking-at "\\(\\w+\\|@\\)")
|
||
(not (looking-at "\\(^\\* \\|^@end menu\\)")))
|
||
(buffer-substring
|
||
(point)
|
||
(save-excursion
|
||
(re-search-forward "\\(^\\* \\|^@end menu\\)" end-of-menu t)
|
||
(forward-line -1)
|
||
(end-of-line) ; go to end of last description line
|
||
(point)))
|
||
""))
|
||
|
||
(defun texinfo-menu-end ()
|
||
"Return position of end of menu. Does not change location of point.
|
||
Signal an error if not end of menu."
|
||
(save-excursion
|
||
(if (re-search-forward "^@end menu" nil t)
|
||
(point)
|
||
(error "Menu does not have an end."))))
|
||
|
||
(defun texinfo-delete-old-menu (beginning first)
|
||
"Delete the old menu. Point must be in or after menu.
|
||
First argument is position of the beginning of the section in which
|
||
the menu will be located; second argument is the position of the first
|
||
node within the section."
|
||
;; No third arg to search, so error if search fails.
|
||
(re-search-backward "^@menu" beginning)
|
||
(delete-region (point)
|
||
(save-excursion
|
||
(re-search-forward "^@end menu" first)
|
||
(point))))
|
||
|
||
|
||
;;;; Inserting new menu
|
||
|
||
;; try 32, but perhaps 24 is better
|
||
(defvar texinfo-column-for-description 32
|
||
"*Column at which descriptions start in a Texinfo menu.")
|
||
|
||
(defun texinfo-insert-menu (menu-list node-name)
|
||
"Insert formatted menu at point.
|
||
Indents the first line of the description, if any, to the value of
|
||
texinfo-column-for-description.
|
||
|
||
MENU-LIST has form:
|
||
|
||
\(\(\"node-name1\" . \"description\"\)
|
||
\(\"node-name\" . \"description\"\) ... \)
|
||
|
||
However, there does not need to be a description field."
|
||
|
||
(insert "@menu\n")
|
||
(while menu-list
|
||
(if (cdr (car menu-list)) ; menu-list has description entry
|
||
(progn
|
||
(insert
|
||
(format "* %s::" (car (car menu-list)))) ; node-name entry
|
||
(indent-to texinfo-column-for-description 2)
|
||
(insert
|
||
(format "%s\n" (cdr (car menu-list))))) ; description entry
|
||
;; else menu-list lacks description entry
|
||
(insert
|
||
(format "* %s::\n" (car (car menu-list))))) ; node-name entry
|
||
(setq menu-list (cdr menu-list)))
|
||
(insert "@end menu")
|
||
(message
|
||
"Updated \"%s\" level menu following node: %s ... "
|
||
level node-name))
|
||
|
||
|
||
;;;; Handling description indentation
|
||
|
||
; Since the make-menu functions indent descriptions, these functions
|
||
; are useful primarily for indenting a single menu specially.
|
||
|
||
(defun texinfo-indent-menu-description (column &optional region-p)
|
||
"Indent every description in menu following point to COLUMN.
|
||
Non-nil argument (prefix, if interactive) means indent every
|
||
description in every menu in the region. Does not indent second and
|
||
subsequent lines of a multi-line description."
|
||
|
||
(interactive
|
||
"nIndent menu descriptions to (column number): \nP")
|
||
(save-excursion
|
||
(save-restriction
|
||
(widen)
|
||
(if (not region-p)
|
||
(progn
|
||
(re-search-forward "^@menu")
|
||
(texinfo-menu-indent-description column)
|
||
(message
|
||
"Indented descriptions in menu. You may save the buffer."))
|
||
;;else
|
||
(message "Indenting every menu description in region... ")
|
||
(goto-char (region-beginning))
|
||
(while (and (< (point) (region-end))
|
||
(texinfo-locate-menu-p))
|
||
(forward-line 1)
|
||
(texinfo-menu-indent-description column))
|
||
(message "Indenting done. You may save the buffer.")))))
|
||
|
||
(defun texinfo-menu-indent-description (to-column-number)
|
||
"Indent the Texinfo file menu description to TO-COLUMN-NUMBER.
|
||
Start with point just after the word `menu' in the `@menu' line and
|
||
leave point on the line before the `@end menu' line. Does not indent
|
||
second and subsequent lines of a multi-line description."
|
||
(let* ((beginning-of-next-line (point)))
|
||
(while (< beginning-of-next-line
|
||
(save-excursion ; beginning of end menu line
|
||
(goto-char (texinfo-menu-end))
|
||
(beginning-of-line)
|
||
(point)))
|
||
(if (search-forward "::" (texinfo-menu-end) t)
|
||
(progn
|
||
(let ((beginning-white-space (point)))
|
||
(skip-chars-forward " \t") ; skip over spaces
|
||
(if (looking-at "\\(@\\|\\w\\)+") ; if there is text
|
||
(progn
|
||
;; remove pre-existing indentation
|
||
(delete-region beginning-white-space (point))
|
||
(indent-to-column to-column-number))))))
|
||
;; position point at beginning of next line
|
||
(forward-line 1)
|
||
(setq beginning-of-next-line (point)))))
|
||
|
||
|
||
;;;; Making the master menu
|
||
|
||
(defun texinfo-master-menu (update-all-nodes-menus-p)
|
||
"Make a master menu for a whole Texinfo file.
|
||
Non-nil argument (prefix, if interactive) means first update all
|
||
existing nodes and menus. Remove pre-existing master menu, if there is one.
|
||
|
||
This function creates a master menu that follows the top node. The
|
||
master menu includes every entry from all the other menus. It
|
||
replaces any existing ordinary menu that follows the top node.
|
||
|
||
If called with a non-nil argument, this function first updates all the
|
||
menus in the buffer (incorporating descriptions from pre-existing
|
||
menus) before it constructs the master menu.
|
||
|
||
The function removes the detailed part of an already existing master
|
||
menu. This action depends on the pre-exisitng master menu using the
|
||
standard `texinfo-master-menu-header'.
|
||
|
||
The master menu has the following format, which is adapted from the
|
||
recommendation in the Texinfo Manual:
|
||
|
||
* The first part contains the major nodes in the Texinfo file: the
|
||
nodes for the chapters, chapter-like sections, and the major
|
||
appendices. This includes the indices, so long as they are in
|
||
chapter-like sections, such as unnumbered sections.
|
||
|
||
* The second and subsequent parts contain a listing of the other,
|
||
lower level menus, in order. This way, an inquirer can go
|
||
directly to a particular node if he or she is searching for
|
||
specific information.
|
||
|
||
Each of the menus in the detailed node listing is introduced by the
|
||
title of the section containing the menu."
|
||
|
||
(interactive "P")
|
||
(widen)
|
||
(goto-char (point-min))
|
||
|
||
;; Move point to location after `top'.
|
||
(if (not (re-search-forward "^@node [ \t]*top[ \t]*\\(,\\|$\\)" nil t))
|
||
(error "This buffer needs a Top node!"))
|
||
|
||
(let ((first-chapter
|
||
(save-excursion (re-search-forward "^@node") (point))))
|
||
(if (re-search-forward texinfo-master-menu-header first-chapter t)
|
||
;; Remove detailed master menu listing
|
||
(progn
|
||
(goto-char (match-beginning 0))
|
||
(let ((end-of-detailed-menu-descriptions
|
||
(save-excursion ; beginning of end menu line
|
||
(goto-char (texinfo-menu-end))
|
||
(beginning-of-line) (forward-char -1)
|
||
(point))))
|
||
(delete-region (point) end-of-detailed-menu-descriptions)))))
|
||
|
||
(if update-all-nodes-menus-p
|
||
(progn
|
||
(message "Making a master menu...first updating all nodes... ")
|
||
(sleep-for 2)
|
||
(mark-whole-buffer)
|
||
(texinfo-update-node t)
|
||
|
||
(message "Updating all menus... ")
|
||
(sleep-for 2)
|
||
(mark-whole-buffer)
|
||
(texinfo-make-menu t)))
|
||
|
||
(message "Now making the master menu... ")
|
||
(sleep-for 2)
|
||
(goto-char (point-min))
|
||
(texinfo-insert-master-menu-list
|
||
(texinfo-master-menu-list))
|
||
|
||
;; Remove extra newlines that texinfo-insert-master-menu-list
|
||
;; may have inserted.
|
||
|
||
(save-excursion
|
||
(goto-char (point-min))
|
||
|
||
(re-search-forward texinfo-master-menu-header)
|
||
(goto-char (match-beginning 0))
|
||
(insert "\n")
|
||
(delete-blank-lines)
|
||
|
||
(re-search-backward "^@menu")
|
||
(forward-line -1)
|
||
(delete-blank-lines)
|
||
|
||
(re-search-forward "^@end menu")
|
||
(forward-line 1)
|
||
(delete-blank-lines))
|
||
|
||
(message "Done...completed making master menu. You may save the buffer."))
|
||
|
||
(defun texinfo-master-menu-list ()
|
||
"Return a list of menu entries and header lines for the master menu.
|
||
|
||
Start with the menu for chapters and indices and then find each
|
||
following menu and the title of the node preceding that menu.
|
||
|
||
The master menu list has this form:
|
||
|
||
\(\(\(... \"entry-1-2\" \"entry-1\"\) \"title-1\"\)
|
||
\(\(... \"entry-2-2\" \"entry-2-1\"\) \"title-2\"\)
|
||
...\)
|
||
|
||
However, there does not need to be a title field."
|
||
|
||
(let (master-menu-list)
|
||
(while (texinfo-locate-menu-p)
|
||
(setq master-menu-list
|
||
(cons (list
|
||
(texinfo-copy-menu)
|
||
(texinfo-copy-menu-title))
|
||
master-menu-list)))
|
||
(reverse master-menu-list)))
|
||
|
||
(defun texinfo-insert-master-menu-list (master-menu-list)
|
||
"Format and insert the master menu in the current buffer."
|
||
(goto-char (point-min))
|
||
(re-search-forward "^@menu")
|
||
(beginning-of-line)
|
||
(delete-region (point) ; buffer must have ordinary top menu
|
||
(save-excursion
|
||
(re-search-forward "^@end menu")
|
||
(point)))
|
||
|
||
(save-excursion ; leave point at beginning of menu
|
||
;; Handle top of menu
|
||
(insert "\n@menu\n")
|
||
;; Insert chapter menu entries
|
||
(setq this-very-menu-list (reverse (car (car master-menu-list))))
|
||
;;; Tell user what is going on.
|
||
(message "Inserting chapter menu entry: %s ... " this-very-menu-list)
|
||
(while this-very-menu-list
|
||
(insert "* " (car this-very-menu-list) "\n")
|
||
(setq this-very-menu-list (cdr this-very-menu-list)))
|
||
|
||
(setq master-menu-list (cdr master-menu-list))
|
||
|
||
(insert texinfo-master-menu-header)
|
||
|
||
;; Now, insert all the other menus
|
||
|
||
;; The menu master-menu-list has a form like this:
|
||
;; ((("beta" "alpha") "title-A")
|
||
;; (("delta" "gamma") "title-B"))
|
||
|
||
(while master-menu-list
|
||
|
||
(message
|
||
"Inserting menu for %s .... " (car (cdr (car master-menu-list))))
|
||
;; insert title of menu section
|
||
(insert "\n" (car (cdr (car master-menu-list))) "\n\n")
|
||
|
||
;; insert each menu entry
|
||
(setq this-very-menu-list (reverse (car (car master-menu-list))))
|
||
(while this-very-menu-list
|
||
(insert "* " (car this-very-menu-list) "\n")
|
||
(setq this-very-menu-list (cdr this-very-menu-list)))
|
||
|
||
(setq master-menu-list (cdr master-menu-list)))
|
||
|
||
;; Finish menu
|
||
(insert "@end menu\n\n")))
|
||
|
||
(defvar texinfo-master-menu-header
|
||
"\n --- The Detailed Node Listing ---\n"
|
||
"String inserted before lower level entries in Texinfo master menu.
|
||
It comes after the chapter-level menu entries.")
|
||
|
||
(defun texinfo-locate-menu-p ()
|
||
"Find the next menu in the texinfo file.
|
||
If found, leave point after word `menu' on the `@menu' line, and return t.
|
||
If a menu is not found, do not move point and return nil."
|
||
(re-search-forward "\\(^@menu\\)" nil t))
|
||
|
||
(defun texinfo-copy-menu-title ()
|
||
"Return the title of the section preceding the menu as a string.
|
||
If such a title cannot be found, return an empty string. Do not move
|
||
point."
|
||
(save-excursion
|
||
(if (re-search-backward
|
||
(concat
|
||
"\\(^@node\\).*\n" ; match node line
|
||
"\\(\\(\\(^@c\\).*\n\\)" ; match comment line, if any
|
||
"\\|" ; or
|
||
"\\(^@ifinfo[ ]*\n\\)\\)?" ; ifinfo line, if any
|
||
(eval
|
||
(cdr
|
||
(assoc (texinfo-hierarchic-level)
|
||
texinfo-update-menu-higher-regexps))))
|
||
nil
|
||
t)
|
||
(texinfo-copy-section-title)
|
||
" ")))
|
||
|
||
(defun texinfo-copy-menu ()
|
||
"Return the entries of an existing menu as a list.
|
||
Start with point just after the word `menu' in the `@menu' line
|
||
and leave point on the line before the `@end menu' line."
|
||
(let* (this-menu-list
|
||
(end-of-menu (texinfo-menu-end)) ; position of end of `@end menu'
|
||
(last-entry (save-excursion ; position of beginning of
|
||
; last `* ' entry
|
||
(goto-char end-of-menu)
|
||
(re-search-backward "^\* ") ; handle multi-line desc.
|
||
(point))))
|
||
(while (< (point) last-entry)
|
||
(if (re-search-forward "^\* " end-of-menu t)
|
||
(progn
|
||
(setq this-menu-list
|
||
(cons
|
||
(buffer-substring
|
||
(point)
|
||
;; copy multi-line descriptions
|
||
(save-excursion
|
||
(re-search-forward "\\(^\* \\|^@e\\)" nil t)
|
||
(- (point) 3)))
|
||
this-menu-list)))))
|
||
this-menu-list))
|
||
|
||
|
||
;;;; Determining the hierarchical level in the texinfo file
|
||
|
||
(defun texinfo-specific-section-type ()
|
||
"Return the specific type of next section, as a string.
|
||
For example, \"unnumberedsubsec\". Return \"top\" for top node.
|
||
|
||
Searches forward for a section. Hence, point must be before the
|
||
section whose type will be found. Does not move point. Signal an
|
||
error if the node is not the top node and a section is not found."
|
||
(save-excursion
|
||
(cond
|
||
((re-search-forward "^@node [ \t]*top[ \t]*\\(,\\|$\\)"
|
||
(save-excursion
|
||
(end-of-line)
|
||
(point))
|
||
t)
|
||
"top")
|
||
((re-search-forward texinfo-section-types-regexp nil t)
|
||
(buffer-substring (progn (beginning-of-line) ; copy its name
|
||
(1+ (point)))
|
||
(progn (forward-word 1)
|
||
(point))))
|
||
(t
|
||
(error
|
||
"texinfo-specific-section-type: Chapter or section not found.")))))
|
||
|
||
(defun texinfo-hierarchic-level ()
|
||
"Return the general hierarchal level of the next node in a texinfo file.
|
||
Thus, a subheading or appendixsubsec is of type subsection."
|
||
(cdr (assoc
|
||
(texinfo-specific-section-type)
|
||
texinfo-section-to-generic-alist)))
|
||
|
||
|
||
;;;; Locating the major positions
|
||
|
||
(defun texinfo-update-menu-region-beginning (level)
|
||
"Locate beginning of higher level section this section is within.
|
||
Return position of the beginning of the node line; do not move point.
|
||
Thus, if this level is subsection, searches backwards for section node.
|
||
Only argument is a string of the general type of section."
|
||
|
||
(cond
|
||
((or (string-equal "top" level)
|
||
(string-equal "chapter" level))
|
||
(save-excursion
|
||
(goto-char (point-min))
|
||
(re-search-forward "^@node [ \t]*top[ \t]*\\(,\\|$\\)" nil t)
|
||
(beginning-of-line)
|
||
(point)))
|
||
(t
|
||
(save-excursion
|
||
(re-search-backward
|
||
(concat
|
||
"\\(^@node\\).*\n" ; match node line
|
||
"\\(\\(\\(^@c\\).*\n\\)" ; match comment line, if any
|
||
"\\|" ; or
|
||
"\\(^@ifinfo[ ]*\n\\)\\)?" ; ifinfo line, if any
|
||
(eval
|
||
(cdr (assoc level texinfo-update-menu-higher-regexps))))
|
||
nil
|
||
'goto-beginning)
|
||
(point)))))
|
||
|
||
(defun texinfo-update-menu-region-end (level)
|
||
"Locate end of higher level section this section is within.
|
||
Return position; do not move point. Thus, if this level is a
|
||
subsection, find the node for the section this subsection is within.
|
||
If level is top or chapter, returns end of file. Only argument is a
|
||
string of the general type of section."
|
||
|
||
(save-excursion
|
||
(if (re-search-forward
|
||
(concat
|
||
"\\(^@node\\).*\n" ; match node line
|
||
"\\(\\(\\(^@c\\).*\n\\)" ; match comment line, if any
|
||
"\\|" ; or
|
||
"\\(^@ifinfo[ ]*\n\\)\\)?" ; ifinfo line, if any
|
||
(eval
|
||
(cdr (assoc level texinfo-update-menu-higher-regexps))))
|
||
nil
|
||
'goto-end)
|
||
(match-beginning 1)
|
||
(point-max))))
|
||
|
||
(defun texinfo-menu-first-node (beginning end)
|
||
"Locate first node of the section the menu will be placed in.
|
||
Return position; do not move point.
|
||
The menu will be located just before this position.
|
||
|
||
First argument is the position of the beginning of the section in
|
||
which the menu will be located; second argument is the position of the
|
||
end of that region; it limits the search."
|
||
|
||
(save-excursion
|
||
(goto-char beginning)
|
||
(forward-line 1)
|
||
(re-search-forward "^@node" end t)
|
||
(beginning-of-line)
|
||
(point)))
|
||
|
||
|
||
;;;; Alists and regular expressions for defining hierarchical levels
|
||
|
||
(defvar texinfo-section-to-generic-alist
|
||
'(("top" . "top")
|
||
|
||
("chapter" . "chapter")
|
||
("unnumbered" . "chapter")
|
||
("majorheading" . "chapter")
|
||
("chapheading" . "chapter")
|
||
("appendix" . "chapter")
|
||
|
||
("section" . "section")
|
||
("unnumberedsec" . "section")
|
||
("heading" . "section")
|
||
("appendixsec" . "section")
|
||
|
||
("subsection" . "subsection")
|
||
("unnumberedsubsec" . "subsection")
|
||
("subheading" . "subsection")
|
||
("appendixsubsec" . "subsection")
|
||
|
||
("subsubsection" . "subsubsection")
|
||
("unnumberedsubsubsec" . "subsubsection")
|
||
("subsubheading" . "subsubsection")
|
||
("appendixsubsubsec" . "subsubsection"))
|
||
"*An alist of specific and corresponding generic Texinfo section types.
|
||
The keys are strings specifying specific types of section; the values
|
||
are strings of their corresponding general types.")
|
||
|
||
(defvar texinfo-section-types-regexp
|
||
"^@\\(chapter \\|sect\\|sub\\|unnum\\|major\\|chapheading \\|heading \\|appendix\\)"
|
||
"Regexp matching chapter, section, other headings (but not the top node).")
|
||
|
||
(defvar texinfo-chapter-level-regexp
|
||
"chapter\\|unnumbered \\|appendix \\|majorheading\\|chapheading"
|
||
"Regular expression matching just the Texinfo chapter level headings.")
|
||
|
||
(defvar texinfo-section-level-regexp
|
||
"section\\|unnumberedsec\\|heading \\|appendixsec"
|
||
"Regular expression matching just the Texinfo section level headings.")
|
||
|
||
(defvar texinfo-subsection-level-regexp
|
||
"subsection\\|unnumberedsubsec\\|subheading\\|appendixsubsec"
|
||
"Regular expression matching just the Texinfo subsection level headings.")
|
||
|
||
(defvar texinfo-subsubsection-level-regexp
|
||
"subsubsection\\|unnumberedsubsubsec\\|subsubheading\\|appendixsubsubsec"
|
||
"Regular expression matching just the Texinfo subsubsection level headings.")
|
||
|
||
(defvar texinfo-update-menu-same-level-regexps
|
||
'(("top" . "top[ \t]+")
|
||
("chapter" .
|
||
(concat "\\(^@\\)\\(" texinfo-chapter-level-regexp "\\)[ \t]*"))
|
||
("section" .
|
||
(concat "\\(^@\\)\\(" texinfo-section-level-regexp "\\)[ \t]*"))
|
||
("subsection" .
|
||
(concat "\\(^@\\)\\(" texinfo-subsection-level-regexp "\\)[ \t]+"))
|
||
("subsubsection" .
|
||
(concat "\\(^@\\)\\(" texinfo-subsubsection-level-regexp "\\)[ \t]+")))
|
||
"*Regexps for searching for same level sections in a Texinfo file.
|
||
The keys are strings specifying the general hierarchical level in the
|
||
document; the values are regular expressions.")
|
||
|
||
(defvar texinfo-update-menu-higher-regexps
|
||
'(("top" . "^@node [ \t]*DIR")
|
||
("chapter" . "^@node [ \t]*top[ \t]*\\(,\\|$\\)")
|
||
("section" .
|
||
(concat
|
||
"\\(^@\\("
|
||
texinfo-chapter-level-regexp
|
||
"\\)[ \t]*\\)"))
|
||
("subsection" .
|
||
(concat
|
||
"\\(^@\\("
|
||
texinfo-section-level-regexp
|
||
"\\|"
|
||
texinfo-chapter-level-regexp
|
||
"\\)[ \t]*\\)"))
|
||
("subsubsection" .
|
||
(concat
|
||
"\\(^@\\("
|
||
texinfo-subsection-level-regexp
|
||
"\\|"
|
||
texinfo-section-level-regexp
|
||
"\\|"
|
||
texinfo-chapter-level-regexp
|
||
"\\)[ \t]*\\)")))
|
||
"*Regexps for searching for higher level sections in a Texinfo file.
|
||
The keys are strings specifying the general hierarchical level in the
|
||
document; the values are regular expressions.")
|
||
|
||
(defvar texinfo-update-menu-lower-regexps
|
||
'(("top" .
|
||
(concat
|
||
"\\(^@\\("
|
||
texinfo-chapter-level-regexp
|
||
"\\|"
|
||
texinfo-section-level-regexp
|
||
"\\|"
|
||
texinfo-subsection-level-regexp
|
||
"\\|"
|
||
texinfo-subsubsection-level-regexp
|
||
"\\)[ \t]*\\)"))
|
||
("chapter" .
|
||
(concat
|
||
"\\(^@\\("
|
||
texinfo-section-level-regexp
|
||
"\\|"
|
||
texinfo-subsection-level-regexp
|
||
"\\|"
|
||
texinfo-subsubsection-level-regexp
|
||
"\\)[ \t]*\\)"))
|
||
("section" .
|
||
(concat
|
||
"\\(^@\\("
|
||
texinfo-subsection-level-regexp
|
||
"\\|"
|
||
texinfo-subsubsection-level-regexp
|
||
"\\)[ \t]+\\)"))
|
||
("subsection" .
|
||
(concat
|
||
"\\(^@\\("
|
||
texinfo-subsubsection-level-regexp
|
||
"\\)[ \t]+\\)"))
|
||
("subsubsection" . "nothing lower"))
|
||
"*Regexps for searching for lower level sections in a Texinfo file.
|
||
The keys are strings specifying the general hierarchical level in the
|
||
document; the values are regular expressions.")
|
||
|
||
|
||
;;;; Updating a Node
|
||
|
||
(defun texinfo-update-node (&optional region-p)
|
||
"Without any prefix argument, update the node in which point is located.
|
||
Non-nil argument (prefix, if interactive) means update the nodes in the
|
||
marked region.
|
||
|
||
The functions for creating or updating nodes and menus, and their
|
||
keybindings, are:
|
||
|
||
texinfo-update-node (&optional region-p) \\[texinfo-update-node]
|
||
texinfo-every-node-update () \\[texinfo-every-node-update]
|
||
texinfo-sequential-node-update (&optional region-p)
|
||
|
||
texinfo-make-menu (&optional region-p) \\[texinfo-make-menu]
|
||
texinfo-all-menus-update () \\[texinfo-all-menus-update]
|
||
texinfo-master-menu ()
|
||
|
||
texinfo-indent-menu-description (column &optional region-p)
|
||
|
||
The `texinfo-column-for-description' variable specifies the column to
|
||
which menu descriptions are indented. Its default value is 24."
|
||
|
||
(interactive "P")
|
||
(if (not region-p)
|
||
(let ((auto-fill-function nil)) ; update a single node
|
||
(if (not (re-search-backward "^@node" (point-min) t))
|
||
(error "Node line not found before this position."))
|
||
(texinfo-update-the-node)
|
||
(message "Done...updated the node. You may save the buffer."))
|
||
;; else
|
||
(let ((auto-fill-function nil)
|
||
(beginning (region-beginning))
|
||
(end (region-end)))
|
||
(if (= end beginning)
|
||
(error "Please mark a region!"))
|
||
(save-restriction
|
||
(narrow-to-region beginning end)
|
||
(goto-char beginning)
|
||
(push-mark)
|
||
(while (re-search-forward "^@node" (point-max) t)
|
||
(beginning-of-line)
|
||
(texinfo-update-the-node))
|
||
(message "Done...updated nodes in region. You may save the buffer.")))))
|
||
|
||
(defun texinfo-every-node-update ()
|
||
"Update every node in a Texinfo file."
|
||
(interactive)
|
||
(save-excursion
|
||
(mark-whole-buffer)
|
||
(texinfo-update-node t)
|
||
(message "Done...updated every node. You may save the buffer.")))
|
||
|
||
(defun texinfo-update-the-node ()
|
||
"Update one node. Point must be at the beginning of node line.
|
||
Leave point at the end of the node line."
|
||
(texinfo-check-for-node-name)
|
||
(texinfo-delete-existing-pointers)
|
||
(message "Updating node: %s ... " (texinfo-copy-node-name))
|
||
(save-restriction
|
||
(widen)
|
||
(let*
|
||
((case-fold-search t)
|
||
(level (texinfo-hierarchic-level))
|
||
(beginning (texinfo-update-menu-region-beginning level))
|
||
(end (texinfo-update-menu-region-end level)))
|
||
(if (string-equal level "top")
|
||
(texinfo-top-pointer-case)
|
||
;; else
|
||
(texinfo-insert-pointer beginning end level 'next)
|
||
(texinfo-insert-pointer beginning end level 'previous)
|
||
(texinfo-insert-pointer beginning end level 'up)
|
||
(texinfo-clean-up-node-line)))))
|
||
|
||
(defun texinfo-top-pointer-case ()
|
||
"Insert pointers in the Top node. This is a special case.
|
||
|
||
The `Next' pointer is a pointer to a chapter or section at a lower
|
||
hierarchical level in the file. The `Previous' and `Up' pointers are
|
||
to `(dir)'. Point must be at the beginning of the node line, and is
|
||
left at the end of the node line."
|
||
|
||
(texinfo-clean-up-node-line)
|
||
(insert ", "
|
||
(save-excursion
|
||
;; There may be an @chapter or other such command between
|
||
;; the top node line and the next node line, as a title
|
||
;; for an `ifinfo' section. This @chapter command must
|
||
;; must be skipped. So the procedure is to search for
|
||
;; the next `@node' line, and then copy its name.
|
||
(if (re-search-forward "^@node" nil t)
|
||
(progn
|
||
(beginning-of-line)
|
||
(texinfo-copy-node-name))
|
||
" "))
|
||
", (dir), (dir)"))
|
||
|
||
(defun texinfo-check-for-node-name ()
|
||
"Determine whether the node has a node name. Prompt for one if not.
|
||
Point must be at beginning of node line. Does not move point."
|
||
(save-excursion
|
||
(forward-word 1) ; skip over node command
|
||
(skip-chars-forward " \t") ; and over spaces
|
||
(if (not (looking-at "[^,\t\n ]+")) ; regexp based on what info looks for
|
||
; alternatively, use "[a-zA-Z]+"
|
||
(let ((node-name (read-from-minibuffer "Node name: ")))
|
||
(insert " " node-name)))))
|
||
|
||
(defun texinfo-delete-existing-pointers ()
|
||
"Delete `Next', `Previous', and `Up' pointers.
|
||
Starts from the current position of the cursor, and searches forward
|
||
on the line for a comma and if one is found, deletes the rest of the
|
||
line, including the comma. Leaves point at beginning of line."
|
||
(if (search-forward "," (save-excursion (end-of-line) (point)) t)
|
||
(progn
|
||
(goto-char (1- (point)))
|
||
(kill-line nil)))
|
||
(beginning-of-line))
|
||
|
||
(defun texinfo-find-pointer (beginning end level direction)
|
||
"Move point to section associated with next, previous, or up pointer.
|
||
Return type of pointer (either 'normal or 'no-pointer).
|
||
|
||
The first and second arguments bound the search for a pointer to the
|
||
beginning and end, respectively, of the enclosing higher level
|
||
section. The third argument is a string specifying the general kind
|
||
of section such as \"chapter\ or \"section\". When looking for the
|
||
`Next' pointer, the section found will be at the same hierarchical
|
||
level in the Texinfo file; when looking for the `Previous' pointer,
|
||
the section found will be at the same or higher hierarchical level in
|
||
the Texinfo file; when looking for the `Up' pointer, the section found
|
||
will be at some level higher in the Texinfo file. The fourth argument
|
||
\(one of 'next, 'previous, or 'up\) specifies whether to find the
|
||
`Next', `Previous', or `Up' pointer."
|
||
|
||
(cond ((eq direction 'next)
|
||
(forward-line 3) ; skip over current node
|
||
(if (re-search-forward
|
||
(eval
|
||
(cdr (assoc level texinfo-update-menu-same-level-regexps)))
|
||
end
|
||
t)
|
||
'normal
|
||
'no-pointer))
|
||
((eq direction 'previous)
|
||
(if (re-search-backward
|
||
(concat
|
||
"\\("
|
||
(eval
|
||
(cdr (assoc level texinfo-update-menu-same-level-regexps)))
|
||
"\\|"
|
||
(eval
|
||
(cdr (assoc level texinfo-update-menu-higher-regexps)))
|
||
"\\)")
|
||
beginning
|
||
t)
|
||
'normal
|
||
'no-pointer))
|
||
((eq direction 'up)
|
||
(if (re-search-backward
|
||
(eval (cdr (assoc level texinfo-update-menu-higher-regexps)))
|
||
(save-excursion
|
||
(goto-char beginning)
|
||
(beginning-of-line)
|
||
(point))
|
||
t)
|
||
'normal
|
||
'no-pointer))
|
||
(t
|
||
(error "texinfo-find-pointer: lack proper arguments"))))
|
||
|
||
(defun texinfo-pointer-name (kind)
|
||
"Return the node name preceding the section command.
|
||
The argument is the kind of section, either normal or no-pointer."
|
||
(let (name)
|
||
(cond ((eq kind 'normal)
|
||
(end-of-line) ; this handles prev node top case
|
||
(re-search-backward ; when point is already
|
||
"^@node" ; at the beginning of @node line
|
||
(save-excursion (forward-line -3))
|
||
t)
|
||
(setq name (texinfo-copy-node-name)))
|
||
((eq kind 'no-pointer)
|
||
(setq name " "))) ; put a blank in the pointer slot
|
||
name))
|
||
|
||
(defun texinfo-insert-pointer (beginning end level direction)
|
||
"Insert the `Next', `Previous' or `Up' node name at point.
|
||
Move point forward.
|
||
|
||
The first and second arguments bound the search for a pointer to the
|
||
beginning and end, respectively, of the enclosing higher level
|
||
section. The third argument is the hierarchical level of the Texinfo
|
||
file, a string such as \"section\". The fourth argument is direction
|
||
towards which the pointer is directed, one of `next, `previous, or
|
||
'up."
|
||
|
||
(end-of-line)
|
||
(insert
|
||
", "
|
||
(save-excursion
|
||
(texinfo-pointer-name
|
||
(texinfo-find-pointer beginning end level direction)))))
|
||
|
||
(defun texinfo-clean-up-node-line ()
|
||
"Remove extra commas, if any, at end of node line."
|
||
(end-of-line)
|
||
(skip-chars-backward ", ")
|
||
(delete-region (point) (save-excursion (end-of-line) (point))))
|
||
|
||
|
||
;;;; Updating nodes sequentially
|
||
; These sequential update functions insert `Next' or `Previous'
|
||
; pointers that point to the following or preceding nodes even if they
|
||
; are at higher or lower hierarchical levels. This means that if a
|
||
; section contains one or more subsections, the section's `Next'
|
||
; pointer will point to the subsection and not the following section.
|
||
; (The subsection to which `Next' points will most likely be the first
|
||
; item on the section's menu.)
|
||
|
||
(defun texinfo-sequential-node-update (&optional region-p)
|
||
"Update one node (or many) in a Texinfo file with sequential pointers.
|
||
|
||
This function causes the `Next' or `Previous' pointer to point to the
|
||
immediately preceding or following node, even if it is at a higher or
|
||
lower hierarchical level in the document. Continually pressing `n' or
|
||
`p' takes you straight through the file.
|
||
|
||
Without any prefix argument, update the node in which point is located.
|
||
Non-nil argument (prefix, if interactive) means update the nodes in the
|
||
marked region.
|
||
|
||
This command makes it awkward to navigate among sections and
|
||
subsections; it should be used only for those documents that are meant
|
||
to be read like a novel rather than a reference, and for which the
|
||
Info `g*' command is inadequate."
|
||
|
||
(interactive "P")
|
||
(if (not region-p)
|
||
(let ((auto-fill-function nil)) ; update a single node
|
||
(if (not (re-search-backward "^@node" (point-min) t))
|
||
(error "Node line not found before this position."))
|
||
(texinfo-sequentially-update-the-node)
|
||
(message
|
||
"Done...sequentially updated the node . You may save the buffer."))
|
||
;; else
|
||
(let ((auto-fill-function nil)
|
||
(beginning (region-beginning))
|
||
(end (region-end)))
|
||
(if (= end beginning)
|
||
(error "Please mark a region!"))
|
||
(save-restriction
|
||
(narrow-to-region beginning end)
|
||
(goto-char beginning)
|
||
(push-mark)
|
||
(while (re-search-forward "^@node" (point-max) t)
|
||
(beginning-of-line)
|
||
(texinfo-sequentially-update-the-node))
|
||
(message
|
||
"Done...updated the nodes in sequence. You may save the buffer.")))))
|
||
|
||
(defun texinfo-sequentially-update-the-node ()
|
||
"Update one node such that the pointers are sequential.
|
||
A `Next' or `Previous' pointer points to any preceding or following node,
|
||
regardless of its hierarchical level."
|
||
|
||
(texinfo-check-for-node-name)
|
||
(texinfo-delete-existing-pointers)
|
||
(message
|
||
"Sequentially updating node: %s ... " (texinfo-copy-node-name))
|
||
(save-restriction
|
||
(widen)
|
||
(let*
|
||
((case-fold-search t)
|
||
(level (texinfo-hierarchic-level)))
|
||
(if (string-equal level "top")
|
||
(texinfo-top-pointer-case)
|
||
;; else
|
||
(texinfo-sequentially-insert-pointer level 'next)
|
||
(texinfo-sequentially-insert-pointer level 'previous)
|
||
(texinfo-sequentially-insert-pointer level 'up)
|
||
(texinfo-clean-up-node-line)))))
|
||
|
||
(defun texinfo-sequentially-find-pointer (level direction)
|
||
"Find next or previous pointer sequentially in Texinfo file, or up pointer.
|
||
Move point to section associated with the pointer. Find point even if
|
||
it is in a different section.
|
||
|
||
Return type of pointer (either 'normal or 'no-pointer).
|
||
|
||
The first argument is a string specifying the general kind of section
|
||
such as \"chapter\ or \"section\". The section found will be at the
|
||
same hierarchical level in the Texinfo file, or, in the case of the up
|
||
pointer, some level higher. The second argument (one of 'next,
|
||
'previous, or 'up) specifies whether to find the `Next', `Previous',
|
||
or `Up' pointer."
|
||
|
||
(cond ((eq direction 'next)
|
||
(forward-line 3) ; skip over current node
|
||
(if (re-search-forward
|
||
texinfo-section-types-regexp
|
||
(point-max)
|
||
t)
|
||
'normal
|
||
'no-pointer))
|
||
((eq direction 'previous)
|
||
(if (re-search-backward
|
||
texinfo-section-types-regexp
|
||
(point-min)
|
||
t)
|
||
'normal
|
||
'no-pointer))
|
||
((eq direction 'up)
|
||
(if (re-search-backward
|
||
(eval (cdr (assoc level texinfo-update-menu-higher-regexps)))
|
||
beginning
|
||
t)
|
||
'normal
|
||
'no-pointer))
|
||
(t
|
||
(error "texinfo-sequential-find-pointer: lack proper arguments"))))
|
||
|
||
(defun texinfo-sequentially-insert-pointer (level direction)
|
||
"Insert the `Next', `Previous' or `Up' node name at point.
|
||
Move point forward.
|
||
|
||
The first argument is the hierarchical level of the Texinfo file, a
|
||
string such as \"section\". The second argument is direction, one of
|
||
`next, `previous, or 'up."
|
||
|
||
(end-of-line)
|
||
(insert
|
||
", "
|
||
(save-excursion
|
||
(texinfo-pointer-name
|
||
(texinfo-sequentially-find-pointer level direction)))))
|
||
|
||
|
||
;;;; Inserting `@node' lines
|
||
; The `texinfo-insert-node-lines' function inserts `@node' lines as needed
|
||
; before the `@chapter', `@section', and such like lines of a region
|
||
; in a Texinfo file.
|
||
|
||
(defun texinfo-insert-node-lines (&optional title-p)
|
||
"Insert missing `@node' lines in region of Texinfo file.
|
||
Non-nil argument (prefix, if interactive) means also to insert the
|
||
section titles as node names; and also to insert the section titles as
|
||
node names in pre-existing @node lines that lack names."
|
||
(interactive "P")
|
||
(save-excursion
|
||
(let ((begin-region (region-beginning))
|
||
(end-region (region-end)))
|
||
(goto-char begin-region)
|
||
(while (< (point) end-region)
|
||
(re-search-forward texinfo-section-types-regexp nil 'end)
|
||
;; copy title, since most often, we will need it
|
||
(let ((title
|
||
(progn
|
||
(beginning-of-line)
|
||
(forward-word 1)
|
||
(skip-chars-forward " \t")
|
||
(buffer-substring
|
||
(point)
|
||
(save-excursion (end-of-line) (point))))))
|
||
;; insert a node if necessary
|
||
(if (re-search-backward
|
||
"^@node"
|
||
(save-excursion
|
||
(forward-line -3)
|
||
(point))
|
||
t)
|
||
;; @node present, and point at beginning of that line
|
||
(forward-word 1)
|
||
;; else @node missing, insert one
|
||
(progn
|
||
(beginning-of-line) ; beginning of `@section' line
|
||
(insert "@node\n")
|
||
(backward-char 1))) ; leave point just after `@node'
|
||
;; insert a title if warranted
|
||
(if title-p
|
||
(progn
|
||
(skip-chars-forward " \t")
|
||
;; use regexp based on what info looks for
|
||
;; (alternatively, use "[a-zA-Z]+")
|
||
(if (not (looking-at "[^,\t\n ]+"))
|
||
(progn
|
||
(beginning-of-line)
|
||
(forward-word 1)
|
||
(insert " " title)
|
||
(message "Inserted title %s ... " title)))))
|
||
;; in any case, go forward beyond current section title
|
||
(forward-line 3)))))
|
||
(if title-p
|
||
(message
|
||
"Done inserting node lines and titles. You may save the buffer.")
|
||
(message "Done inserting node lines. You may save the buffer.")))
|
||
|
||
|
||
;;;; Update and create menus for multi-file Texinfo sources
|
||
|
||
;; 1. M-x texinfo-multiple-files-update
|
||
;;
|
||
;; Read the include file list of an outer Texinfo file and
|
||
;; update all highest level nodes in the files listed and insert a
|
||
;; main menu in the outer file after its top node.
|
||
|
||
;; 2. C-u M-x texinfo-multiple-files-update
|
||
;;
|
||
;; Same as 1, but insert a master menu. (Saves reupdating lower
|
||
;; level menus and nodes.) This command simply reads every menu,
|
||
;; so if the menus are wrong, the master menu will be wrong.
|
||
;; Similarly, if the lower level node pointers are wrong, they
|
||
;; will stay wrong.
|
||
|
||
;; 3. C-u 2 M-x texinfo-multiple-files-update
|
||
;;
|
||
;; Read the include file list of an outer Texinfo file and
|
||
;; update all nodes and menus in the files listed and insert a
|
||
;; master menu in the outer file after its top node.
|
||
|
||
;;; Note: these functions:
|
||
;;;
|
||
;;; * Do not save or delete any buffers. You may fill up your memory.
|
||
;;; * Do not handle any pre-existing nodes in outer file.
|
||
;;; Hence, you may need a file for indices.
|
||
|
||
|
||
;;;; Auxiliary functions for multiple file updating
|
||
|
||
(defun texinfo-multi-file-included-list (outer-file)
|
||
"Return a list of the included files in OUTER-FILE."
|
||
(let ((included-file-list (list outer-file))
|
||
start)
|
||
(save-excursion
|
||
(switch-to-buffer (find-file-noselect outer-file))
|
||
(widen)
|
||
(goto-char (point-min))
|
||
(while (re-search-forward "^@include" nil t)
|
||
(skip-chars-forward " \t")
|
||
(setq start (point))
|
||
(end-of-line)
|
||
(skip-chars-backward " \t")
|
||
(setq included-file-list
|
||
(cons (buffer-substring start (point))
|
||
included-file-list)))
|
||
(nreverse included-file-list))))
|
||
|
||
(defun texinfo-copy-next-section-title ()
|
||
"Return the name of the immediately following section as a string.
|
||
|
||
Start with point at the beginning of the node line. Leave point at the
|
||
same place. If there is no title, returns an empty string."
|
||
|
||
(save-excursion
|
||
(end-of-line)
|
||
(let ((section-end (or
|
||
(save-excursion
|
||
(re-search-forward "\\(^@node\\)" nil t)
|
||
(match-beginning 0))
|
||
(point-max))))
|
||
(if (re-search-forward texinfo-section-types-regexp section-end t)
|
||
;; copy title
|
||
(let ((title
|
||
(buffer-substring
|
||
(progn (forward-word 1) ; skip over section type
|
||
(skip-chars-forward " \t") ; and over spaces
|
||
(point))
|
||
(progn (end-of-line) (point)))))
|
||
title)
|
||
""))))
|
||
|
||
(defun texinfo-multi-file-update (files &optional update-everything)
|
||
"Update first node pointers in each file in FILES.
|
||
Return a list of the node names and the title immediate following them.
|
||
|
||
The first file in the list is an outer file; the remaining are
|
||
files included in the outer file with `@include' commands.
|
||
|
||
If optional arg UPDATE-EVERYTHING non-nil, update every menu and
|
||
pointer in each of the included files.
|
||
|
||
Also update the `Top' level node pointers of the outer file.
|
||
|
||
Requirements:
|
||
|
||
* the first file in the FILES list must be the outer file,
|
||
* each of the included files must contain exactly one highest
|
||
hierarchical level node,
|
||
* this node must be the first node in the included file,
|
||
* each highest hierarchical level node must be of the same type.
|
||
|
||
Thus, normally, each included file contains one, and only one,
|
||
chapter.
|
||
|
||
The menu-list has the form:
|
||
|
||
\(\(\"node-name1\" . \"title1\"\)
|
||
\(\"node-name2\" . \"title2\"\) ... \)
|
||
|
||
However, there does not need to be a title field."
|
||
|
||
(let (menu-list)
|
||
|
||
;; Find the name of the first node of the first included file.
|
||
(switch-to-buffer (find-file-noselect (car (cdr files))))
|
||
(widen)
|
||
(goto-char (point-min))
|
||
(if (not (re-search-forward "^@node" nil t))
|
||
(error "No `@node' line found in %s !" (buffer-name)))
|
||
(beginning-of-line)
|
||
(texinfo-check-for-node-name)
|
||
(setq next-node-name (texinfo-copy-node-name))
|
||
|
||
(setq menu-list
|
||
(cons (cons
|
||
next-node-name
|
||
(texinfo-copy-next-section-title))
|
||
menu-list))
|
||
|
||
;; Go to outer file
|
||
(switch-to-buffer (find-file-noselect (car files)))
|
||
(goto-char (point-min))
|
||
(if (not (re-search-forward "^@node [ \t]*top[ \t]*\\(,\\|$\\)" nil t))
|
||
(error "This buffer needs a Top node!"))
|
||
(beginning-of-line)
|
||
(texinfo-delete-existing-pointers)
|
||
(end-of-line)
|
||
(insert ", " next-node-name ", (dir), (dir)")
|
||
(beginning-of-line)
|
||
(setq previous-node-name "Top")
|
||
(setq files (cdr files))
|
||
|
||
(while files
|
||
|
||
(if (not (cdr files))
|
||
;; No next file
|
||
(setq next-node-name "")
|
||
;; Else,
|
||
;; find the name of the first node in the next file.
|
||
(switch-to-buffer (find-file-noselect (car (cdr files))))
|
||
(widen)
|
||
(goto-char (point-min))
|
||
(if (not (re-search-forward "^@node" nil t))
|
||
(error "No `@node' line found in %s !" (buffer-name)))
|
||
(beginning-of-line)
|
||
(texinfo-check-for-node-name)
|
||
(setq next-node-name (texinfo-copy-node-name))
|
||
(setq menu-list
|
||
(cons (cons
|
||
next-node-name
|
||
(texinfo-copy-next-section-title))
|
||
menu-list)))
|
||
|
||
;; Go to node to be updated.
|
||
(switch-to-buffer (find-file-noselect (car files)))
|
||
(goto-char (point-min))
|
||
(if (not (re-search-forward "^@node" nil t))
|
||
(error "No `@node' line found in %s !" (buffer-name)))
|
||
(beginning-of-line)
|
||
(texinfo-delete-existing-pointers)
|
||
(end-of-line)
|
||
(insert ", " next-node-name ", " previous-node-name ", " up-node-name)
|
||
|
||
(beginning-of-line)
|
||
(setq previous-node-name (texinfo-copy-node-name))
|
||
|
||
;; Update other menus and nodes if requested.
|
||
(if update-everything (texinfo-all-menus-update t))
|
||
|
||
(setq files (cdr files)))
|
||
(nreverse menu-list)))
|
||
|
||
(defun texinfo-multi-files-insert-main-menu (menu-list)
|
||
"Insert formatted main menu at point.
|
||
Indents the first line of the description, if any, to the value of
|
||
texinfo-column-for-description."
|
||
|
||
(insert "@menu\n")
|
||
(while menu-list
|
||
(if (cdr (car menu-list)) ; menu-list has description entry
|
||
(progn
|
||
(insert
|
||
(format "* %s::" (car (car menu-list)))) ; node-name entry
|
||
(indent-to texinfo-column-for-description 2)
|
||
(insert
|
||
(format "%s\n" (cdr (car menu-list))))) ; description entry
|
||
;; else menu-list lacks description entry
|
||
(insert
|
||
(format "* %s::\n" (car (car menu-list))))) ; node-name entry
|
||
(setq menu-list (cdr menu-list)))
|
||
(insert "@end menu"))
|
||
|
||
|
||
(defun texinfo-multi-file-master-menu-list (files-list)
|
||
"Return master menu list from files in FILES-LIST.
|
||
Menu entries in each file collected using `texinfo-master-menu-list'.
|
||
|
||
The first file in FILES-LIST must be the outer file; the others must
|
||
be the files included within it. A main menu must already exist."
|
||
(save-excursion
|
||
(let (master-menu-list)
|
||
(while files-list
|
||
(switch-to-buffer (find-file-noselect (car files-list)))
|
||
(message "Working on: %s " (current-buffer))
|
||
(goto-char (point-min))
|
||
(setq master-menu-list
|
||
(append master-menu-list (texinfo-master-menu-list)))
|
||
(setq files-list (cdr files-list)))
|
||
master-menu-list)))
|
||
|
||
|
||
;;;; The multiple-file update function
|
||
|
||
(defun texinfo-multiple-files-update
|
||
(outer-file &optional update-everything make-master-menu)
|
||
"Update first node pointers in each file included in OUTER-FILE;
|
||
create or update main menu in the outer file that refers to such nodes.
|
||
This does not create or update menus or pointers within the included files.
|
||
|
||
With optional MAKE-MASTER-MENU argument (prefix arg, if interactive),
|
||
insert a master menu in OUTER-FILE. This does not create or update
|
||
menus or pointers within the included files.
|
||
|
||
With optional UPDATE-EVERYTHING argument (numeric prefix arg, if
|
||
interactive), update all the menus and all the `Next', `Previous', and
|
||
`Up' pointers of all the files included in OUTER-FILE before inserting
|
||
a master menu in OUTER-FILE.
|
||
|
||
The command also updates the `Top' level node pointers of OUTER-FILE.
|
||
|
||
Notes:
|
||
|
||
* this command does NOT save any files--you must save the
|
||
outer file and any modified, included files.
|
||
|
||
* except for the `Top' node, this command does NOT handle any
|
||
pre-existing nodes in the outer file; hence, indices must be
|
||
enclosed in an included file.
|
||
|
||
Requirements:
|
||
|
||
* each of the included files must contain exactly one highest
|
||
hierarchical level node,
|
||
* this highest node must be the first node in the included file,
|
||
* each highest hierarchical level node must be of the same type.
|
||
|
||
Thus, normally, each included file contains one, and only one,
|
||
chapter."
|
||
|
||
(interactive "fName of outer `include' file: ")
|
||
|
||
(cond (current-prefix-arg
|
||
(setq make-master-menu (listp current-prefix-arg))
|
||
(setq update-everything (numberp current-prefix-arg))))
|
||
|
||
(let* ((included-file-list (texinfo-multi-file-included-list outer-file))
|
||
(files included-file-list)
|
||
main-menu-list
|
||
next-node-name
|
||
previous-node-name
|
||
(up-node-name "Top"))
|
||
|
||
;;; Update the pointers
|
||
;;; and collect the names of the nodes and titles
|
||
(setq main-menu-list (texinfo-multi-file-update files update-everything))
|
||
|
||
;;; Insert main menu
|
||
|
||
;; Go to outer file
|
||
(switch-to-buffer (find-file-noselect (car included-file-list)))
|
||
(if (texinfo-old-menu-p
|
||
(point-min)
|
||
(save-excursion
|
||
(re-search-forward "^@include")
|
||
(beginning-of-line)
|
||
(point)))
|
||
|
||
;; If found, leave point after word `menu' on the `@menu' line.
|
||
(progn
|
||
(texinfo-incorporate-descriptions main-menu-list)
|
||
;; Delete existing menu.
|
||
(beginning-of-line)
|
||
(delete-region
|
||
(point)
|
||
(save-excursion (re-search-forward "^@end menu") (point)))
|
||
;; Insert main menu
|
||
(texinfo-multi-files-insert-main-menu main-menu-list))
|
||
|
||
;; Else no current menu; insert it before `@include'
|
||
(texinfo-multi-files-insert-main-menu main-menu-list))
|
||
|
||
;;; Insert master menu
|
||
|
||
(if make-master-menu
|
||
(progn
|
||
;; First, removing detailed part of any pre-existing master menu
|
||
(goto-char (point-min))
|
||
(if (re-search-forward texinfo-master-menu-header nil t)
|
||
;; Remove detailed master menu listing
|
||
(progn
|
||
(goto-char (match-beginning 0))
|
||
(let ((end-of-detailed-menu-descriptions
|
||
(save-excursion ; beginning of end menu line
|
||
(goto-char (texinfo-menu-end))
|
||
(beginning-of-line) (forward-char -1)
|
||
(point))))
|
||
(delete-region (point) end-of-detailed-menu-descriptions))))
|
||
|
||
;; Create a master menu and insert it
|
||
(texinfo-insert-master-menu-list
|
||
(texinfo-multi-file-master-menu-list
|
||
included-file-list)))))
|
||
(message "Multiple files updated."))
|
||
|
||
(provide 'texnfo-upd)
|
||
|
||
;;; texnfo-upd.el ends here
|