;;;; tdtd.el --- Tony's DTD mode ;; $Id: tdtd.el,v 1.41 1999/03/26 22:08:00 tkg Exp $ ;; $Name: tdtd071 $ ;; Copyright (C) 1996, 1997, 1998, 1999 Tony Graham ;; Author: Tony Graham ;; Contributors: Juanma Barranquero, Adam di Carlo ;;; This file is not part of GNU Emacs. ;; This program 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 ;; of the License, or (at your option) any later version. ;; ;; This program 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 this program; if not, write to the Free Software ;; Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ;;;; Commentary: ;; Macros for editing DTDs ;; Requires tdtd-font.el ;; Requires 'etags for `find-tag-default' ;; Requires 'imenu for "Goto" menu ;; Requires 'make-regexp for tdtd-font.el ;; Requires 'reporter for `dtd-submit-bug-report' ;; Send bugs to tdtd-bug@menteith.com ;;;; Code: (eval-and-compile (require 'font-lock)) (eval-and-compile (autoload 'sgml-validate "psgml")) (eval-and-compile (autoload 'reporter-submit-bug-report "reporter")) ;; XEmacs users don't always have imenu.el installed, so use ;; condition-case to cope if we cause an error by requiring imenu. (eval-and-compile (condition-case nil (require 'imenu) (error nil))) ;; We need etags for `find-tag-default' (require 'etags) (require 'tdtd-font "tdtd-font") (provide 'tdtd) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Version information (defconst tdtd-version "0.7.1" "Version number of tdtd.") (defun tdtd-version () "Returns the value of the variable tdtd-version." tdtd-version) (defconst tdtd-maintainer-address "tdtd-bug@menteith.com") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Variables (defvar dtd-autodetect-type t "*Non-nil enables autodetection of XML or SGML when entering dtd-mode.") (defvar dtd-xml-flag nil "*Non-nil enables XML-specific behaviour, where implemented.") (defvar dtd-decl-flag nil "*Non-nil enables SGML Declaration-specific behaviour, where implemented.") (defvar dtd-sys-decl-flag nil "*Non-nil enables System Declaration-specific behaviour, where implemented." ) (defun dtd-xml-p () "Returns t when XML-specific behaviour is enabled, otherwise returns nil." (if dtd-xml-flag t nil)) (defun dtd-decl-p () "Returns t when SGML Declaration-specific behaviour is enabled, otherwise nil." (if dtd-decl-flag t nil)) (defun dtd-sys-decl-p () "Returns t when System Declaration-specific behaviour is enabled, otherwise nil." (if dtd-sys-decl-flag t nil)) (defvar dtd-indent-tabs-mode nil "*Initial value of indent-tabs-mode on entering dtd-mode") (defvar dtd-default-filespec "*.dtd *.ent" "*Inital prompt value for `dtd-etags''s FILESPEC argument.") (defvar dtd-filespec-history (list dtd-default-filespec) "Minibuffer history list for `dtd-etags' and `dtd-grep''s FILESPEC argument.") (defvar dtd-grep-pattern-history nil "Minibuffer history list for `dtd-grep''s PATTERN argument.") (defvar dtd-grep-case-sensitive-flag nil "*Non-nil disables case insensitive searches by `dtd-grep'.") (defvar dtd-grep-command-format "grep -n %s -- '%s' %s" "*Format string for the grep command called by `dtd-grep'.") ;; Aren't shell escapes fun! ;; "\" -> "\\\\\\" ;; any other significant character -> "\\" + character (defvar dtd-etags-regex-option "--regex=/\\<\\!\\\\\\(ELEMENT\\\\\\|ENTITY\\[\\ \\\\\\t]\\+%\\\\\\|NOTATION\\\\\\|ATTLIST\\\\\\)\\[\\ \\\\\\t]\\+\\\\\\(\\[^\\ \\\\\\t]\\+\\\\\\)/\\\\\\2/" "*Complete, including \"--regex=\", etags regular expression option string for the etags command line for extracting tags (in the Emacs sense) from DTDs.") ;; Use this regex with 4NT: ;; "--regex=\"/" "*Markup Delimiter Close (MDC) character sequence") (defvar dtd-comment-start "" "*Comment end character sequence") ;; SGML Syntactic Literals (defvar dtd-empty-literal "EMPTY" "EMPTY element syntactic literal") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Variables controlling indentation (defvar dtd-dtd-max-column 70 "*Rightmost column for text in the DTD") (defvar dtd-mdc-indent-column (1- dtd-dtd-max-column) "*Column at which to insert a MDC") (defvar dtd-comment-start-column 25 "*Column for starting text in a comment") (defvar dtd-comment-max-column (- dtd-dtd-max-column 3) "*Maximum column number for text in a comment") ;; (defvar dtd-sgml-comment-max-column (- dtd-dtd-max-column 2)) (defvar dtd-element-name-column 12 "*Column for element name in an element declaration") (defvar dtd-element-tag-omission-column 25 "*Column for inserting the omissibility indicators, if used") (defvar dtd-element-content-spec-start-column 29 "*Column at which to start content model") (defvar dtd-element-content-spec-continuation-column 30 "*Column at which to start second and subsequent lines of content model") (defvar dtd-xml-element-content-spec-start-column 25 "*Column at which to start XML content model") (defvar dtd-xml-element-content-spec-continuation-column (1+ dtd-xml-element-content-spec-start-column) "*Column at which to start second and subsequent lines of XML content model") (defvar dtd-entity-entity-value-start-column 24 "*Column at which to start parameter entity's entity value") (defvar dtd-entity-entity-value-continuation-column (1+ dtd-entity-entity-value-start-column) "*Column at which to start second and subsequent lines of parameter entity value") (defvar dtd-attribute-name-column (+ dtd-element-name-column 2) "*Indent for inserting attribute names in attribute definitions") (defvar dtd-attribute-default-column (- dtd-dtd-max-column 10) "*Indent for inserting attribute defaults in attribute definitions") (defvar dtd-line-comment (concat dtd-comment-start " " (make-string (- dtd-dtd-max-column (length (concat dtd-comment-start " " dtd-comment-end))) ?=) " " dtd-comment-end "\n") "*Separator comment line: \"\".") (defvar dtd-init-comment-column 16) (defvar dtd-init-comment-fill-prefix (concat dtd-comment-start (make-string (- dtd-init-comment-column (length dtd-comment-start)) ?\ )) "*Prefix for comments making up the initial comment in a module.") (defvar dtd-design-comment-start-column (+ (length dtd-comment-start) 2) "*Column for starting text in a \"Design Considerations\" comment") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Functions (defun dtd-autodetect-type () "Check for initial declaration and set flags accordingly." (interactive) (save-excursion (goto-char (point-min)) (skip-chars-forward "\\s-") (cond ((looking-at "<\\?xml") (setq dtd-xml-flag t) (setq dtd-decl-flag nil) (setq dtd-sys-decl-flag nil)) ((looking-at " (current-column) target-column) (insert "\n")) (indent-to target-column)) (defun dtd-center-comment (comment) "Center a comment on line" (interactive "sComment: ") (insert dtd-comment-start) (indent-to (/ (- dtd-dtd-max-column (length comment)) 2)) (insert comment) (indent-to dtd-comment-max-column) (insert dtd-comment-start) (insert "\n")) (defun dtd-recenter-comment () "Recenter text in a presumably modified comment line" (interactive) (save-excursion (save-match-data (beginning-of-line) (if (looking-at "^$") (let ((contents (match-string 1))) (delete-region (match-beginning 0) (match-end 0)) (dtd-center-comment contents)))))) (defun dtd-filled-comment (comment) "Insert a comment with \"=\" in most of the whitespace" (interactive "sComment: ") (dtd-comment comment "=")) ;; TDTD house style puts all comments starting on a favourite column (defun dtd-comment (comment &optional fill-character) "Insert COMMENT starting at the usual column. With a prefix argument, e.g. \\[universal-argument] \\[dtd-comment], insert separator comment lines above and below COMMENT in the manner of `dtd-big-comment'." (interactive "sComment: ") (if current-prefix-arg (insert dtd-line-comment)) (insert "\n") (backward-char) (let ((fill-column (1- dtd-comment-max-column)) (fill-prefix (make-string (1- dtd-comment-start-column) ?\ )) (comment-start dtd-init-comment-fill-prefix) (saved-auto-fill-function auto-fill-function)) (auto-fill-mode 1) (insert dtd-comment-start) (if (and (stringp fill-character) (not (string-equal fill-character ""))) (progn (insert " ") (insert (make-string (- dtd-comment-start-column (current-column) 2) (string-to-char fill-character))))) (indent-to (1- dtd-comment-start-column)) (fill-region (point) (save-excursion (insert comment) (point)) nil 1 1) ;; The fill does the right thing, but in Emacs 19.34 it always ends with ;; an extra newline, so we delete the newline. (if (bolp) (delete-backward-char 1)) (if (not saved-auto-fill-function) (auto-fill-mode 0)) (if (and (stringp fill-character) (not (string-equal fill-character ""))) (progn (insert " ") (insert (make-string (- dtd-comment-max-column (current-column) 1) (string-to-char fill-character))))) (indent-to dtd-comment-max-column) (insert dtd-comment-end) (insert "\n") (if current-prefix-arg (insert dtd-line-comment)) (if font-lock-mode (save-excursion (font-lock-fontify-region (dtd-font-lock-region-point-min) (dtd-font-lock-region-point-max)))))) (defun dtd-recomment () "Fix text position in a presumably modified comment line" (interactive) (save-excursion (save-match-data (beginning-of-line) (if (looking-at "^\\(\\)?\n") (let ((contents (match-string 2))) (delete-region (match-beginning 0) (match-end 0)) (dtd-comment contents)))))) (defun dtd-join-comments (mark point) "Join comments by removing the \"interior\" comment delimiters." (interactive "r") (save-excursion (save-match-data (goto-char (min mark point)) ;; Insert a comment start if there isn't one at the beginning. (if (not (looking-at dtd-comment-start)) (progn (if (looking-at (make-string (length dtd-comment-start) ?\ )) (delete-region (match-beginning 0) (match-end 0))) (insert dtd-comment-start))) ;; Delete the "interior" comment delimiters (goto-char (min mark point)) (while (re-search-forward (concat "[ \t\n]*" dtd-comment-end "\n" dtd-comment-start) (max mark point) t) (replace-match (concat "\n" (make-string (length dtd-comment-start) ?\ )) nil nil)))) ;; Insert a comment end if there isn't one at the end ;; (goto-char (- (max mark point) ;; (length dtd-comment-end))) ;; (if (not (looking-at dtd-comment-end)) ;; (if font-lock-mode (save-excursion (font-lock-fontify-region (dtd-font-lock-region-point-min) (dtd-font-lock-region-point-max))))) (defun dtd-declare-element (element-tag element-name element-comment content-spec) "Insert an element type declaration at the current point. ELEMENT-TAG is the element type name as it appears in start- and end-tags. ELEMENT-NAME is a descriptive name for the element. It is output as a comment. ELEMENT-COMMENT is a further comment about the element. This comment is not output if ELEMENT-COMMENT is an empty string. CONTENT-SPEC is the element type's content specification. When dtd-xml-flag in nil, the omitted tag minimization parameter is output as part of the element type declaration. When CONTENT-SPEC is \"EMPTY\", the minimization parameter is \"- o\", otherwise it is \"- -\" When dtd-upcase-name-comment-flag is non-nil, the text of the descriptive element name is converted to uppercase before output. ELEMENT-NAME is not prompted for if dtd-prompt-descriptive-name is nil, and ELEMENT-COMMENT is not prompted for if dtd-prompt-descriptive-comment is nil. An example inserted element type declaration is as follows: " (interactive ;; Hackery and fakery (let ((element-tag nil)) (list (progn ;; set element-tag to what's read from the minibuffer (setq element-tag (dtd-read-from-minibuffer "Element tag: " (car dtd-referenced-element-type-names) 'dtd-referenced-element-type-names)) ;; complain if element-tag is an empty string and there's no ;; default (if (string-equal element-tag "") (if default (setq element-tag default) (error "You must supply an element tag name")) ;; return element-tag is we had one already element-tag)) (if dtd-prompt-descriptive-name (read-from-minibuffer (format "<%s> descriptive name: " element-tag) ;; use element-tag as the default for ;; the descriptive name since we often ;; base the descriptive name on it element-tag nil nil nil) "") (if dtd-prompt-descriptive-comment (read-from-minibuffer (format "<%s> comment: " element-tag) nil nil nil 'dtd-element-comment-history) "") (read-from-minibuffer (format "<%s> content spec: " element-tag) nil nil nil 'dtd-element-content-spec-history)))) (if (not (string-equal element-name "")) (progn (if dtd-upcase-name-comment-flag (setq element-name (upcase element-name))) (dtd-comment element-name))) (if (not (string-equal element-comment "")) (dtd-comment element-comment)) (insert "\n") (backward-char) (insert "\" that closes the attribute list declaration." (interactive (let ((attribute-tag nil)) ;; Hackery and fakery ;; If we're not after "" nil t) (looking-at "]" nil t) (1+ (point)))))) (dtd-indent-or-newline-to (1- dtd-attribute-name-column)) (if (string-equal (substring attribute-tag 0 1) "%") (delete-backward-char 1)) (insert attribute-tag) (dtd-indent-or-newline-to (1- dtd-comment-start-column)) (fill-region (point) (save-excursion (insert attribute-type) (insert "\n") (point)) nil 1 1) ;; The fill does the right thing, but in Emacs 19.34 it always ends with ;; an extra newline, so we delete the newline. (if (bolp) (delete-backward-char 1)) ;; We want the default to line up on dtd-attribute-default-column ;; unless it's too long, in which case we want one space between ;; the end of the default and the mdc (>). (if (< (length attribute-default) (- dtd-dtd-max-column dtd-attribute-default-column 1)) (dtd-indent-or-newline-to (1- dtd-attribute-default-column)) (dtd-indent-or-newline-to (- dtd-dtd-max-column (length attribute-default) 2))) (insert attribute-default) ;; Insert an mdc only if there isn't one between here and the ;; next mdo. (if (or (looking-at "<") (not (save-excursion (re-search-forward "^" nil t) (goto-char (match-beginning 0)) (looking-at ">")))) (progn (dtd-indent-or-newline-to dtd-mdc-indent-column) (insert dtd-sgml-mdc))) (insert "\n") (if font-lock-mode (save-excursion (font-lock-fontify-region (dtd-font-lock-region-point-min) (dtd-font-lock-region-point-max)))))) (defun dtd-declare-attribute-list (element-type-name) "Declare an attribute list." (interactive (list (dtd-read-from-minibuffer "Element type: " (save-excursion (save-match-data (re-search-backward "^ %notation-tag; " (interactive ;; Hackery and fakery (let ((notation-tag nil)) (list (progn ;; set notation-tag to what's read from the minibuffer (setq notation-tag (dtd-read-from-minibuffer "Notation tag: " (car dtd-referenced-notation-type-names) 'dtd-referenced-notation-type-names)) ;; complain if notation-tag is an empty string and there's no ;; default (if (string-equal notation-tag "") (if default (setq notation-tag default) (error "You must supply an notation tag name")) ;; return notation-tag is we had one already notation-tag)) (if dtd-prompt-descriptive-name (read-from-minibuffer (format "`%s' descriptive name: " notation-tag) ;; use notation-tag as the default for ;; the descriptive name since we often ;; base the descriptive name on it notation-tag nil nil nil) "") (if dtd-prompt-descriptive-comment (read-from-minibuffer (format "`%s' comment: " notation-tag) nil nil nil 'dtd-notation-comment-history) "") (read-from-minibuffer (format "`%s' content spec: " notation-tag) nil nil nil 'dtd-notation-content-spec-history)))) (if (not (string-equal notation-name "")) (progn (if dtd-upcase-name-comment-flag (setq notation-name (upcase notation-name))) (dtd-comment notation-name))) (if (not (string-equal notation-comment "")) (dtd-comment notation-comment)) ;; (insert "\n") ;; (backward-char) (insert " %entity-tag; " ;; (interactive "sEntity tag: \nsEntity name: \nsEntity comment: \nsPublic Identifier: \nsSystem Identifier: ") (interactive ;; Hackery and fakery (let ((entity-tag nil)) (list (setq entity-tag (read-from-minibuffer "External entity tag: " nil nil nil 'dtd-referenced-parameter-entity-names)) (if dtd-prompt-descriptive-name (read-from-minibuffer (format "`%%%s;\' descriptive name: " entity-tag) nil nil nil nil) "") (if dtd-prompt-descriptive-comment (read-from-minibuffer (format "`%%%s;' comment: " entity-tag) nil nil nil nil) "") (read-from-minibuffer (format "`%%%s;' public identifier: " entity-tag) nil nil nil 'dtd-external-entity-public-history) (read-from-minibuffer (format "`%%%s;' system identifier: " entity-tag) nil nil nil 'dtd-external-entity-system-history)))) (if (not (string-equal entity-name "")) (progn (if dtd-upcase-name-comment-flag (setq entity-name (upcase entity-name))) (dtd-comment entity-name))) ;; (insert "\n") (if (not (string-equal entity-comment "")) (progn (dtd-comment entity-comment) (insert "\n"))) ;; (insert "\n") ;; (backward-char) (insert " " (interactive ;; Hackery and fakery (let ((entity-tag nil)) (list (setq entity-tag (read-from-minibuffer "Entity tag: " nil nil nil 'dtd-referenced-parameter-entity-names)) (if dtd-prompt-descriptive-name (read-from-minibuffer (format "`%%%s;\' descriptive name: " entity-tag) nil nil nil nil) "") (if dtd-prompt-descriptive-comment (read-from-minibuffer (format "`%%%s;' comment: " entity-tag) nil nil nil nil) "") (read-from-minibuffer (format "`%%%s;' value: " entity-tag) nil nil nil 'dtd-parameter-entity-value-history)))) (if (not (string-equal entity-name "")) (progn (if dtd-upcase-name-comment-flag (setq entity-name (upcase entity-name))) (dtd-comment entity-name))) (if (not (string-equal entity-comment "")) (dtd-comment entity-comment)) (insert "). MDC = Markup Declaration Close" (interactive) (save-excursion (let ((current-point (point)) (mdc-column 0) (mdc dtd-sgml-mdc)) (if (if (looking-at dtd-sgml-mdo) 1 (re-search-backward (concat "^" dtd-sgml-mdo) nil t)) (progn (if (looking-at dtd-comment-start) (progn (setq mdc dtd-comment-end) (setq mdc-column dtd-comment-max-column)) (progn (setq mdc dtd-sgml-mdc) (setq mdc-column dtd-mdc-indent-column))) (goto-char current-point) (beginning-of-line) (cond ;; The main "looking-at" regular expression doesn't handle ;; line containing just "-->" or ">" very well, so handle ;; as special case. ((looking-at "[ \t]*\\(--\\)?>$") (progn ;; Delete to the end of line so we can then insert our mdc (delete-region (point) (save-excursion (end-of-line) (point))) (indent-to mdc-column) (insert mdc))) ;; This is the main "looking-at" regular expression that ;; handles most things ((looking-at "^\\(\\(]+\\([- \t]+[^- \t\n>]+\\)*\\)?\\)[ \t]*\\(\\(--\\)?>?\\)?$") (progn ;;(message ":%s:%s:%s:%s:" mdc mdc-column (match-string 4) (match-string 1)) (goto-char (match-end 1)) (delete-region (point) (save-excursion (end-of-line) (point))) (dtd-indent-or-newline-to mdc-column) (insert mdc)))))))) (if font-lock-mode (save-excursion (font-lock-fontify-region (dtd-font-lock-region-point-min) (dtd-font-lock-region-point-max))))) ;; A work in progress (defun dtd-fill-paragraph (&optional justify) "DTD fill paragraph function. A work in progress." (interactive "P") ;;(insert "DTD") (save-excursion (if dtd-autodetect-type (dtd-autodetect-type)) (let ((current-point (point)) (fill-column dtd-mdc-indent-column) (fill-prefix (make-string (if dtd-xml-flag (1- dtd-xml-element-content-spec-continuation-column) (1- dtd-element-content-spec-continuation-column)) ?\ )) (saved-auto-fill-function auto-fill-function)) (if (not (looking-at dtd-sgml-mdo)) (re-search-backward (concat "^" dtd-sgml-mdo) nil t)) (if (looking-at "\\(") ;; (progn ;; (delete-region (match-beginning 1) (match-end 1)) ;; (dtd-indent-or-newline-to dtd-mdc-indent-column) ;; (insert "$") )) ;; (dtd-insert-mdc) ;; (insert "\n") ;; (auto-fill-mode nil) (if font-lock-mode (save-excursion (font-lock-fontify-region (dtd-font-lock-region-point-min) (dtd-font-lock-region-point-max))))))) ;; (if (looking-at "ANY\\|CDATA\\|EMPTY\\|RCDATA") ;; (progn ;; (dtd-indent-or-newline-to ;; (1- dtd-element-content-spec-start-column))) ;; (message "It's something else")))))) ;; If we're not doing anything, return nil so the built-in function ;; will run ;; (font-lock-fontify-block)) ;; Functions that set fill-prefix, etc. for various regions in a DTD (defun dtd-comment-setup () "Setup the auto-fill variables for comments in the body of the DTD" (interactive) (setq fill-prefix (make-string (1- dtd-comment-start-column) ?\ )) (auto-fill-mode 'true) (setq fill-column dtd-comment-max-column)) (defun dtd-design-comment-setup () "Setup the auto-fill variables for \"Design Considerations\" comments" (interactive) (setq fill-prefix (make-string (1- dtd-design-comment-start-column) ?\ )) (auto-fill-mode 'true) (setq fill-column dtd-comment-max-column)) (defun dtd-declaration-setup () "Setup the auto-fill variables for declarations" (interactive) (setq fill-prefix (make-string (1- dtd-element-content-spec-continuation-column) ?\ )) (auto-fill-mode 'true) (setq fill-column dtd-mdc-indent-column)) (defun dtd-init-comment-setup () "Setup the auto-fill variables for initial comments" (interactive) (setq fill-prefix dtd-init-comment-fill-prefix) (auto-fill-mode 'true) (setq fill-column (- dtd-dtd-max-column (length dtd-comment-end)))) ;;(setq fill-paragraph-function 'dtd-fill-paragraph) ;; Not quite sure what this gets me, but these variables seem to apply ;; in auto-fill mode (make-local-variable 'comment-start) (setq comment-start "") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; imenu stuff (defun dtd-sort-alist (alist) "Sort an alist" (sort alist (lambda (a b) (string< (car a) (car b))))) (defun dtd-imenu-create-index-function () "Create an alist of elements, etc. suitable for use with imenu." (let ((element-alist '()) (notation-alist '()) (general-entity-alist '()) (parameter-entity-alist '())) (goto-char (point-min)) (while (re-search-forward "^" 'dtd-electric-mdc) (define-key dtd-mode-map '[(control c) (>)] 'dtd-insert-mdc) (define-key dtd-mode-map '[(control c) (control a)] 'dtd-declare-attribute) (define-key dtd-mode-map '[(control c) (control n)] 'dtd-declare-notation) (define-key dtd-mode-map '[(control c) (meta control %)] 'dtd-declare-external-entity) (define-key dtd-mode-map '[(control c) (control %)] 'dtd-declare-parameter-entity) (define-key dtd-mode-map '[(control c) (control e)] 'dtd-declare-element) (define-key dtd-mode-map '[(control c) (control c)] 'dtd-comment) (define-key dtd-mode-map '[(control c) (control v)] 'sgml-validate) ;;(define-key dtd-mode-map '[(control c) (b)] 'dtd-big-comment) (define-key dtd-mode-map '[(meta g) (meta f)] 'font-lock-fontify-buffer) (define-key dtd-mode-map '[(meta g) (meta control g)] 'font-lock-fontify-buffer) (define-key dtd-mode-map '[(meta control g)] 'font-lock-fontify-buffer) ;; This overrides the sgml-mode mapping and puts it back to its default (define-key dtd-mode-map '[(meta tab)] 'complete-tag)) (defun dtd-electric-mdc () (interactive) (insert ">") (if font-lock-mode (save-excursion (font-lock-fontify-region (dtd-font-lock-region-point-min) (dtd-font-lock-region-point-max))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Syntax table stuff (defvar dtd-mode-syntax-table nil "Syntax table used while in DTD mode.") (if dtd-mode-syntax-table () (setq dtd-mode-syntax-table (make-syntax-table)) ;; set the non-alphanumeric characters in XML names to ;; 'symbol constituent' class (modify-syntax-entry ?: "_" dtd-mode-syntax-table) (modify-syntax-entry ?_ "_" dtd-mode-syntax-table) (modify-syntax-entry ?- "_ 1234" dtd-mode-syntax-table) (modify-syntax-entry ?. "_" dtd-mode-syntax-table) ;; "-" is a special case because it is the first and second characters ;; of the start- and end-comment sequences. (modify-syntax-entry ?- "_ 1234" dtd-mode-syntax-table) ;; "%" does double duty in parameter entity declarations and references. ;; Not necessary to make "%" and ";" act like parentheses since the ;; font lock highlighting tells you when you've put the ";" on the ;; end of a parameter entity reference. (modify-syntax-entry ?% "_" dtd-mode-syntax-table) (modify-syntax-entry ?\; "_" dtd-mode-syntax-table) ;; "/" is just punctuation in DTDs, and really only has a role in ;; Formal Public Identifiers (modify-syntax-entry ?/ "." dtd-mode-syntax-table) ;; Sometimes a string is more than just a string, Dr Freud. ;; Unfortunately, the syntax stuff isn't fussy about matching ;; on paired delimeters, and will happily match a single quote ;; with a double quote, and vice versa. At least the font ;; lock stuff is more fussy and won't change colour if the ;; delimiters aren't paired. (modify-syntax-entry ?\" "$" dtd-mode-syntax-table) (modify-syntax-entry ?\' "$" dtd-mode-syntax-table) ;; The occurrence indicators and connectors are punctuation to us. (modify-syntax-entry ?| "." dtd-mode-syntax-table) (modify-syntax-entry ?, "." dtd-mode-syntax-table) (modify-syntax-entry ?& "." dtd-mode-syntax-table) (modify-syntax-entry ?? "." dtd-mode-syntax-table) (modify-syntax-entry ?+ "." dtd-mode-syntax-table) (modify-syntax-entry ?* "." dtd-mode-syntax-table) ;; `<' and `>' are also punctuation (modify-syntax-entry ?< "." dtd-mode-syntax-table) (modify-syntax-entry ?> "." dtd-mode-syntax-table) ;; "#" is syntax too (modify-syntax-entry ?# "_" dtd-mode-syntax-table)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Font-lock utility functions (defun dtd-font-lock-mark-block-function () "Function to mark the area of text we want to fontify. Used with font-lock-fontify-block. Set font-lock-mark-block-function to this function for this function to take effect. This function marks the area beginning five \"\" at ends of lines after point. The default without a function like this is to fontify 16 lines before and after point, but then the region often starts or ends partway through a comment or declaration, turning that half white because the keywords didn't match, and it just looks so ugly." (let ((current-point (point))) (re-search-forward ">$" (point-max) 'limit 5) (set-mark (point)) (goto-char current-point) (re-search-backward "^$" (point-max) 'limit 5) ;; (insert "!") (point))) ;;;###autoload (defun dtd-mode () "Major mode for SGML and XML DTDs. dtd-mode features include: - dtd-etags function for creating Emacs TAGS files for easy lookup of any element, parameter entity, or notation's definition using Emacs's built-in tag-lookup functions; - Font lock highlighting of declarations so that the important information stands out; - XML-specific behaviour that, at user option, is triggered by automatic detection of the XML Declaration; and - Functions for writing and editing declarations and comments to ease creating and keeping a consistent style. dtd-mode builds on sgml-mode, and the full sgml-mode functions are still available. Use with Lennart Staflin's psgml package is recommended. dtd-mode uses many user-definable variables to control the formatting of declarations, some of which are shown in the following examples: dtd-comment-start-column dtd-dtd-max-column | dtd-comment-max-column | | | | dtd-element-name-column | dtd-element-tag-ommission-column | | dtd-element-content-spec-start-column | | |dtd-element-content-spec-continuation-column | | || dtd-dtd-max-column | | || | \\{dtd-mode-map}" (interactive) (kill-all-local-variables) (use-local-map dtd-mode-map) (setq mode-name "DTD") (setq major-mode 'dtd-mode) (setq local-abbrev-table dtd-mode-abbrev-table) ;; XEmacs users don't all have imenu (if (featurep 'imenu) (progn ;; If you don't have imenu, you'll get a "free variable" ;; warning for imenu-create-index-function when you ;; byte-compile, but not having imenu won't cause problems ;; when you use tdtd (setq imenu-create-index-function 'dtd-imenu-create-index-function) (setq imenu-extract-index-name-function 'dtd-imenu-create-index-function) (imenu-add-to-menubar "Goto"))) (set-syntax-table dtd-mode-syntax-table) ;; XML specific behaviour can be specific to a buffer (make-local-variable 'dtd-xml-flag) ;; Maybe select XML-specific behaviour if we have an XML declaration (if dtd-autodetect-type (dtd-autodetect-type)) ;; dtd font-lock highlighting setup (make-local-variable 'font-lock-defaults) (make-local-variable 'font-lock-mark-block-function) (cond (dtd-xml-flag (setq font-lock-defaults '(dtd-xml-font-lock-keywords t))) (dtd-decl-flag (setq font-lock-defaults '(dtd-decl-font-lock-keywords t))) (dtd-sys-decl-flag (setq font-lock-defaults '(dtd-sys-decl-font-lock-keywords t))) (t (setq font-lock-defaults '(dtd-sgml-font-lock-keywords t)))) (setq font-lock-mark-block-function 'dtd-font-lock-mark-block-function) ;; Enable mode-specific behaviour on finding tags (in the Emacs sense) (make-local-variable 'find-tag-hook) (add-hook 'find-tag-hook 'dtd-find-tag-hook) ;; Maybe insert space characters when user hits "Tab" key (setq indent-tabs-mode dtd-indent-tabs-mode) (run-hooks 'dtd-mode-hooks)) ;;;; Bug reporting (defun dtd-submit-bug-report () "Submit via mail a bug report on TDTD." (interactive) (and (y-or-n-p "Do you really want to submit a report on DTD mode? ") (reporter-submit-bug-report tdtd-maintainer-address (concat "tdtd.el " tdtd-version) (list 'dtd-attribute-default-column 'dtd-attribute-default-history 'dtd-attribute-name-column 'dtd-attribute-tag-history 'dtd-attribute-type-history 'dtd-autodetect-type 'dtd-comment-end 'dtd-comment-max-column 'dtd-comment-start 'dtd-comment-start-column 'dtd-declared-element-type-names 'dtd-declared-parameter-entity-names 'dtd-default-element-type-name 'dtd-default-filespec 'dtd-design-comment-start-column 'dtd-dtd-max-column 'dtd-element-comment-history 'dtd-element-content-spec-continuation-column 'dtd-element-content-spec-history 'dtd-element-content-spec-start-column 'dtd-element-name-column 'dtd-element-tag-omission-column 'dtd-element-type-name-history 'dtd-empty-literal 'dtd-entity-entity-value-start-column 'dtd-etags-output-file 'dtd-etags-program 'dtd-etags-regex-option 'dtd-filespec-history 'dtd-grep-case-sensitive-flag 'dtd-grep-pattern-history 'dtd-indent-tabs-mode 'dtd-init-comment-column 'dtd-init-comment-fill-prefix 'dtd-line-comment 'dtd-mdc-indent-column 'dtd-outdent-attribute-pe 'dtd-parameter-entity-value-history 'dtd-referenced-element-type-names 'dtd-referenced-parameter-entity-names 'dtd-sgml-mdc 'dtd-sgml-mdo 'dtd-upcase-name-comment-flag 'dtd-xml-element-content-spec-continuation-column 'dtd-xml-element-content-spec-start-column 'dtd-xml-flag ) nil nil "Please change the Subject header to a concise bug description.\nRemember to cover the basics, that is, what you expected to\nhappen and what in fact did happen. Please remove these\ninstructions from your message.") (save-excursion (goto-char (point-min)) (mail-position-on-field "Subject") (beginning-of-line) (delete-region (point) (progn (forward-line) (point))) (insert "Subject: tdtd version " tdtd-version " is wonderful but...\n")))) ;;;; Last provisions ;;;(provide 'tdtd) ;;; tdtd.el ends here