comparison lisp/nxml/nxml-mode.el @ 95598:8c4c0ca00399

nXML: Use font lock
author Michael Olson <mwolson@gnu.org>
date Fri, 06 Jun 2008 16:14:49 +0000
parents d495d4d5452f
children 8b2ac128dd5b
comparison
equal deleted inserted replaced
95597:d89ef0f12bd4 95598:8c4c0ca00399
21 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. 21 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
22 22
23 ;;; Commentary: 23 ;;; Commentary:
24 24
25 ;; See nxml-rap.el for description of parsing strategy. 25 ;; See nxml-rap.el for description of parsing strategy.
26
27 ;; The font locking here is independent of font-lock.el. We want to
28 ;; do more sophisticated handling of changes and we want to use the
29 ;; same xmltok rather than regexps for parsing so that we parse
30 ;; consistently and correctly.
31 26
32 ;;; Code: 27 ;;; Code:
33 28
34 (when (featurep 'mucs) 29 (when (featurep 'mucs)
35 (error "nxml-mode is not compatible with Mule-UCS")) 30 (error "nxml-mode is not compatible with Mule-UCS"))
53 48
54 (defgroup nxml-faces nil 49 (defgroup nxml-faces nil
55 "Faces for XML syntax highlighting." 50 "Faces for XML syntax highlighting."
56 :group 'nxml 51 :group 'nxml
57 :group 'font-lock-faces) 52 :group 'font-lock-faces)
58
59 (defcustom nxml-syntax-highlight-flag t
60 "*Non-nil means nxml-mode should perform syntax highlighting."
61 :group 'nxml
62 :type 'boolean)
63 53
64 (defcustom nxml-char-ref-display-glyph-flag t 54 (defcustom nxml-char-ref-display-glyph-flag t
65 "*Non-nil means display glyph following character reference. 55 "*Non-nil means display glyph following character reference.
66 The glyph is displayed in face `nxml-glyph'. The hook 56 The glyph is displayed in face `nxml-glyph'. The hook
67 `nxml-glyph-set-hook' can be used to customize for which characters 57 `nxml-glyph-set-hook' can be used to customize for which characters
97 This only applies when the first attribute of a tag starts a line. In other 87 This only applies when the first attribute of a tag starts a line. In other
98 cases, the first attribute on one line is indented the same as the first 88 cases, the first attribute on one line is indented the same as the first
99 attribute on the previous line." 89 attribute on the previous line."
100 :group 'nxml 90 :group 'nxml
101 :type 'integer) 91 :type 'integer)
102
103 (defvar nxml-fontify-chunk-size 500)
104 92
105 (defcustom nxml-bind-meta-tab-to-complete-flag (not window-system) 93 (defcustom nxml-bind-meta-tab-to-complete-flag (not window-system)
106 "*Non-nil means bind M-TAB in `nxml-mode-map' to `nxml-complete'. 94 "*Non-nil means bind M-TAB in `nxml-mode-map' to `nxml-complete'.
107 C-return will be bound to `nxml-complete' in any case. 95 C-return will be bound to `nxml-complete' in any case.
108 M-TAB gets swallowed by many window systems/managers, and 96 M-TAB gets swallowed by many window systems/managers, and
430 (when nxml-bind-meta-tab-to-complete-flag 418 (when nxml-bind-meta-tab-to-complete-flag
431 (define-key map "\M-\t" 'nxml-complete)) 419 (define-key map "\M-\t" 'nxml-complete))
432 map) 420 map)
433 "Keymap for nxml-mode.") 421 "Keymap for nxml-mode.")
434 422
423 (defvar nxml-font-lock-keywords
424 '(nxml-fontify-matcher)
425 "Default font lock keywords for nxml-mode.")
426
435 (defsubst nxml-set-face (start end face) 427 (defsubst nxml-set-face (start end face)
436 (when (and face (< start end)) 428 (when (and face (< start end))
437 (put-text-property start end 'face face))) 429 (font-lock-append-text-property start end 'face face)))
438
439 (defun nxml-clear-face (start end)
440 (remove-text-properties start end '(face nil))
441 (nxml-clear-char-ref-extra-display start end))
442
443 (defsubst nxml-set-fontified (start end)
444 (put-text-property start end 'fontified t))
445
446 (defsubst nxml-clear-fontified (start end)
447 (remove-text-properties start end '(fontified nil)))
448 430
449 ;;;###autoload 431 ;;;###autoload
450 (defun nxml-mode () 432 (defun nxml-mode ()
451 ;; We use C-c C-i instead of \\[nxml-balanced-close-start-tag-inline] 433 ;; We use C-c C-i instead of \\[nxml-balanced-close-start-tag-inline]
452 ;; because Emacs turns C-c C-i into C-c TAB which is hard to type and 434 ;; because Emacs turns C-c C-i into C-c TAB which is hard to type and
453 ;; not mnemonic. 435 ;; not mnemonic.
454 "Major mode for editing XML. 436 "Major mode for editing XML.
455
456 Syntax highlighting is performed unless the variable
457 `nxml-syntax-highlight-flag' is nil.
458 437
459 \\[nxml-finish-element] finishes the current element by inserting an end-tag. 438 \\[nxml-finish-element] finishes the current element by inserting an end-tag.
460 C-c C-i closes a start-tag with `>' and then inserts a balancing end-tag 439 C-c C-i closes a start-tag with `>' and then inserts a balancing end-tag
461 leaving point between the start-tag and end-tag. 440 leaving point between the start-tag and end-tag.
462 \\[nxml-balanced-close-start-tag-block] is similar but for block rather than inline elements: 441 \\[nxml-balanced-close-start-tag-block] is similar but for block rather than inline elements:
538 (save-restriction 517 (save-restriction
539 (widen) 518 (widen)
540 (nxml-clear-dependent-regions (point-min) (point-max)) 519 (nxml-clear-dependent-regions (point-min) (point-max))
541 (setq nxml-scan-end (copy-marker (point-min) nil)) 520 (setq nxml-scan-end (copy-marker (point-min) nil))
542 (nxml-with-unmodifying-text-property-changes 521 (nxml-with-unmodifying-text-property-changes
543 (when nxml-syntax-highlight-flag 522 (nxml-clear-inside (point-min) (point-max))
544 (nxml-clear-fontified (point-min) (point-max)))
545 (nxml-clear-inside (point-min) (point-max))
546 (nxml-with-invisible-motion 523 (nxml-with-invisible-motion
547 (nxml-scan-prolog))))) 524 (nxml-scan-prolog)))))
548 (when nxml-syntax-highlight-flag
549 (add-hook 'fontification-functions 'nxml-fontify nil t))
550 (add-hook 'after-change-functions 'nxml-after-change nil t) 525 (add-hook 'after-change-functions 'nxml-after-change nil t)
551 (add-hook 'change-major-mode-hook 'nxml-cleanup nil t) 526 (add-hook 'change-major-mode-hook 'nxml-cleanup nil t)
552 527
553 ;; Emacs 23 handles the encoding attribute on the xml declaration 528 ;; Emacs 23 handles the encoding attribute on the xml declaration
554 ;; transparently to nxml-mode, so there is no longer a need for the below 529 ;; transparently to nxml-mode, so there is no longer a need for the below
559 (when (and nxml-default-buffer-file-coding-system 534 (when (and nxml-default-buffer-file-coding-system
560 (not (local-variable-p 'buffer-file-coding-system))) 535 (not (local-variable-p 'buffer-file-coding-system)))
561 (setq buffer-file-coding-system nxml-default-buffer-file-coding-system)) 536 (setq buffer-file-coding-system nxml-default-buffer-file-coding-system))
562 (when nxml-auto-insert-xml-declaration-flag 537 (when nxml-auto-insert-xml-declaration-flag
563 (nxml-insert-xml-declaration))) 538 (nxml-insert-xml-declaration)))
539
540 (setq font-lock-defaults
541 '(nxml-font-lock-keywords
542 t ; keywords-only; we highlight comments and strings here
543 nil ; font-lock-keywords-case-fold-search. XML is case sensitive
544 nil ; no special syntax table
545 nil ; no automatic syntactic fontification
546 (font-lock-extend-after-change-region-function
547 . nxml-extend-after-change-region)
548 (font-lock-extend-region-functions . (nxml-extend-region))
549 (jit-lock-contextually . t)
550 (font-lock-unfontify-region-function . nxml-unfontify-region)))
551
564 (rng-nxml-mode-init) 552 (rng-nxml-mode-init)
565 (nxml-enable-unicode-char-name-sets) 553 (nxml-enable-unicode-char-name-sets)
566 (run-hooks 'nxml-mode-hook)) 554 (run-hooks 'nxml-mode-hook))
567 555
568 (defun nxml-cleanup () 556 (defun nxml-cleanup ()
589 (setq nxml-prolog-end 1) 577 (setq nxml-prolog-end 1)
590 (save-excursion 578 (save-excursion
591 (save-restriction 579 (save-restriction
592 (widen) 580 (widen)
593 (nxml-with-unmodifying-text-property-changes 581 (nxml-with-unmodifying-text-property-changes
594 (nxml-clear-face (point-min) (point-max))
595 (nxml-set-fontified (point-min) (point-max))
596 (nxml-clear-inside (point-min) (point-max)))))) 582 (nxml-clear-inside (point-min) (point-max))))))
597 583
598 ;;; Change management 584 ;;; Change management
599 585
586 (defun nxml-debug-region (start end)
587 (interactive "r")
588 (let ((font-lock-beg start)
589 (font-lock-end end))
590 (nxml-extend-region)
591 (goto-char font-lock-beg)
592 (set-mark font-lock-end)))
593
600 (defun nxml-after-change (start end pre-change-length) 594 (defun nxml-after-change (start end pre-change-length)
601 ;; Work around bug in insert-file-contents. 595 ; In font-lock mode, nxml-after-change1 is called via
602 (when (> end (1+ (buffer-size))) 596 ; nxml-extend-after-change-region instead so that the updated
603 (setq start 1) 597 ; book-keeping information is available for fontification.
604 (setq end (1+ (buffer-size)))) 598 (unless (or font-lock-mode nxml-degraded)
605 (unless nxml-degraded 599 (nxml-with-degradation-on-error 'nxml-after-change
606 (condition-case err 600 (save-excursion
607 (save-excursion 601 (save-restriction
608 (save-restriction 602 (widen)
609 (widen) 603 (save-match-data
610 (save-match-data 604 (nxml-with-invisible-motion
611 (nxml-with-invisible-motion 605 (nxml-with-unmodifying-text-property-changes
612 (nxml-with-unmodifying-text-property-changes 606 (nxml-after-change1
613 (nxml-after-change1 start end pre-change-length)))))) 607 start end pre-change-length)))))))))
614 (error
615 (nxml-degrade 'nxml-after-change err)))))
616 608
617 (defun nxml-after-change1 (start end pre-change-length) 609 (defun nxml-after-change1 (start end pre-change-length)
618 (setq nxml-last-fontify-end nil) 610 "After-change bookkeeping. Returns a cons cell containing a
611 possibly-enlarged change region. You must call
612 nxml-extend-region on this expanded region to obtain the full
613 extent of the area needing refontification.
614
615 For bookkeeping, call this function even when fontification is
616 disabled."
619 (let ((pre-change-end (+ start pre-change-length))) 617 (let ((pre-change-end (+ start pre-change-length)))
620 (setq start 618 (setq start
621 (nxml-adjust-start-for-dependent-regions start 619 (nxml-adjust-start-for-dependent-regions start
622 end 620 end
623 pre-change-length)) 621 pre-change-length))
622 ;; If the prolog might have changed, rescan the prolog
624 (when (<= start 623 (when (<= start
625 ;; Add 2 so as to include the < and following char 624 ;; Add 2 so as to include the < and following char that
626 ;; that start the instance, since changing these 625 ;; start the instance (document element), since changing
627 ;; can change where the prolog ends. 626 ;; these can change where the prolog ends.
628 (+ nxml-prolog-end 2)) 627 (+ nxml-prolog-end 2))
629 ;; end must be extended to at least the end of the old prolog 628 ;; end must be extended to at least the end of the old prolog in
629 ;; case the new prolog is shorter
630 (when (< pre-change-end nxml-prolog-end) 630 (when (< pre-change-end nxml-prolog-end)
631 (setq end 631 (setq end
632 ;; don't let end get out of range even if pre-change-length 632 ;; don't let end get out of range even if pre-change-length
633 ;; is bogus 633 ;; is bogus
634 (min (point-max) 634 (min (point-max)
635 (+ end (- nxml-prolog-end pre-change-end))))) 635 (+ end (- nxml-prolog-end pre-change-end)))))
636 (nxml-scan-prolog))) 636 (nxml-scan-prolog)
637 (cond ((<= end nxml-prolog-end) 637 (setq start (point-min))))
638 (setq end nxml-prolog-end) 638
639 (goto-char start) 639 (when (> end nxml-prolog-end)
640 ;; This is so that Emacs redisplay works 640 (goto-char start)
641 (setq start (line-beginning-position))) 641 (nxml-move-tag-backwards (point-min))
642 ((and (<= start nxml-scan-end) 642 (setq start (point))
643 (> start (point-min)) 643 (setq end (max (nxml-scan-after-change start end)
644 (nxml-get-inside (1- start))) 644 end)))
645 ;; The closing delimiter might have been removed. 645
646 ;; So we may need to redisplay from the beginning 646 (nxml-debug-change "nxml-after-change1" start end)
647 ;; of the token. 647 (cons start end))
648 (goto-char (1- start)) 648
649 (nxml-move-outside-backwards)
650 ;; This is so that Emacs redisplay works
651 (setq start (line-beginning-position))
652 (setq end (max (nxml-scan-after-change (point) end)
653 end)))
654 (t
655 (goto-char start)
656 ;; This is both for redisplay and to move back
657 ;; past any incomplete opening delimiters
658 (setq start (line-beginning-position))
659 (setq end (max (nxml-scan-after-change start end)
660 end))))
661 (when nxml-syntax-highlight-flag
662 (when (>= start end)
663 ;; Must clear at least one char so as to trigger redisplay.
664 (cond ((< start (point-max))
665 (setq end (1+ start)))
666 (t
667 (setq end (point-max))
668 (goto-char end)
669 (setq start (line-beginning-position)))))
670 (nxml-clear-fontified start end)))
671
672 ;;; Encodings 649 ;;; Encodings
673 650
674 (defun nxml-insert-xml-declaration () 651 (defun nxml-insert-xml-declaration ()
675 "Insert an XML declaration at the beginning of buffer. 652 "Insert an XML declaration at the beginning of buffer.
676 The XML declaration will declare an encoding depending on the buffer's 653 The XML declaration will declare an encoding depending on the buffer's
852 (setq coding-systems (cdr coding-systems)))) 829 (setq coding-systems (cdr coding-systems))))
853 ret)) 830 ret))
854 831
855 ;;; Fontification 832 ;;; Fontification
856 833
857 (defun nxml-fontify (start) 834 (defun nxml-unfontify-region (start end)
858 (condition-case err 835 (font-lock-default-unfontify-region start end)
859 (save-excursion 836 (nxml-clear-char-ref-extra-display start end))
860 (save-restriction 837
861 (widen) 838 (defvar font-lock-beg) (defvar font-lock-end)
862 (save-match-data 839 (defun nxml-extend-region ()
863 (nxml-with-invisible-motion 840 "Extend the region to hold the minimum area we can fontify with nXML.
864 (nxml-with-unmodifying-text-property-changes 841 Called with font-lock-beg and font-lock-end dynamically bound."
865 (if (or nxml-degraded 842 (let ((start font-lock-beg)
866 ;; just in case we get called in the wrong buffer 843 (end font-lock-end))
867 (not nxml-prolog-end)) 844
868 (nxml-set-fontified start (point-max)) 845 (nxml-debug-change "nxml-extend-region(input)" start end)
869 (nxml-fontify1 start))))))) 846
870 (error 847 (when (< start nxml-prolog-end)
871 (nxml-degrade 'nxml-fontify err)))) 848 (setq start (point-min)))
872 849
873 (defun nxml-fontify1 (start) 850 (cond ((<= end nxml-prolog-end)
874 (cond ((< start nxml-prolog-end) 851 (setq end nxml-prolog-end))
875 (nxml-fontify-prolog) 852
876 (nxml-set-fontified (point-min) 853 (t
877 nxml-prolog-end)) 854 (goto-char start)
878 (t 855 ;; some font-lock backends (like Emacs 22 jit-lock) snap
879 (goto-char start) 856 ;; the region to the beginning of the line no matter what
880 (when (not (eq nxml-last-fontify-end start)) 857 ;; we say here. To mitigate the resulting excess
881 (when (not (equal (char-after) ?\<)) 858 ;; fontification, ignore leading whitespace.
882 (search-backward "<" nxml-prolog-end t)) 859 (skip-syntax-forward " ")
883 (nxml-ensure-scan-up-to-date) 860
884 (nxml-move-outside-backwards)) 861 ;; find the beginning of the previous tag
885 (let ((start (point))) 862 (when (not (equal (char-after) ?\<))
886 (nxml-do-fontify (min (point-max) 863 (search-backward "<" nxml-prolog-end t))
887 (+ start nxml-fontify-chunk-size))) 864 (nxml-ensure-scan-up-to-date)
888 (setq nxml-last-fontify-end (point)) 865 (nxml-move-outside-backwards)
889 (nxml-set-fontified start nxml-last-fontify-end))))) 866 (setq start (point))
890 867
891 (defun nxml-fontify-buffer () 868 (while (< (point) end)
892 (interactive) 869 (nxml-tokenize-forward))
893 (save-excursion 870
894 (save-restriction 871 (setq end (point))))
895 (widen) 872
896 (nxml-with-invisible-motion 873 (when (or (< start font-lock-beg)
897 (goto-char (point-min)) 874 (> end font-lock-end))
898 (nxml-with-unmodifying-text-property-changes 875 (setq font-lock-beg start
899 (nxml-fontify-prolog) 876 font-lock-end end)
900 (goto-char nxml-prolog-end) 877 (nxml-debug-change "nxml-extend-region" start end)
901 (nxml-do-fontify)))))) 878 t)))
879
880 (defun nxml-extend-after-change-region (start end pre-change-length)
881 (unless nxml-degraded
882 (setq nxml-last-fontify-end nil)
883
884 (nxml-with-degradation-on-error 'nxml-extend-after-change-region
885 (save-excursion
886 (save-restriction
887 (widen)
888 (save-match-data
889 (nxml-with-invisible-motion
890 (nxml-with-unmodifying-text-property-changes
891 (nxml-extend-after-change-region1
892 start end pre-change-length)))))))))
893
894 (defun nxml-extend-after-change-region1 (start end pre-change-length)
895 (let* ((region (nxml-after-change1 start end pre-change-length))
896 (font-lock-beg (car region))
897 (font-lock-end (cdr region)))
898
899 (nxml-extend-region)
900 (cons font-lock-beg font-lock-end)))
901
902 (defun nxml-fontify-matcher (bound)
903 "Called as font-lock keyword matcher."
904
905 (unless nxml-degraded
906 (nxml-debug-change "nxml-fontify-matcher" (point) bound)
907
908 (when (< (point) nxml-prolog-end)
909 ;; prolog needs to be fontified in one go, and
910 ;; nxml-extend-region makes sure we start at BOB.
911 (assert (bobp))
912 (nxml-fontify-prolog)
913 (goto-char nxml-prolog-end))
914
915 (let (xmltok-dependent-regions
916 xmltok-errors)
917 (while (and (nxml-tokenize-forward)
918 (<= (point) bound)) ; intervals are open-ended
919 (nxml-apply-fontify-rule)))
920
921 (setq nxml-last-fontify-end (point)))
922
923 ;; Since we did the fontification internally, tell font-lock to not
924 ;; do anything itself.
925 nil)
902 926
903 (defun nxml-fontify-prolog () 927 (defun nxml-fontify-prolog ()
904 "Fontify the prolog. 928 "Fontify the prolog.
905 The buffer is assumed to be prepared for fontification. 929 The buffer is assumed to be prepared for fontification.
906 This does not set the fontified property, but it does clear 930 This does not set the fontified property, but it does clear
907 faces appropriately." 931 faces appropriately."
908 (let ((regions nxml-prolog-regions)) 932 (let ((regions nxml-prolog-regions))
909 (nxml-clear-face (point-min) nxml-prolog-end)
910 (while regions 933 (while regions
911 (let ((region (car regions))) 934 (let ((region (car regions)))
912 (nxml-apply-fontify-rule (aref region 0) 935 (nxml-apply-fontify-rule (aref region 0)
913 (aref region 1) 936 (aref region 1)
914 (aref region 2))) 937 (aref region 2)))
915 (setq regions (cdr regions))))) 938 (setq regions (cdr regions)))))
916
917 (defun nxml-do-fontify (&optional bound)
918 "Fontify at least as far as bound.
919 Leave point after last fontified position."
920 (unless bound (setq bound (point-max)))
921 (let (xmltok-dependent-regions
922 xmltok-errors)
923 (while (and (< (point) bound)
924 (nxml-tokenize-forward))
925 (nxml-clear-face xmltok-start (point))
926 (nxml-apply-fontify-rule))))
927 939
928 ;; Vectors identify a substring of the token to be highlighted in some face. 940 ;; Vectors identify a substring of the token to be highlighted in some face.
929 941
930 ;; Token types returned by xmltok-forward. 942 ;; Token types returned by xmltok-forward.
931 943
2572 (let ((new (if (null arg) 2584 (let ((new (if (null arg)
2573 (not nxml-char-ref-extra-display) 2585 (not nxml-char-ref-extra-display)
2574 (> (prefix-numeric-value arg) 0)))) 2586 (> (prefix-numeric-value arg) 0))))
2575 (when (not (eq new nxml-char-ref-extra-display)) 2587 (when (not (eq new nxml-char-ref-extra-display))
2576 (setq nxml-char-ref-extra-display new) 2588 (setq nxml-char-ref-extra-display new)
2577 (save-excursion 2589 (font-lock-fontify-buffer))))
2578 (save-restriction
2579 (widen)
2580 (if nxml-char-ref-extra-display
2581 (nxml-with-unmodifying-text-property-changes
2582 (nxml-clear-fontified (point-min) (point-max)))
2583 (nxml-clear-char-ref-extra-display (point-min) (point-max))))))))
2584 2590
2585 (put 'nxml-char-ref 'evaporate t) 2591 (put 'nxml-char-ref 'evaporate t)
2586 2592
2587 (defun nxml-char-ref-display-extra (start end n) 2593 (defun nxml-char-ref-display-extra (start end n)
2588 (when nxml-char-ref-extra-display 2594 (when nxml-char-ref-extra-display