Mercurial > emacs
comparison lisp/textmodes/sgml-mode.el @ 81456:2be88db9aeed
(sgml-xml-guess): Return the result rather than setting sgml-xml-mode.
(sgml-mode, html-mode): Set sgml-xml-mode.
(sgml-skip-tag-backward): Tell if we skipped over matched tags.
(sgml-skip-tag-backward, sgml-electric-tag-pair-overlays): New var.
(sgml-electric-tag-pair-before-change-function)
(sgml-electric-tag-pair-flush-overlays): New functions.
(sgml-electric-tag-pair-mode): New minor mode.
(sgml-font-lock-keywords-2, sgml-get-context, sgml-unclosed-tag-p)
(sgml-calculate-indent): Use assoc-string.
author | Stefan Monnier <monnier@iro.umontreal.ca> |
---|---|
date | Sun, 17 Jun 2007 13:55:17 +0000 |
parents | b4da44959c38 |
children | b98604865ea0 988f1edc9674 |
comparison
equal
deleted
inserted
replaced
81455:c08813c7fc1c | 81456:2be88db9aeed |
---|---|
279 sgml-font-lock-keywords-1 | 279 sgml-font-lock-keywords-1 |
280 '((eval | 280 '((eval |
281 . (cons (concat "<" | 281 . (cons (concat "<" |
282 (regexp-opt (mapcar 'car sgml-tag-face-alist) t) | 282 (regexp-opt (mapcar 'car sgml-tag-face-alist) t) |
283 "\\([ \t][^>]*\\)?>\\([^<]+\\)</\\1>") | 283 "\\([ \t][^>]*\\)?>\\([^<]+\\)</\\1>") |
284 '(3 (cdr (assoc (downcase (match-string 1)) | 284 '(3 (cdr (assoc-string (match-string 1) sgml-tag-face-alist t)) |
285 sgml-tag-face-alist)) prepend)))))) | 285 prepend)))))) |
286 | 286 |
287 ;; for font-lock, but must be defvar'ed after | 287 ;; for font-lock, but must be defvar'ed after |
288 ;; sgml-font-lock-keywords-1 and sgml-font-lock-keywords-2 above | 288 ;; sgml-font-lock-keywords-1 and sgml-font-lock-keywords-2 above |
289 (defvar sgml-font-lock-keywords sgml-font-lock-keywords-1 | 289 (defvar sgml-font-lock-keywords sgml-font-lock-keywords-1 |
290 "*Rules for highlighting SGML code. See also `sgml-tag-face-alist'.") | 290 "*Rules for highlighting SGML code. See also `sgml-tag-face-alist'.") |
366 | 366 |
367 (defvar sgml-unclosed-tags nil | 367 (defvar sgml-unclosed-tags nil |
368 "List of tags whose !ELEMENT definition says the end-tag is optional.") | 368 "List of tags whose !ELEMENT definition says the end-tag is optional.") |
369 | 369 |
370 (defun sgml-xml-guess () | 370 (defun sgml-xml-guess () |
371 "Guess whether the current buffer is XML." | 371 "Guess whether the current buffer is XML. Return non-nil if so." |
372 (save-excursion | 372 (save-excursion |
373 (goto-char (point-min)) | 373 (goto-char (point-min)) |
374 (when (or (string= "xml" (file-name-extension (or buffer-file-name ""))) | 374 (or (string= "xml" (file-name-extension (or buffer-file-name ""))) |
375 (looking-at "\\s-*<\\?xml") | 375 (looking-at "\\s-*<\\?xml") |
376 (when (re-search-forward | 376 (when (re-search-forward |
377 (eval-when-compile | 377 (eval-when-compile |
378 (mapconcat 'identity | 378 (mapconcat 'identity |
379 '("<!DOCTYPE" "\\(\\w+\\)" "\\(\\w+\\)" | 379 '("<!DOCTYPE" "\\(\\w+\\)" "\\(\\w+\\)" |
380 "\"\\([^\"]+\\)\"" "\"\\([^\"]+\\)\"") | 380 "\"\\([^\"]+\\)\"" "\"\\([^\"]+\\)\"") |
381 "\\s-+")) | 381 "\\s-+")) |
382 nil t) | 382 nil t) |
383 (string-match "X\\(HT\\)?ML" (match-string 3)))) | 383 (string-match "X\\(HT\\)?ML" (match-string 3)))))) |
384 (set (make-local-variable 'sgml-xml-mode) t)))) | |
385 | 384 |
386 (defvar v2) ; free for skeleton | 385 (defvar v2) ; free for skeleton |
387 | 386 |
388 (defun sgml-comment-indent-new-line (&optional soft) | 387 (defun sgml-comment-indent-new-line (&optional soft) |
389 (let ((comment-start "-- ") | 388 (let ((comment-start "-- ") |
407 (and (not (zerop (skip-syntax-backward "w_"))) | 406 (and (not (zerop (skip-syntax-backward "w_"))) |
408 (skip-chars-backward "/?!") | 407 (skip-chars-backward "/?!") |
409 (eq (char-before) ?<)))) | 408 (eq (char-before) ?<)))) |
410 | 409 |
411 ;;;###autoload | 410 ;;;###autoload |
412 (define-derived-mode sgml-mode text-mode "SGML" | 411 (define-derived-mode sgml-mode text-mode '(sgml-xml-mode "XML" "SGML") |
413 "Major mode for editing SGML documents. | 412 "Major mode for editing SGML documents. |
414 Makes > match <. | 413 Makes > match <. |
415 Keys <, &, SPC within <>, \", / and ' can be electric depending on | 414 Keys <, &, SPC within <>, \", / and ' can be electric depending on |
416 `sgml-quick-keys'. | 415 `sgml-quick-keys'. |
417 | 416 |
459 nil t nil nil | 458 nil t nil nil |
460 (font-lock-syntactic-keywords | 459 (font-lock-syntactic-keywords |
461 . sgml-font-lock-syntactic-keywords))) | 460 . sgml-font-lock-syntactic-keywords))) |
462 (set (make-local-variable 'facemenu-add-face-function) | 461 (set (make-local-variable 'facemenu-add-face-function) |
463 'sgml-mode-facemenu-add-face-function) | 462 'sgml-mode-facemenu-add-face-function) |
464 (sgml-xml-guess) | 463 (set (make-local-variable 'sgml-xml-mode) (sgml-xml-guess)) |
465 (if sgml-xml-mode | 464 (if sgml-xml-mode |
466 (setq mode-name "XML") | 465 () |
467 (set (make-local-variable 'skeleton-transformation-function) | 466 (set (make-local-variable 'skeleton-transformation-function) |
468 sgml-transformation-function)) | 467 sgml-transformation-function)) |
469 ;; This will allow existing comments within declarations to be | 468 ;; This will allow existing comments within declarations to be |
470 ;; recognized. | 469 ;; recognized. |
471 (set (make-local-variable 'comment-start-skip) "\\(?:<!\\)?--[ \t]*") | 470 (set (make-local-variable 'comment-start-skip) "\\(?:<!\\)?--[ \t]*") |
734 (self-insert-command (prefix-numeric-value arg)) | 733 (self-insert-command (prefix-numeric-value arg)) |
735 (sgml-name-self))) | 734 (sgml-name-self))) |
736 | 735 |
737 (defun sgml-skip-tag-backward (arg) | 736 (defun sgml-skip-tag-backward (arg) |
738 "Skip to beginning of tag or matching opening tag if present. | 737 "Skip to beginning of tag or matching opening tag if present. |
739 With prefix argument ARG, repeat this ARG times." | 738 With prefix argument ARG, repeat this ARG times. |
739 Return non-nil if we skipped over matched tags." | |
740 (interactive "p") | 740 (interactive "p") |
741 ;; FIXME: use sgml-get-context or something similar. | 741 ;; FIXME: use sgml-get-context or something similar. |
742 (while (>= arg 1) | 742 (let ((return t)) |
743 (search-backward "<" nil t) | 743 (while (>= arg 1) |
744 (if (looking-at "</\\([^ \n\t>]+\\)") | 744 (search-backward "<" nil t) |
745 ;; end tag, skip any nested pairs | 745 (if (looking-at "</\\([^ \n\t>]+\\)") |
746 (let ((case-fold-search t) | 746 ;; end tag, skip any nested pairs |
747 (re (concat "</?" (regexp-quote (match-string 1)) | 747 (let ((case-fold-search t) |
748 ;; Ignore empty tags like <foo/>. | 748 (re (concat "</?" (regexp-quote (match-string 1)) |
749 "\\([^>]*[^/>]\\)?>"))) | 749 ;; Ignore empty tags like <foo/>. |
750 (while (and (re-search-backward re nil t) | 750 "\\([^>]*[^/>]\\)?>"))) |
751 (eq (char-after (1+ (point))) ?/)) | 751 (while (and (re-search-backward re nil t) |
752 (forward-char 1) | 752 (eq (char-after (1+ (point))) ?/)) |
753 (sgml-skip-tag-backward 1)))) | 753 (forward-char 1) |
754 (setq arg (1- arg)))) | 754 (sgml-skip-tag-backward 1))) |
755 (setq return nil)) | |
756 (setq arg (1- arg))) | |
757 return)) | |
758 | |
759 (defvar sgml-electric-tag-pair-overlays nil) | |
760 (defvar sgml-electric-tag-pair-timer nil) | |
761 | |
762 (defun sgml-electric-tag-pair-before-change-function (beg end) | |
763 (condition-case err | |
764 (save-excursion | |
765 (goto-char end) | |
766 (skip-chars-backward "[:alnum:]-_.:") | |
767 (if (and ;; (<= (point) beg) ; This poses problems for downcase-word. | |
768 (or (eq (char-before) ?<) | |
769 (and (eq (char-before) ?/) | |
770 (eq (char-before (1- (point))) ?<))) | |
771 (null (get-char-property (point) 'text-clones))) | |
772 (let* ((endp (eq (char-before) ?/)) | |
773 (cl-start (point)) | |
774 (cl-end (progn (skip-chars-forward "[:alnum:]-_.:") (point))) | |
775 (match | |
776 (if endp | |
777 (when (sgml-skip-tag-backward 1) (forward-char 1) t) | |
778 (with-syntax-table sgml-tag-syntax-table | |
779 (up-list -1) | |
780 (when (sgml-skip-tag-forward 1) | |
781 (backward-sexp 1) | |
782 (forward-char 2) | |
783 t)))) | |
784 (clones (get-char-property (point) 'text-clones))) | |
785 (when (and match | |
786 (/= cl-end cl-start) | |
787 (equal (buffer-substring cl-start cl-end) | |
788 (buffer-substring (point) | |
789 (save-excursion | |
790 (skip-chars-forward "[:alnum:]-_.:") | |
791 (point)))) | |
792 (or (not endp) (eq (char-after cl-end) ?>))) | |
793 (when clones | |
794 (message "sgml-electric-tag-pair-before-change-function: deleting old OLs") | |
795 (mapc 'delete-overlay clones)) | |
796 (message "sgml-electric-tag-pair-before-change-function: new clone") | |
797 (text-clone-create cl-start cl-end 'spread "[[:alnum:]-_.:]+") | |
798 (setq sgml-electric-tag-pair-overlays | |
799 (append (get-char-property (point) 'text-clones) | |
800 sgml-electric-tag-pair-overlays)))))) | |
801 (scan-error nil) | |
802 (error (message "Error in sgml-electric-pair-mode: %s" err)))) | |
803 | |
804 (defun sgml-electric-tag-pair-flush-overlays () | |
805 (while sgml-electric-tag-pair-overlays | |
806 (delete-overlay (pop sgml-electric-tag-pair-overlays)))) | |
807 | |
808 (define-minor-mode sgml-electric-tag-pair-mode | |
809 "Automatically update the closing tag when editing the opening one." | |
810 :lighter "/e" | |
811 (if sgml-electric-tag-pair-mode | |
812 (progn | |
813 (add-hook 'before-change-functions | |
814 'sgml-electric-tag-pair-before-change-function | |
815 nil t) | |
816 (unless sgml-electric-tag-pair-timer | |
817 (setq sgml-electric-tag-pair-timer | |
818 (run-with-idle-timer 5 'repeat 'sgml-electric-tag-pair-flush-overlays)))) | |
819 (remove-hook 'before-change-functions | |
820 'sgml-electric-tag-pair-before-change-function | |
821 t) | |
822 ;; We leave the timer running for other buffers. | |
823 )) | |
824 | |
755 | 825 |
756 (defun sgml-skip-tag-forward (arg) | 826 (defun sgml-skip-tag-forward (arg) |
757 "Skip to end of tag or matching closing tag if present. | 827 "Skip to end of tag or matching closing tag if present. |
758 With prefix argument ARG, repeat this ARG times. | 828 With prefix argument ARG, repeat this ARG times. |
759 Return t iff after a closing tag." | 829 Return t iff after a closing tag." |
1218 | 1288 |
1219 ;; start-tag | 1289 ;; start-tag |
1220 ((eq (sgml-tag-type tag-info) 'open) | 1290 ((eq (sgml-tag-type tag-info) 'open) |
1221 (cond | 1291 (cond |
1222 ((null stack) | 1292 ((null stack) |
1223 (if (member-ignore-case (sgml-tag-name tag-info) ignore) | 1293 (if (assoc-string (sgml-tag-name tag-info) ignore t) |
1224 ;; There was an implicit end-tag. | 1294 ;; There was an implicit end-tag. |
1225 nil | 1295 nil |
1226 (push tag-info context) | 1296 (push tag-info context) |
1227 ;; We're changing context so the tags implicitly closed inside | 1297 ;; We're changing context so the tags implicitly closed inside |
1228 ;; the previous context aren't implicitly closed here any more. | 1298 ;; the previous context aren't implicitly closed here any more. |
1303 (error "Nothing to close")))) | 1373 (error "Nothing to close")))) |
1304 | 1374 |
1305 (defun sgml-empty-tag-p (tag-name) | 1375 (defun sgml-empty-tag-p (tag-name) |
1306 "Return non-nil if TAG-NAME is an implicitly empty tag." | 1376 "Return non-nil if TAG-NAME is an implicitly empty tag." |
1307 (and (not sgml-xml-mode) | 1377 (and (not sgml-xml-mode) |
1308 (member-ignore-case tag-name sgml-empty-tags))) | 1378 (assoc-string tag-name sgml-empty-tags 'ignore-case))) |
1309 | 1379 |
1310 (defun sgml-unclosed-tag-p (tag-name) | 1380 (defun sgml-unclosed-tag-p (tag-name) |
1311 "Return non-nil if TAG-NAME is a tag for which an end-tag is optional." | 1381 "Return non-nil if TAG-NAME is a tag for which an end-tag is optional." |
1312 (and (not sgml-xml-mode) | 1382 (and (not sgml-xml-mode) |
1313 (member-ignore-case tag-name sgml-unclosed-tags))) | 1383 (assoc-string tag-name sgml-unclosed-tags 'ignore-case))) |
1384 | |
1314 | 1385 |
1315 (defun sgml-calculate-indent (&optional lcon) | 1386 (defun sgml-calculate-indent (&optional lcon) |
1316 "Calculate the column to which this line should be indented. | 1387 "Calculate the column to which this line should be indented. |
1317 LCON is the lexical context, if any." | 1388 LCON is the lexical context, if any." |
1318 (unless lcon (setq lcon (sgml-lexical-context))) | 1389 (unless lcon (setq lcon (sgml-lexical-context))) |
1374 (forward-sexp 1) | 1445 (forward-sexp 1) |
1375 (skip-chars-forward " \t")) | 1446 (skip-chars-forward " \t")) |
1376 (let* ((here (point)) | 1447 (let* ((here (point)) |
1377 (unclosed (and ;; (not sgml-xml-mode) | 1448 (unclosed (and ;; (not sgml-xml-mode) |
1378 (looking-at sgml-tag-name-re) | 1449 (looking-at sgml-tag-name-re) |
1379 (member-ignore-case (match-string 1) | 1450 (assoc-string (match-string 1) |
1380 sgml-unclosed-tags) | 1451 sgml-unclosed-tags 'ignore-case) |
1381 (match-string 1))) | 1452 (match-string 1))) |
1382 (context | 1453 (context |
1383 ;; If possible, align on the previous non-empty text line. | 1454 ;; If possible, align on the previous non-empty text line. |
1384 ;; Otherwise, do a more serious parsing to find the | 1455 ;; Otherwise, do a more serious parsing to find the |
1385 ;; tag(s) relative to which we should be indenting. | 1456 ;; tag(s) relative to which we should be indenting. |
1813 ("tt" . "Typewriter face") | 1884 ("tt" . "Typewriter face") |
1814 ("u" . "Underlined text") | 1885 ("u" . "Underlined text") |
1815 ("ul" . "Unordered list") | 1886 ("ul" . "Unordered list") |
1816 ("var" . "Math variable face") | 1887 ("var" . "Math variable face") |
1817 ("wbr" . "Enable <br> within <nobr>")) | 1888 ("wbr" . "Enable <br> within <nobr>")) |
1818 "*Value of `sgml-tag-help' for HTML mode.") | 1889 "*Value of `sgml-tag-help' for HTML mode.") |
1819 | 1890 |
1820 | 1891 |
1821 ;;;###autoload | 1892 ;;;###autoload |
1822 (define-derived-mode html-mode sgml-mode "HTML" | 1893 (define-derived-mode html-mode sgml-mode '(sgml-xml-mode "XHTML" "HTML") |
1823 "Major mode based on SGML mode for editing HTML documents. | 1894 "Major mode based on SGML mode for editing HTML documents. |
1824 This allows inserting skeleton constructs used in hypertext documents with | 1895 This allows inserting skeleton constructs used in hypertext documents with |
1825 completion. See below for an introduction to HTML. Use | 1896 completion. See below for an introduction to HTML. Use |
1826 \\[browse-url-of-buffer] to see how this comes out. See also `sgml-mode' on | 1897 \\[browse-url-of-buffer] to see how this comes out. See also `sgml-mode' on |
1827 which this is based. | 1898 which this is based. |
1871 outline-regexp "^.*<[Hh][1-6]\\>" | 1942 outline-regexp "^.*<[Hh][1-6]\\>" |
1872 outline-heading-end-regexp "</[Hh][1-6]>" | 1943 outline-heading-end-regexp "</[Hh][1-6]>" |
1873 outline-level (lambda () | 1944 outline-level (lambda () |
1874 (char-before (match-end 0)))) | 1945 (char-before (match-end 0)))) |
1875 (setq imenu-create-index-function 'html-imenu-index) | 1946 (setq imenu-create-index-function 'html-imenu-index) |
1876 (when sgml-xml-mode (setq mode-name "XHTML")) | |
1877 (set (make-local-variable 'sgml-empty-tags) | 1947 (set (make-local-variable 'sgml-empty-tags) |
1878 ;; From HTML-4.01's loose.dtd, parsed with `sgml-parse-dtd', | 1948 ;; From HTML-4.01's loose.dtd, parsed with `sgml-parse-dtd', |
1879 ;; plus manual addition of "wbr". | 1949 ;; plus manual addition of "wbr". |
1880 '("area" "base" "basefont" "br" "col" "frame" "hr" "img" "input" | 1950 '("area" "base" "basefont" "br" "col" "frame" "hr" "img" "input" |
1881 "isindex" "link" "meta" "param" "wbr")) | 1951 "isindex" "link" "meta" "param" "wbr")) |