Mercurial > hg > xemacs-beta
diff lisp/energize/energize-mode.el @ 0:376386a54a3c r19-14
Import from CVS: tag r19-14
author | cvs |
---|---|
date | Mon, 13 Aug 2007 08:45:50 +0200 |
parents | |
children | ec9a17fef872 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lisp/energize/energize-mode.el Mon Aug 13 08:45:50 2007 +0200 @@ -0,0 +1,790 @@ +;;; -*- Mode:Emacs-Lisp -*- +;;; Copyright © 1991-1993 by Lucid, Inc. All Rights Reserved. + +(eval-when-compile + (require 'etags)) + +;; true if current-buffer is an energize buffer that does not support +;; the real write-file and so has to do the special energize way of doing +;; write-file that loses the annotations. +(defun energize-write-file-buffer-p () + ;; (and (energize-buffer-p (current-buffer) + ;; (not (eq major-mode 'energize-project-mode))) + (energize-buffer-p (current-buffer))) + + +(defun energize-beginning-of-defun (&optional arg) + "Move point to the beginning of the current top-level form. +With a numeric argument, move back that many forms." + (interactive "_p") + (or arg (setq arg 1)) + (if (not (energize-buffer-p (current-buffer))) + (error "Not an Energize buffer") + (if (< arg 0) + (energize-end-of-defun (- arg)) + (while (> arg 0) + (or (bobp) (forward-char -1)) + (while (and (not (bobp)) (null (energize-extent-at (point)))) + (forward-char -1)) + (let ((pos (point))) + (map-extents + (function + (lambda (extent dummy) + (if (< (setq pos (extent-start-position extent)) (point)) + (goto-char pos)))) + (current-buffer) (point) (point) nil t)) + (setq arg (1- arg)))))) + +(defun energize-end-of-defun (&optional arg) + "Move point to the end of the current top-level form. +With a numeric argument, move forward over that many forms." + (interactive "_p") + (or arg (setq arg 1)) + (if (not (energize-buffer-p (current-buffer))) + (error "Not an Energize buffer") + (if (< arg 0) + (energize-beginning-of-defun (- arg)) + (while (> arg 0) + (or (eobp) (forward-char 1)) + (while (and (not (eobp)) (null (energize-extent-at (point)))) + (forward-char 1)) + (let ((pos (point))) + (map-extents + (function + (lambda (extent dummy) + (if (> (setq pos (extent-end-position extent)) (point)) + (goto-char pos)))) + (current-buffer) (point) (point) nil t)) + (setq arg (1- arg)))))) + + +;;; Patching Energize into file I/O via the standard hooks. + +(defun energize-write-data-hook (name) + ;; for use as the last element of write-file-data-hooks + ;; in energize buffers. + (if (energize-buffer-p (current-buffer)) + (progn + (message "saving %s to Energize..." name) + (energize-execute-command "save") + (energize-update-menubar) + (message "saved %s to Energize." name) + t) + nil)) + +(defun energize-revert-buffer-insert-file-contents-hook (file noconfirm) + ;; for use as the value of revert-buffer-insert-file-contents-function + ;; in energize buffers. + (if (not (energize-buffer-p (current-buffer))) + (error "energize-revert-buffer-hook called for a non-energize buffer")) + (widen) + (cond ((equal file buffer-file-name) ; reverting from energize + ;; Do the default as in files.el + (if (file-exists-p file) + (progn + ;; Bind buffer-file-name to nil + ;; so that we don't try to lock the file. + (let ((buffer-file-name nil)) + (unlock-buffer) + (erase-buffer)) + (insert-file-contents file t))) + ;; Then asks the extents from Energize + (energize-execute-command "revert")) + (t ; reverting from autosave + (if (not (file-exists-p file)) + (error "File %s no longer exists!" file)) + (erase-buffer) + (insert-file-contents file))) + t) + + +(defun energize-kill-buffer-hook () + ;; for use as the value of kill-buffer-hook in energize buffers. + (if (energize-buffer-p (current-buffer)) + (energize-request-kill-buffer (current-buffer)) + (error "energize-kill-buffer-hook called on a non-energize buffer")) + t) + + +;;; + +(defun energize-edit-definition-default () + (save-excursion + (if (not (memq (char-syntax (preceding-char)) '(?w ?_))) + (while (not (looking-at "\\sw\\|\\s_\\|\\'")) + (forward-char 1))) + (while (looking-at "\\sw\\|\\s_") + (forward-char 1)) + (if (re-search-backward "\\sw\\|\\s_" nil t) + (progn (forward-char 1) + (buffer-substring (point) + (progn (forward-sexp -1) + (while (looking-at "\\s'") + (forward-char 1)) + (point)))) + nil))) + +;;; This prompts in the minibuffer, ##### with no completion. +(defun energize-edit-definition (def) + "If connected to Energize, the Energize database is used. +Otherwise, `find-tag' is invoked. +The X selection is used as a default, if it exists and contains no +newlines. Otherwise, the preceeding token is used as a default. +If invoked from a mouse command, prompting happens with a dialog box; +otherwise, the minibuffer is used." + (interactive + (if (and (connected-to-energize-p) + (or (menu-event-p last-command-event) + (button-press-event-p last-command-event) + (button-release-event-p last-command-event))) + '(nil) + (list + (let (default + def) + (cond ((x-selection-owner-p) + (setq default (x-get-selection)) + (while (string-match "\\`[ \t\n]+" default) + (setq default (substring default (match-end 0)))) + (while (string-match "[ \t\n]+\\'" default) + (setq default (substring default 0 (match-beginning 0)))) + (if (string-match "[ \t\n]" default) + (setq default nil)))) + (or default (setq default (energize-edit-definition-default))) + (setq def + (if (connected-to-energize-p) + (completing-read + (if default + (format "Edit definition [%s]: " default) + "Edit definition: ") + nil nil; 'energize-edit-def-predicate + nil nil) + (or (and (fboundp 'find-tag-tag) (fboundp 'find-tag-default)) + (require 'tags "etags")) + (find-tag-tag "Edit definition: "))) + (if (or (equal "" def) + (equal '("") def)) + (setq def default)) + def)))) + (if (connected-to-energize-p) + ;; FIXME - this should fall back on tags if it fails...we might be + ;; searching for elisp or something... + (energize-execute-command "editdef" () (if (consp def) (car def) def) t) + (find-tag def))) + +(define-key global-map "\M-." 'energize-edit-definition) +(define-key global-map "\M-B" 'energize-build-a-target) ; M-Sh-B + +(defun disconnect-from-energize-query () + "Disconnect this emacs from the Energize server, after confirming." + (interactive) + (or (y-or-n-p "Disconnect from Energize? ") (error "not confirmed")) + (disconnect-from-energize)) + + +;;; Functions to add commands to the project buffers +(defun energize-insert-slots (got-to-top-p l) + (if (not (eq major-mode 'energize-project-mode)) + (error "Command available only in project buffers")) + ;; move to a suitable place + (if got-to-top-p + (beginning-of-buffer) + (beginning-of-line)) + ;; go before "Associated Projects" and "Related Files" + (if (or (search-backward "Related Projects:" () t) + (search-backward "Associated Files:" () t) + (looking-at "Related Projects:") + (looking-at "Associated Files:")) + (previous-line 2)) + ;; find empty space + (while (and (not (looking-at "$")) + (not (eq (point) (point-max)))) + (next-line 1)) + (newline) + (save-excursion + (mapcar '(lambda (i) (insert i) (newline)) l)) + ;; this is magic + (forward-char 18)) + +(defun energize-insert-rule () + (interactive) + (energize-insert-slots + t + '(" Rules:" + " <rule>: lcc -Xez -c -g -Xa -o $object $source"))) + +(defun energize-insert-object-file-target () + (interactive) + (energize-insert-slots + () + '(" Object File: <object-file>" + " Source File: <source-file>" + " Build Rule: <rule>"))) + +(defun energize-insert-executable-target () + (interactive) + (energize-insert-slots + () + '(" Executable: <executable>" + " Build Command: lcc -Xf -Xez -o $object <object-file> ..."))) + +(defun energize-insert-library-target () + (interactive) + (energize-insert-slots + () + '(" Library: <library>" + " Build Command: energize_ar -Xez -remove -ranlib clq $object \\" + " <object-file> ..."))) + +(defun energize-insert-collection-target () + (interactive) + (energize-insert-slots + () + '(" Collection: <collection>" + " Build Command: energize_collect -Xez -o $object <object-file> ..."))) + +(defun energize-insert-file-target () + (interactive) + (energize-insert-slots + () + '(" File Target: <target>" + " Dependencies: <target> ..." + " Build Command: <shell-command>"))) + +(defun energize-insert-target-target () + (interactive) + (energize-insert-slots + () + '(" Target: <target>" + " Dependencies: <target> ..." + " Build Command: <shell-command>"))) + + + +;;; Keymaps for Energize buffers. + +(defvar energize-map nil "*Parent keymap for all Energize buffers") +(defvar energize-top-level-map nil "*Keymap for the Energize top-level buffer") +(defvar energize-debugger-map nil "*Keymap for Energize debugger buffers") +(defvar energize-breakpoint-map nil "*Keymap for Energize breakpoint-lists") +(defvar energize-browser-map nil "*Keymap for Energize browser buffers") +(defvar energize-project-map nil "*Keymap for Energize project buffers") +(defvar energize-no-file-project-map nil + "*Keymap for Energize project buffers not associated with a file") +(defvar energize-source-map nil "*Keymap for Energize source buffers") + +(defvar energize-mode-hook nil + "Hook called when each energize buffer is created.") +(defvar energize-top-level-mode-hook nil + "Hook called when the energize top-level buffer is created.") +(defvar energize-project-mode-hook nil + "Hook called when an Energize project buffer is created.") +(defvar energize-no-file-project-mode-hook nil + "Hook called when an Energize project buffer with no file is created.") +(defvar energize-breakpoint-mode-hook nil + "Hook called when an Energize breakpoint-list buffer is created.") +(defvar energize-browser-mode-hook nil + "Hook called when an Energize browser buffer is created.") +(defvar energize-log-mode-hook nil + "Hook called when an Energize log buffer is created.") +(defvar energize-manual-mode-hook nil + "Hook called when an Energize manual buffer is created.") +(defvar energize-source-mode-hook nil + "Hook called when any source buffer is placed in the Energize minor-mode.") + + +(if energize-map + nil + (setq energize-map (make-sparse-keymap)) + (set-keymap-name energize-map 'energize-map) + (define-key energize-map "\^C\^F" 'energize-find-project) + (define-key energize-map "\^C\^B\^E" 'energize-browse-error) + (define-key energize-map "\^C\^B\^L" 'energize-browse-language-elt) + (define-key energize-map "\^C\^B\^T" 'energize-browse-tree) + (define-key energize-map "\^C\^B\^C" 'energize-browse-class) +;; now in global-map +;; (define-key energize-map "\M-B" 'energize-build-a-target) ; M-Sh-B + (define-key energize-map "\M-C" 'energize-default-compile-file) ; M-Sh-C + (define-key energize-map 'button3 'energize-popup-menu) + ) + +(if energize-top-level-map + nil + (setq energize-top-level-map (make-sparse-keymap)) + (set-keymap-name energize-top-level-map 'energize-top-level-map) + (set-keymap-parent energize-top-level-map energize-map) + (suppress-keymap energize-top-level-map) + (define-key energize-top-level-map "?" 'describe-mode) + (define-key energize-top-level-map " " 'energize-top-next-project) + (define-key energize-top-level-map "n" 'energize-top-next-project) + (define-key energize-top-level-map "p" 'energize-top-prev-project) + (define-key energize-top-level-map "N" 'energize-top-next-project) + (define-key energize-top-level-map "P" 'energize-top-prev-project) + (define-key energize-top-level-map "\t" 'energize-top-next-project) + (define-key energize-top-level-map '(shift tab) 'energize-top-prev-project) + (define-key energize-top-level-map '(control I) 'energize-top-prev-project) + + (define-key energize-top-level-map "Q" 'disconnect-from-energize-query) + + (define-key energize-top-level-map "d" 'energize-top-debug) + (define-key energize-top-level-map "\^D" 'energize-top-delete-project) + (define-key energize-top-level-map "e" 'energize-top-edit-project) + ) + +(if energize-project-map + nil + (setq energize-project-map (make-sparse-keymap)) + (set-keymap-name energize-project-map 'energize-project-map) + (set-keymap-parent energize-project-map energize-map) + ;;(suppress-keymap energize-project-map) + ;;(define-key energize-project-map "\t" 'energize-project-next-field) + ;;(define-key energize-project-map '(shift tab) 'energize-project-prev-field) + ;;(define-key energize-project-map '(control I) 'energize-project-prev-field) + + (define-key energize-project-map "\^C\^I" 'energize-import-file) + (define-key energize-project-map "\^C\^E" 'energize-project-edit-file) + (define-key energize-project-map "\^C\^S\^A" 'energize-project-sort-alpha) + (define-key energize-project-map "\^C\^S\^L" 'energize-project-sort-link) + (define-key energize-project-map "\^C\^V\^N" 'energize-project-view-names) +; (define-key energize-project-map "\^C\^V\^L" 'energize-project-view-long) + (define-key energize-project-map "\^C\^V\^C" 'energize-project-view-options) + ) + + +(if energize-no-file-project-map + nil + (setq energize-no-file-project-map (make-sparse-keymap)) + (set-keymap-name energize-no-file-project-map 'energize-no-file-project-map) + (set-keymap-parent energize-no-file-project-map energize-map)) + +(if energize-breakpoint-map + nil + (setq energize-breakpoint-map (make-sparse-keymap)) + (set-keymap-name energize-breakpoint-map 'energize-breakpoint-map) + (set-keymap-parent energize-breakpoint-map energize-map) + ) + +(if energize-browser-map + nil + (setq energize-browser-map (make-sparse-keymap)) + (set-keymap-name energize-browser-map 'energize-browser-map) + (set-keymap-parent energize-browser-map energize-map) + ) + +(if energize-source-map + nil + (setq energize-source-map (make-sparse-keymap)) + (set-keymap-name energize-source-map 'energize-source-map) + (set-keymap-parent energize-source-map energize-map) +;; There are too many problems with using extents to determine where the +;; top level forms are... +;; (define-key energize-source-map "\M-\C-a" 'energize-beginning-of-defun) +;; (define-key energize-source-map "\M-\C-e" 'energize-end-of-defun) + ) + +(defvar energize-menu-state nil + "State of the energize menu items of the buffer. +Automatically updated by the kernel when the state changes") + +(defvar energize-default-menu-state nil + "State of the energize default menu items. +Automatically updated by the kernel when the state changes") + +(defun energize-mode-internal () + ;; initialize stuff common to all energize buffers (hooks, etc). + (make-local-hook 'write-file-data-hooks) + (add-hook 'write-file-data-hooks 'energize-write-data-hook t t) + ;; + (make-local-variable 'revert-buffer-insert-file-contents-function) + (setq revert-buffer-insert-file-contents-function + 'energize-revert-buffer-insert-file-contents-hook) + ;; + (make-local-hook 'kill-buffer-hook) + (add-hook 'kill-buffer-hook 'energize-kill-buffer-hook nil t) + ;; + (make-local-variable 'require-final-newline) + (setq require-final-newline t) + ;; + (make-local-variable 'energize-menu-state) + ;; + (run-hooks 'energize-mode-hook)) + +(defun energize-non-file-mode-internal () + ;; do magic associated with energize-modes for buffers which are not + ;; and cannot be associated with files. +; (or (null buffer-file-name) +; (equal buffer-file-name mode-name) +; (error +; "This buffer is associated with a file, it can't be placed in %s mode" +; mode-name)) + ;; hack so that save-file doesn't prompt for a filename. + (or buffer-file-name + (setq buffer-file-name (buffer-name))) + (set (make-local-variable 'version-control) 'never) + nil) + +;; don't create random new buffers in these modes +(put 'energize-top-level-mode 'mode-class 'special) +(put 'energize-project-mode 'mode-class 'special) +(put 'energize-no-file-project-mode 'mode-class 'special) +(put 'energize-breakpoint-mode 'mode-class 'special) +(put 'energize-browser-mode 'mode-class 'special) +(put 'energize-log-mode 'mode-class 'special) + +(defun energize-top-level-mode () + "Major mode for the Energize top-level buffer. +In addition to normal cursor-motion commands, the following keys are bound: +\\{energize-top-level-map}" + (interactive) + (energize-mode-internal) + (use-local-map energize-top-level-map) + (setq major-mode 'energize-top-level-mode + mode-name "Energize") + (energize-non-file-mode-internal) + ;; the default of "energize: Energize" is not very attractive. + (if (equal frame-title-format "%S: %b") + (set (make-local-variable 'frame-title-format) "%S: Top-Level")) + (run-hooks 'energize-top-level-mode-hook)) + + +(defun energize-project-mode () + "Major mode for the Energize Project buffers. +In addition to the normal editing commands, the following keys are bound: +\\{energize-project-map}" + (interactive) + (energize-mode-internal) + (use-local-map energize-project-map) + (setq major-mode 'energize-project-mode + mode-name "Project") + ;; in later revisions of the kernel the project is really a file. + (if (< (cdr (energize-protocol-level)) 8) + (energize-non-file-mode-internal)) + (run-hooks 'energize-project-mode-hook)) + +(defun energize-no-file-project-mode () + "Major mode for the Energize Project buffers not associated with a file. +In addition to the normal editing commands, the following keys are bound: +\\{energize-no-file-project-map}" + (interactive) + (energize-mode-internal) + (use-local-map energize-no-file-project-map) + (setq major-mode 'energize-no-file-project-mode + mode-name "NoFileProject") + (energize-non-file-mode-internal) + (run-hooks 'energize-no-file-project-mode-hook)) + +(defun energize-breakpoint-mode () + "Major mode for the Energize Breakpoint-list buffers. +In addition to the normal editing commands, the following keys are bound: +\\{energize-breakpoint-map}" + (interactive) + (energize-mode-internal) + (use-local-map energize-breakpoint-map) + (setq major-mode 'energize-breakpoint-mode + mode-name "Breakpoint") + (energize-non-file-mode-internal) + (run-hooks 'energize-breakpoint-mode-hook)) + +(defun energize-browser-mode () + "Major mode for the Energize Browser buffers. +In addition to the normal editing commands, the following keys are bound: +\\{energize-browser-map}" + (interactive) + (energize-mode-internal) + (use-local-map energize-browser-map) + (setq major-mode 'energize-browser-mode + mode-name "Browser") + (energize-non-file-mode-internal) + (run-hooks 'energize-browser-mode-hook)) + +(defun energize-log-mode () + "Major mode for the Energize Error Log and System Log buffers. +In addition to the normal editing commands, the following keys are bound: +\\{energize-map}" + (interactive) + (energize-mode-internal) + (use-local-map energize-map) + (setq major-mode 'energize-log-mode + mode-name "Energize-Log") + (energize-non-file-mode-internal) + (run-hooks 'energize-log-mode-hook)) + +(defun energize-manual-mode () + "Major mode for the Energize UNIX Manual buffers. +In addition to the normal editing commands, the following keys are bound: +\\{energize-map}" + (interactive) + (energize-mode-internal) + (use-local-map energize-map) + (setq major-mode 'energize-manual-mode + mode-name "Energize-Manual") + (energize-non-file-mode-internal) + (run-hooks 'energize-manual-mode-hook)) + +(defvar energize-source-mode nil) +;;(put 'energize-source-mode 'permanent-local t) ; persists beyond mode-change + +;;; Add energize-source-mode to minor-mode-alist so that it shows up in +;;; the modeline when true. +;;; +(or (assq 'energize-source-mode minor-mode-alist) + (setq minor-mode-alist + (append minor-mode-alist + '((energize-source-mode " Energize"))))) + + +(defun energize-source-minor-mode () + "Minor mode for adding additional keybindings to Energize source buffers. +The following key bindings are added: +\\{energize-source-map} + +Since this minor mode defines keys, once it gets turned on you can't really +turn it off." + (interactive) + (energize-mode-internal) + (make-local-variable 'energize-source-mode) + (setq energize-source-mode t) + (let ((source-map energize-source-map) + (dest-map (make-sparse-keymap))) + (set-keymap-parent dest-map (current-local-map)) + (set-keymap-name dest-map 'energize-minor-mode-map) + (while source-map + (let (mapper prefixes) + (setq mapper (function (lambda (key val) + (if (keymapp val) + (let ((prefixes (append prefixes + (cons key nil)))) + (map-keymap val mapper)) + (define-key dest-map + (apply 'vector + (append prefixes (cons key nil))) + val) + )))) + (map-keymap source-map mapper)) + (setq source-map (keymap-parent source-map))) + (use-local-map dest-map)) + (run-hooks 'energize-source-mode-hook)) + + +;;; Commands in source buffers + +(defun recenter-definition () + "Position the beginning of the current definition at the top of the frame." + (interactive) + (end-of-line) + (if (eq major-mode 'c++-mode) + (c++-beginning-of-defun 1) + (beginning-of-defun 1)) + (recenter 1)) + +(define-key global-map "\M-\C-r" 'recenter-definition) + +(defun energize-hide-error-glyphs-in-form () + "Hides the error icons in the current toplevel form. +You cannot get them back until you recompile the file." + (interactive) + (save-excursion + (save-restriction + (let ((start (progn (energize-beginning-of-defun) (point))) + (end (progn (energize-end-of-defun) (point))) + e) + (narrow-to-region start end) + (goto-char (point-min)) + (setq e (extent-at (point))) + (while (and e + (< (extent-end-position e) (point-max))) + (if (extent-property e 'begin-glyph) + (set-extent-begin-glyph e nil)) + (setq e (next-extent e))))))) + +;;; Dired-like commands + +(defun energize-next-extent-for (command prev not-this-one) + (let ((last-e (if not-this-one 'none nil)) + e result) + (save-excursion + (while (not (or result + (if prev (bobp) (eobp)))) + (setq e (extent-at (point) (current-buffer))) + (if (and (not (eq e last-e)) + (not (eq last-e 'none))) + (setq result + (energize-menu-item-for-name e command))) + (forward-char (if prev -1 1)) + (setq last-e e))) + (if result e))) + +(defun energize-next-extent-on-line-for (command not-this-one) + (save-excursion + (save-restriction + (narrow-to-region (point) (progn (end-of-line) (point))) + (goto-char (point-min)) + (energize-next-extent-for command nil not-this-one)))) + + +;;; commands in the top-level buffer + +(defun energize-top-next-project () + "Position the cursor at the beginning of the following project." + (interactive) + (let ((p (point))) + (let ((e (energize-next-extent-for "editproject" nil t))) + (if (and e (= p (extent-start-position e))) + (save-excursion + (forward-char (extent-length e)) + (setq e (energize-next-extent-for "editproject" nil t)))) + (if e + (goto-char (extent-start-position e)) + (error "no next project"))))) + +(defun energize-top-prev-project () + "Position the cursor at the beginning of the preceeding project." + (interactive) + (let ((p (point))) + (let ((e (energize-next-extent-for "editproject" t t))) + (if (and e (= p (extent-start-position e))) + (save-excursion + (forward-char -1) + (setq e (energize-next-extent-for "editproject" t t)))) + (if e + (goto-char (extent-start-position e)) + (error "no previous project"))))) + +(defun energize-top-execute-command (command) + (let ((e (or (energize-next-extent-on-line-for command nil) + (error + (concat "no following field on this line that handles the `" + command "' Energize command."))))) + (energize-execute-command command e))) + +(defun energize-top-debug () + "Execute the `Debug' command on the project at or following point." + (interactive) + (energize-top-execute-command "debugprogram")) + +(defun energize-top-delete-project () + "Delete the project at or following point." + (interactive) + (energize-top-execute-command "deleteproject")) + +(defun energize-top-edit-project () + "Edit the project at or following point." + (interactive) + (energize-top-execute-command "editproject")) + +;;; commands in the project buffer + +(defun energize-project-next-field (&optional prev) + (interactive) + (let ((e (extent-at (point) (current-buffer)))) + (if e + (if prev + (goto-char (1- (extent-start-position e))) + (goto-char (1+ (extent-end-position e))))) + (while (null (extent-at (point) (current-buffer))) + (forward-char (if prev -1 1))) + (while (extent-at (point) (current-buffer) 'write-protected) + (forward-char (if prev -1 1))) + (if prev + (if (setq e (extent-at (point) (current-buffer))) + (goto-char (extent-start-position e)) + (while (not (extent-at (point) (current-buffer))) + (forward-char -1)))))) + +(defun energize-project-prev-field () (interactive) + (energize-project-next-field t)) + +(defun energize-project-edit-file () (interactive) + (energize-top-execute-command "editfile")) + + +(defun energize-project-prune-unused-rules () + "Deletes all unused rules from the Rules: part of a Project buffer, +and renumbers the remaining rules sequentially." + (interactive) + (save-excursion + (goto-char (point-min)) + (re-search-forward "^[ \t]+Rules:") + (forward-line 1) + (let ((rules-regexp "^[ \t]*\\(\\.[a-zA-Z]+\\(([0-9]+)\\)?\\):") + (all-rules nil) + eor) + ;; + ;; Gather the contents of the Rule section, and find its end. + ;; + (save-excursion + (while (looking-at rules-regexp) + (setq all-rules (cons (list (buffer-substring (match-beginning 1) + (match-end 1)) + (point-marker)) + all-rules)) + (while (progn (end-of-line) (= (preceding-char) ?\\)) + (forward-line 1)) + (forward-line 1)) + (setq eor (point-marker))) + (setq all-rules (nreverse all-rules)) + (let ((rest all-rules) + rule) + ;; + ;; Walk through the buffer gathering references to the rules. + ;; + (while rest + (setq rule (car rest)) + (goto-char eor) + (let ((pattern (concat "^[ \t]+" (regexp-quote (car rule)) ":"))) + (while (re-search-forward pattern nil t) + (setcdr (cdr rule) + (cons (set-marker (make-marker) (match-beginning 0)) + (cdr (cdr rule)))))) + (setq rest (cdr rest))) + ;; + ;; Delete those rules that have no references. + ;; + (goto-char eor) + (setq rest all-rules) + (while rest + (setq rule (car rest)) + (if (null (cdr (cdr rule))) + (let ((p (nth 1 rule))) + (goto-char p) + (while (progn (end-of-line) (= (preceding-char) ?\\)) + (forward-line 1)) + (forward-line 1) + (delete-region p (point)) + (set-marker p nil) + (setq all-rules (delq rule all-rules)) + )) + (setq rest (cdr rest))) + ;; + ;; Renumber the remaining rules sequentially. + ;; + (goto-char eor) + (setq rest all-rules) + (let ((i 1)) + (while rest + (setq rule (car rest)) + (let ((referents (cdr rule))) ; including definition + (while referents + (goto-char (car referents)) + (or (and (looking-at + (concat "^[ \t]+" (regexp-quote (car rule)) ":")) + (looking-at "[^:(]+\\((\\([0-9]+\\))\\|\\):")) + (error "internal error")) + (if (null (match-beginning 2)) + (progn + (goto-char (match-beginning 1)) + (insert "(" (int-to-string i) ")")) + (goto-char (match-beginning 2)) + (delete-region (match-beginning 2) (match-end 2)) + (insert (int-to-string i))) + (set-marker (car referents) nil) + (setq referents (cdr referents)))) + (setq i (1+ i)) + (setq rest (cdr rest)))) + ;; + ;; TODO: + ;; - order the Rule Users list in the same order as the Rules list. + ;; - or, order the Rule Users list by number of files, and then + ;; order the Rules list the same as that (numbered sequentially.) + ;; - or, order the Rules list by length-of-rule (= complicatedness.) + ) + (set-marker eor nil))))