diff options
| author | Stefan Monnier | 2002-03-29 19:38:22 +0000 |
|---|---|---|
| committer | Stefan Monnier | 2002-03-29 19:38:22 +0000 |
| commit | 5f3d924defda210f55bde63c5d5f67f55e633dee (patch) | |
| tree | ef3232650a14815c2abaf72a847920c371040a43 | |
| parent | 544bf8ad9dbcb3269952e930bc5acf4e929b9ffd (diff) | |
| download | emacs-5f3d924defda210f55bde63c5d5f67f55e633dee.tar.gz emacs-5f3d924defda210f55bde63c5d5f67f55e633dee.zip | |
(sgml-basic-offset): New var.
(sgml-name-re, sgml-attrs-re): New consts.
(sgml-tag-name-re, sgml-start-tag-regex, sgml-font-lock-keywords-1)
(sgml-mode): Use them.
(sgml-lexical-context): Default to (point-min) if nothing else works.
(sgml-calculate-indent): Indent slightly differently.
(sgml-indent-line): Use back-to-indentation.
(sgml-parse-dtd): New function.
(sgml-unclosed-tags): New var.
(html-mode): Set it.
| -rw-r--r-- | lisp/textmodes/sgml-mode.el | 105 |
1 files changed, 66 insertions, 39 deletions
diff --git a/lisp/textmodes/sgml-mode.el b/lisp/textmodes/sgml-mode.el index 415d69eb800..9ebf2d3be78 100644 --- a/lisp/textmodes/sgml-mode.el +++ b/lisp/textmodes/sgml-mode.el | |||
| @@ -41,6 +41,11 @@ | |||
| 41 | "SGML editing mode" | 41 | "SGML editing mode" |
| 42 | :group 'languages) | 42 | :group 'languages) |
| 43 | 43 | ||
| 44 | (defcustom sgml-basic-offset 2 | ||
| 45 | "*Specifies the basic indentation level for `sgml-indent-line'." | ||
| 46 | :type 'integer | ||
| 47 | :group 'sgml) | ||
| 48 | |||
| 44 | (defcustom sgml-transformation 'identity | 49 | (defcustom sgml-transformation 'identity |
| 45 | "*Default value for `skeleton-transformation' (which see) in SGML mode." | 50 | "*Default value for `skeleton-transformation' (which see) in SGML mode." |
| 46 | :type 'function | 51 | :type 'function |
| @@ -237,20 +242,21 @@ separated by a space." | |||
| 237 | :type '(choice (const nil) integer) | 242 | :type '(choice (const nil) integer) |
| 238 | :group 'sgml) | 243 | :group 'sgml) |
| 239 | 244 | ||
| 240 | (defconst sgml-tag-name-re "<\\([!/?]?[[:alpha:]][-_.:[:alnum:]]*\\)") | 245 | (defconst sgml-name-re "[_:[:alpha:]][-_.:[:alnum:]]*") |
| 241 | (defconst sgml-start-tag-regex | 246 | (defconst sgml-tag-name-re (concat "<\\([!/?]?" sgml-name-re "\\)")) |
| 242 | "<[[:alpha:]]\\([-_.:[:alnum:]= \n\t]\\|\"[^\"]*\"\\|'[^']*'\\)*" | 247 | (defconst sgml-attrs-re "\\(?:[^\"'/><]\\|\"[^\"]*\"\\|'[^']*'\\)*") |
| 248 | (defconst sgml-start-tag-regex (concat "<" sgml-name-re sgml-attrs-re) | ||
| 243 | "Regular expression that matches a non-empty start tag. | 249 | "Regular expression that matches a non-empty start tag. |
| 244 | Any terminating `>' or `/' is not matched.") | 250 | Any terminating `>' or `/' is not matched.") |
| 245 | 251 | ||
| 246 | 252 | ||
| 247 | ;; internal | 253 | ;; internal |
| 248 | (defconst sgml-font-lock-keywords-1 | 254 | (defconst sgml-font-lock-keywords-1 |
| 249 | '(("<\\([!?][[:alpha:]][-_.:[:alnum:]]*\\)" 1 font-lock-keyword-face) | 255 | `((,(concat "<\\([!?]" sgml-name-re "\\)") 1 font-lock-keyword-face) |
| 250 | ("<\\(/?[[:alpha:]][-_.:[:alnum:]]*\\)" 1 font-lock-function-name-face) | 256 | (,(concat "<\\(/?" sgml-name-re"\\)") 1 font-lock-function-name-face) |
| 251 | ;; FIXME: this doesn't cover the variables using a default value. | 257 | ;; FIXME: this doesn't cover the variables using a default value. |
| 252 | ("\\([[:alpha:]][-_.:[:alnum:]]*\\)=[\"']" 1 font-lock-variable-name-face) | 258 | (,(concat "\\(" sgml-name-re "\\)=[\"']") 1 font-lock-variable-name-face) |
| 253 | ("[&%][[:alpha:]][-_.:[:alnum:]]*;?" . font-lock-variable-name-face))) | 259 | (,(concat "[&%]" sgml-name-re ";?") . font-lock-variable-name-face))) |
| 254 | 260 | ||
| 255 | (defconst sgml-font-lock-keywords-2 | 261 | (defconst sgml-font-lock-keywords-2 |
| 256 | (append | 262 | (append |
| @@ -344,6 +350,9 @@ Otherwise, it is set to be buffer-local when the file has | |||
| 344 | (defvar sgml-empty-tags nil | 350 | (defvar sgml-empty-tags nil |
| 345 | "List of tags whose !ELEMENT definition says EMPTY.") | 351 | "List of tags whose !ELEMENT definition says EMPTY.") |
| 346 | 352 | ||
| 353 | (defvar sgml-unclosed-tags nil | ||
| 354 | "List of tags whose !ELEMENT definition says the end-tag is optional.") | ||
| 355 | |||
| 347 | (defun sgml-xml-guess () | 356 | (defun sgml-xml-guess () |
| 348 | "Guess whether the current buffer is XML." | 357 | "Guess whether the current buffer is XML." |
| 349 | (save-excursion | 358 | (save-excursion |
| @@ -396,10 +405,10 @@ Do \\[describe-key] on the following bindings to discover what they do. | |||
| 396 | ;; A start or end tag by itself on a line separates a paragraph. | 405 | ;; A start or end tag by itself on a line separates a paragraph. |
| 397 | ;; This is desirable because SGML discards a newline that appears | 406 | ;; This is desirable because SGML discards a newline that appears |
| 398 | ;; immediately after a start tag or immediately before an end tag. | 407 | ;; immediately after a start tag or immediately before an end tag. |
| 399 | (set (make-local-variable 'paragraph-separate) "[ \t]*$\\|\ | 408 | (set (make-local-variable 'paragraph-start) (concat "[ \t]*$\\|\ |
| 400 | \[ \t]*</?\\([[:alpha:]]\\([-_.:[:alnum:]= \t\n]\\|\"[^\"]*\"\\|'[^']*'\\)*\\)?>$") | 409 | \[ \t]*</?\\(" sgml-name-re sgml-attrs-re "\\)?>")) |
| 401 | (set (make-local-variable 'paragraph-start) "[ \t]*$\\|\ | 410 | (set (make-local-variable 'paragraph-separate) |
| 402 | \[ \t]*</?\\([[:alpha:]]\\([-_.:[:alnum:]= \t\n]\\|\"[^\"]*\"\\|'[^']*'\\)*\\)?>") | 411 | (concat paragraph-start "$")) |
| 403 | (set (make-local-variable 'adaptive-fill-regexp) "[ \t]*") | 412 | (set (make-local-variable 'adaptive-fill-regexp) "[ \t]*") |
| 404 | (set (make-local-variable 'comment-start) "<!-- ") | 413 | (set (make-local-variable 'comment-start) "<!-- ") |
| 405 | (set (make-local-variable 'comment-end) " -->") | 414 | (set (make-local-variable 'comment-end) " -->") |
| @@ -430,7 +439,8 @@ Do \\[describe-key] on the following bindings to discover what they do. | |||
| 430 | (set (make-local-variable 'comment-end-skip) "[ \t]*--\\([ \t\n]*>\\)?") | 439 | (set (make-local-variable 'comment-end-skip) "[ \t]*--\\([ \t\n]*>\\)?") |
| 431 | ;; This definition probably is not useful in derived modes. | 440 | ;; This definition probably is not useful in derived modes. |
| 432 | (set (make-local-variable 'imenu-generic-expression) | 441 | (set (make-local-variable 'imenu-generic-expression) |
| 433 | "<!\\(element\\|entity\\)[ \t\n]+%?[ \t\n]*\\([[:alpha:]][-_.:[:alnum:]]*\\)")) | 442 | (concat "<!\\(element\\|entity\\)[ \t\n]+%?[ \t\n]*\\(" |
| 443 | sgml-name-re "\\)"))) | ||
| 434 | 444 | ||
| 435 | 445 | ||
| 436 | (defun sgml-comment-indent () | 446 | (defun sgml-comment-indent () |
| @@ -846,22 +856,22 @@ If non-nil LIMIT is a nearby position before point outside of any tag." | |||
| 846 | (save-excursion | 856 | (save-excursion |
| 847 | (let ((pos (point)) | 857 | (let ((pos (point)) |
| 848 | (state nil)) | 858 | (state nil)) |
| 849 | ;; Hopefully this regexp will match something that's not inside | 859 | (if limit (goto-char limit) |
| 850 | ;; a tag and also hopefully the match is nearby. | 860 | ;; Hopefully this regexp will match something that's not inside |
| 851 | (when (or (and limit (goto-char limit)) | 861 | ;; a tag and also hopefully the match is nearby. |
| 852 | (re-search-backward "^[ \t]*<" nil t)) | 862 | (re-search-backward "^[ \t]*<[_:[:alpha:]/%!?#]" nil 'move)) |
| 853 | (with-syntax-table sgml-tag-syntax-table | 863 | (with-syntax-table sgml-tag-syntax-table |
| 854 | (while (< (point) pos) | 864 | (while (< (point) pos) |
| 855 | ;; When entering this loop we're inside text. | 865 | ;; When entering this loop we're inside text. |
| 856 | (skip-chars-forward "^<" pos) | 866 | (skip-chars-forward "^<" pos) |
| 857 | ;; We skipped text and reached a tag. Parse it. | 867 | ;; We skipped text and reached a tag. Parse it. |
| 858 | ;; FIXME: this does not handle CDATA and funny stuff yet. | 868 | ;; FIXME: this does not handle CDATA and funny stuff yet. |
| 859 | (setq state (parse-partial-sexp (point) pos 0))) | 869 | (setq state (parse-partial-sexp (point) pos 0))) |
| 860 | (cond | 870 | (cond |
| 861 | ((nth 3 state) (cons 'string (nth 8 state))) | 871 | ((nth 3 state) (cons 'string (nth 8 state))) |
| 862 | ((nth 4 state) (cons 'comment (nth 8 state))) | 872 | ((nth 4 state) (cons 'comment (nth 8 state))) |
| 863 | ((and state (> (nth 0 state) 0)) (cons 'tag (nth 1 state))) | 873 | ((and state (> (nth 0 state) 0)) (cons 'tag (nth 1 state))) |
| 864 | (t nil))))))) | 874 | (t nil)))))) |
| 865 | 875 | ||
| 866 | (defun sgml-beginning-of-tag (&optional top-level) | 876 | (defun sgml-beginning-of-tag (&optional top-level) |
| 867 | "Skip to beginning of tag and return its name. | 877 | "Skip to beginning of tag and return its name. |
| @@ -965,18 +975,15 @@ With prefix argument, unquote the region." | |||
| 965 | (let ((context (xml-lite-get-context))) | 975 | (let ((context (xml-lite-get-context))) |
| 966 | (cond | 976 | (cond |
| 967 | ((null context) 0) ; no context | 977 | ((null context) 0) ; no context |
| 968 | ;; Align closing tag with the opening one. | ||
| 969 | ;; ((and (eq (length context) 1) (looking-at "</")) | ||
| 970 | ;; (goto-char (xml-lite-tag-start (car context))) | ||
| 971 | ;; (current-column)) | ||
| 972 | (t | 978 | (t |
| 973 | (let ((here (point))) | 979 | (let ((here (point))) |
| 974 | (goto-char (xml-lite-tag-end (car context))) | 980 | (goto-char (xml-lite-tag-end (car context))) |
| 975 | (skip-chars-forward " \t\n") | 981 | (skip-chars-forward " \t\n") |
| 976 | (if (< (point) here) | 982 | (if (and (< (point) here) (xml-lite-at-indentation-p)) |
| 977 | (current-column) | 983 | (current-column) |
| 978 | (goto-char (xml-lite-tag-start (car context))) | 984 | (goto-char (xml-lite-tag-start (car context))) |
| 979 | (+ (current-column) sgml-basic-offset)))))))))) | 985 | (+ (current-column) |
| 986 | (* sgml-basic-offset (length context)))))))))))) | ||
| 980 | 987 | ||
| 981 | (defun sgml-indent-line () | 988 | (defun sgml-indent-line () |
| 982 | "Indent the current line as SGML." | 989 | "Indent the current line as SGML." |
| @@ -984,15 +991,29 @@ With prefix argument, unquote the region." | |||
| 984 | (let* ((savep (point)) | 991 | (let* ((savep (point)) |
| 985 | (indent-col | 992 | (indent-col |
| 986 | (save-excursion | 993 | (save-excursion |
| 987 | (beginning-of-line) | 994 | (back-to-indentation) |
| 988 | (skip-chars-forward " \t") | ||
| 989 | (if (>= (point) savep) (setq savep nil)) | 995 | (if (>= (point) savep) (setq savep nil)) |
| 990 | ;; calculate basic indent | ||
| 991 | (sgml-calculate-indent)))) | 996 | (sgml-calculate-indent)))) |
| 992 | (if savep | 997 | (if savep |
| 993 | (save-excursion (indent-line-to indent-col)) | 998 | (save-excursion (indent-line-to indent-col)) |
| 994 | (indent-line-to indent-col)))) | 999 | (indent-line-to indent-col)))) |
| 995 | 1000 | ||
| 1001 | (defun sgml-parse-dtd () | ||
| 1002 | "Simplistic parse of the current buffer as a DTD. | ||
| 1003 | Currently just returns (EMPTY-TAGS UNCLOSED-TAGS)." | ||
| 1004 | (goto-char (point-min)) | ||
| 1005 | (let ((empty nil) | ||
| 1006 | (unclosed nil)) | ||
| 1007 | (while (re-search-forward "<!ELEMENT[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+[-O][ \t\n]+\\([-O]\\)[ \t\n]+\\([^ \t\n]+\\)" nil t) | ||
| 1008 | (cond | ||
| 1009 | ((string= (match-string 3) "EMPTY") | ||
| 1010 | (push (match-string-no-properties 1) empty)) | ||
| 1011 | ((string= (match-string 2) "O") | ||
| 1012 | (push (match-string-no-properties 1) unclosed)))) | ||
| 1013 | (setq empty (sort (mapcar 'downcase empty) 'string<)) | ||
| 1014 | (setq unclosed (sort (mapcar 'downcase unclosed) 'string<)) | ||
| 1015 | (list empty unclosed))) | ||
| 1016 | |||
| 996 | ;;; HTML mode | 1017 | ;;; HTML mode |
| 997 | 1018 | ||
| 998 | (defcustom html-mode-hook nil | 1019 | (defcustom html-mode-hook nil |
| @@ -1404,8 +1425,14 @@ To work around that, do: | |||
| 1404 | (setq imenu-create-index-function 'html-imenu-index) | 1425 | (setq imenu-create-index-function 'html-imenu-index) |
| 1405 | (when sgml-xml-mode (setq mode-name "XHTML")) | 1426 | (when sgml-xml-mode (setq mode-name "XHTML")) |
| 1406 | (set (make-local-variable 'sgml-empty-tags) | 1427 | (set (make-local-variable 'sgml-empty-tags) |
| 1407 | '("br" "hr" "img" "input" "area" "link" "param" "col" | 1428 | ;; From HTML-4.01's loose.dtd, parsed with `sgml-parse-dtd', |
| 1408 | "base" "meta" "basefont" "frame" "isindex" "wbr")) | 1429 | ;; plus manual addition of "wbr". |
| 1430 | '("area" "base" "basefont" "br" "col" "frame" "hr" "img" "input" | ||
| 1431 | "isindex" "link" "meta" "param" "wbr")) | ||
| 1432 | (set (make-local-variable 'sgml-unclosed-tags) | ||
| 1433 | ;; From HTML-4.01's loose.dtd, parsed with `sgml-parse-dtd'. | ||
| 1434 | '("body" "colgroup" "dd" "dt" "head" "html" "li" "option" | ||
| 1435 | "p" "tbody" "td" "tfoot" "th" "thead" "tr")) | ||
| 1409 | ;; It's for the user to decide if it defeats it or not -stef | 1436 | ;; It's for the user to decide if it defeats it or not -stef |
| 1410 | ;; (make-local-variable 'imenu-sort-function) | 1437 | ;; (make-local-variable 'imenu-sort-function) |
| 1411 | ;; (setq imenu-sort-function nil) ; sorting the menu defeats the purpose | 1438 | ;; (setq imenu-sort-function nil) ; sorting the menu defeats the purpose |