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"))