1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2026-01-03 18:41:25 -08:00

nXML: Use font lock

This commit is contained in:
Michael Olson 2008-06-06 16:14:49 +00:00
parent 0a3a94b3d2
commit e8ec402f1b
3 changed files with 215 additions and 157 deletions

View file

@ -24,11 +24,6 @@
;; See nxml-rap.el for description of parsing strategy. ;; See nxml-rap.el for description of parsing strategy.
;; The font locking here is independent of font-lock.el. We want to
;; do more sophisticated handling of changes and we want to use the
;; same xmltok rather than regexps for parsing so that we parse
;; consistently and correctly.
;;; Code: ;;; Code:
(when (featurep 'mucs) (when (featurep 'mucs)
@ -56,11 +51,6 @@
:group 'nxml :group 'nxml
:group 'font-lock-faces) :group 'font-lock-faces)
(defcustom nxml-syntax-highlight-flag t
"*Non-nil means nxml-mode should perform syntax highlighting."
:group 'nxml
:type 'boolean)
(defcustom nxml-char-ref-display-glyph-flag t (defcustom nxml-char-ref-display-glyph-flag t
"*Non-nil means display glyph following character reference. "*Non-nil means display glyph following character reference.
The glyph is displayed in face `nxml-glyph'. The hook The glyph is displayed in face `nxml-glyph'. The hook
@ -100,8 +90,6 @@ attribute on the previous line."
:group 'nxml :group 'nxml
:type 'integer) :type 'integer)
(defvar nxml-fontify-chunk-size 500)
(defcustom nxml-bind-meta-tab-to-complete-flag (not window-system) (defcustom nxml-bind-meta-tab-to-complete-flag (not window-system)
"*Non-nil means bind M-TAB in `nxml-mode-map' to `nxml-complete'. "*Non-nil means bind M-TAB in `nxml-mode-map' to `nxml-complete'.
C-return will be bound to `nxml-complete' in any case. C-return will be bound to `nxml-complete' in any case.
@ -432,19 +420,13 @@ reference.")
map) map)
"Keymap for nxml-mode.") "Keymap for nxml-mode.")
(defvar nxml-font-lock-keywords
'(nxml-fontify-matcher)
"Default font lock keywords for nxml-mode.")
(defsubst nxml-set-face (start end face) (defsubst nxml-set-face (start end face)
(when (and face (< start end)) (when (and face (< start end))
(put-text-property start end 'face face))) (font-lock-append-text-property start end 'face face)))
(defun nxml-clear-face (start end)
(remove-text-properties start end '(face nil))
(nxml-clear-char-ref-extra-display start end))
(defsubst nxml-set-fontified (start end)
(put-text-property start end 'fontified t))
(defsubst nxml-clear-fontified (start end)
(remove-text-properties start end '(fontified nil)))
;;;###autoload ;;;###autoload
(defun nxml-mode () (defun nxml-mode ()
@ -453,9 +435,6 @@ reference.")
;; not mnemonic. ;; not mnemonic.
"Major mode for editing XML. "Major mode for editing XML.
Syntax highlighting is performed unless the variable
`nxml-syntax-highlight-flag' is nil.
\\[nxml-finish-element] finishes the current element by inserting an end-tag. \\[nxml-finish-element] finishes the current element by inserting an end-tag.
C-c C-i closes a start-tag with `>' and then inserts a balancing end-tag C-c C-i closes a start-tag with `>' and then inserts a balancing end-tag
leaving point between the start-tag and end-tag. leaving point between the start-tag and end-tag.
@ -540,13 +519,9 @@ Many aspects this mode can be customized using
(nxml-clear-dependent-regions (point-min) (point-max)) (nxml-clear-dependent-regions (point-min) (point-max))
(setq nxml-scan-end (copy-marker (point-min) nil)) (setq nxml-scan-end (copy-marker (point-min) nil))
(nxml-with-unmodifying-text-property-changes (nxml-with-unmodifying-text-property-changes
(when nxml-syntax-highlight-flag (nxml-clear-inside (point-min) (point-max))
(nxml-clear-fontified (point-min) (point-max)))
(nxml-clear-inside (point-min) (point-max))
(nxml-with-invisible-motion (nxml-with-invisible-motion
(nxml-scan-prolog))))) (nxml-scan-prolog)))))
(when nxml-syntax-highlight-flag
(add-hook 'fontification-functions 'nxml-fontify nil t))
(add-hook 'after-change-functions 'nxml-after-change nil t) (add-hook 'after-change-functions 'nxml-after-change nil t)
(add-hook 'change-major-mode-hook 'nxml-cleanup nil t) (add-hook 'change-major-mode-hook 'nxml-cleanup nil t)
@ -561,6 +536,19 @@ Many aspects this mode can be customized using
(setq buffer-file-coding-system nxml-default-buffer-file-coding-system)) (setq buffer-file-coding-system nxml-default-buffer-file-coding-system))
(when nxml-auto-insert-xml-declaration-flag (when nxml-auto-insert-xml-declaration-flag
(nxml-insert-xml-declaration))) (nxml-insert-xml-declaration)))
(setq font-lock-defaults
'(nxml-font-lock-keywords
t ; keywords-only; we highlight comments and strings here
nil ; font-lock-keywords-case-fold-search. XML is case sensitive
nil ; no special syntax table
nil ; no automatic syntactic fontification
(font-lock-extend-after-change-region-function
. nxml-extend-after-change-region)
(font-lock-extend-region-functions . (nxml-extend-region))
(jit-lock-contextually . t)
(font-lock-unfontify-region-function . nxml-unfontify-region)))
(rng-nxml-mode-init) (rng-nxml-mode-init)
(nxml-enable-unicode-char-name-sets) (nxml-enable-unicode-char-name-sets)
(run-hooks 'nxml-mode-hook)) (run-hooks 'nxml-mode-hook))
@ -591,84 +579,73 @@ Many aspects this mode can be customized using
(save-restriction (save-restriction
(widen) (widen)
(nxml-with-unmodifying-text-property-changes (nxml-with-unmodifying-text-property-changes
(nxml-clear-face (point-min) (point-max))
(nxml-set-fontified (point-min) (point-max))
(nxml-clear-inside (point-min) (point-max)))))) (nxml-clear-inside (point-min) (point-max))))))
;;; Change management ;;; Change management
(defun nxml-debug-region (start end)
(interactive "r")
(let ((font-lock-beg start)
(font-lock-end end))
(nxml-extend-region)
(goto-char font-lock-beg)
(set-mark font-lock-end)))
(defun nxml-after-change (start end pre-change-length) (defun nxml-after-change (start end pre-change-length)
;; Work around bug in insert-file-contents. ; In font-lock mode, nxml-after-change1 is called via
(when (> end (1+ (buffer-size))) ; nxml-extend-after-change-region instead so that the updated
(setq start 1) ; book-keeping information is available for fontification.
(setq end (1+ (buffer-size)))) (unless (or font-lock-mode nxml-degraded)
(unless nxml-degraded (nxml-with-degradation-on-error 'nxml-after-change
(condition-case err (save-excursion
(save-excursion (save-restriction
(save-restriction (widen)
(widen) (save-match-data
(save-match-data (nxml-with-invisible-motion
(nxml-with-invisible-motion (nxml-with-unmodifying-text-property-changes
(nxml-with-unmodifying-text-property-changes (nxml-after-change1
(nxml-after-change1 start end pre-change-length)))))) start end pre-change-length)))))))))
(error
(nxml-degrade 'nxml-after-change err)))))
(defun nxml-after-change1 (start end pre-change-length) (defun nxml-after-change1 (start end pre-change-length)
(setq nxml-last-fontify-end nil) "After-change bookkeeping. Returns a cons cell containing a
possibly-enlarged change region. You must call
nxml-extend-region on this expanded region to obtain the full
extent of the area needing refontification.
For bookkeeping, call this function even when fontification is
disabled."
(let ((pre-change-end (+ start pre-change-length))) (let ((pre-change-end (+ start pre-change-length)))
(setq start (setq start
(nxml-adjust-start-for-dependent-regions start (nxml-adjust-start-for-dependent-regions start
end end
pre-change-length)) pre-change-length))
;; If the prolog might have changed, rescan the prolog
(when (<= start (when (<= start
;; Add 2 so as to include the < and following char ;; Add 2 so as to include the < and following char that
;; that start the instance, since changing these ;; start the instance (document element), since changing
;; can change where the prolog ends. ;; these can change where the prolog ends.
(+ nxml-prolog-end 2)) (+ nxml-prolog-end 2))
;; end must be extended to at least the end of the old prolog ;; end must be extended to at least the end of the old prolog in
;; case the new prolog is shorter
(when (< pre-change-end nxml-prolog-end) (when (< pre-change-end nxml-prolog-end)
(setq end (setq end
;; don't let end get out of range even if pre-change-length ;; don't let end get out of range even if pre-change-length
;; is bogus ;; is bogus
(min (point-max) (min (point-max)
(+ end (- nxml-prolog-end pre-change-end))))) (+ end (- nxml-prolog-end pre-change-end)))))
(nxml-scan-prolog))) (nxml-scan-prolog)
(cond ((<= end nxml-prolog-end) (setq start (point-min))))
(setq end nxml-prolog-end)
(goto-char start) (when (> end nxml-prolog-end)
;; This is so that Emacs redisplay works (goto-char start)
(setq start (line-beginning-position))) (nxml-move-tag-backwards (point-min))
((and (<= start nxml-scan-end) (setq start (point))
(> start (point-min)) (setq end (max (nxml-scan-after-change start end)
(nxml-get-inside (1- start))) end)))
;; The closing delimiter might have been removed.
;; So we may need to redisplay from the beginning (nxml-debug-change "nxml-after-change1" start end)
;; of the token. (cons start end))
(goto-char (1- start))
(nxml-move-outside-backwards)
;; This is so that Emacs redisplay works
(setq start (line-beginning-position))
(setq end (max (nxml-scan-after-change (point) end)
end)))
(t
(goto-char start)
;; This is both for redisplay and to move back
;; past any incomplete opening delimiters
(setq start (line-beginning-position))
(setq end (max (nxml-scan-after-change start end)
end))))
(when nxml-syntax-highlight-flag
(when (>= start end)
;; Must clear at least one char so as to trigger redisplay.
(cond ((< start (point-max))
(setq end (1+ start)))
(t
(setq end (point-max))
(goto-char end)
(setq start (line-beginning-position)))))
(nxml-clear-fontified start end)))
;;; Encodings ;;; Encodings
(defun nxml-insert-xml-declaration () (defun nxml-insert-xml-declaration ()
@ -854,51 +831,98 @@ The XML declaration will declare an encoding depending on the buffer's
;;; Fontification ;;; Fontification
(defun nxml-fontify (start) (defun nxml-unfontify-region (start end)
(condition-case err (font-lock-default-unfontify-region start end)
(save-excursion (nxml-clear-char-ref-extra-display start end))
(save-restriction
(widen)
(save-match-data
(nxml-with-invisible-motion
(nxml-with-unmodifying-text-property-changes
(if (or nxml-degraded
;; just in case we get called in the wrong buffer
(not nxml-prolog-end))
(nxml-set-fontified start (point-max))
(nxml-fontify1 start)))))))
(error
(nxml-degrade 'nxml-fontify err))))
(defun nxml-fontify1 (start) (defvar font-lock-beg) (defvar font-lock-end)
(cond ((< start nxml-prolog-end) (defun nxml-extend-region ()
(nxml-fontify-prolog) "Extend the region to hold the minimum area we can fontify with nXML.
(nxml-set-fontified (point-min) Called with font-lock-beg and font-lock-end dynamically bound."
nxml-prolog-end)) (let ((start font-lock-beg)
(t (end font-lock-end))
(goto-char start)
(when (not (eq nxml-last-fontify-end start))
(when (not (equal (char-after) ?\<))
(search-backward "<" nxml-prolog-end t))
(nxml-ensure-scan-up-to-date)
(nxml-move-outside-backwards))
(let ((start (point)))
(nxml-do-fontify (min (point-max)
(+ start nxml-fontify-chunk-size)))
(setq nxml-last-fontify-end (point))
(nxml-set-fontified start nxml-last-fontify-end)))))
(defun nxml-fontify-buffer () (nxml-debug-change "nxml-extend-region(input)" start end)
(interactive)
(save-excursion (when (< start nxml-prolog-end)
(save-restriction (setq start (point-min)))
(widen)
(nxml-with-invisible-motion (cond ((<= end nxml-prolog-end)
(goto-char (point-min)) (setq end nxml-prolog-end))
(nxml-with-unmodifying-text-property-changes
(nxml-fontify-prolog) (t
(goto-char nxml-prolog-end) (goto-char start)
(nxml-do-fontify)))))) ;; some font-lock backends (like Emacs 22 jit-lock) snap
;; the region to the beginning of the line no matter what
;; we say here. To mitigate the resulting excess
;; fontification, ignore leading whitespace.
(skip-syntax-forward " ")
;; find the beginning of the previous tag
(when (not (equal (char-after) ?\<))
(search-backward "<" nxml-prolog-end t))
(nxml-ensure-scan-up-to-date)
(nxml-move-outside-backwards)
(setq start (point))
(while (< (point) end)
(nxml-tokenize-forward))
(setq end (point))))
(when (or (< start font-lock-beg)
(> end font-lock-end))
(setq font-lock-beg start
font-lock-end end)
(nxml-debug-change "nxml-extend-region" start end)
t)))
(defun nxml-extend-after-change-region (start end pre-change-length)
(unless nxml-degraded
(setq nxml-last-fontify-end nil)
(nxml-with-degradation-on-error 'nxml-extend-after-change-region
(save-excursion
(save-restriction
(widen)
(save-match-data
(nxml-with-invisible-motion
(nxml-with-unmodifying-text-property-changes
(nxml-extend-after-change-region1
start end pre-change-length)))))))))
(defun nxml-extend-after-change-region1 (start end pre-change-length)
(let* ((region (nxml-after-change1 start end pre-change-length))
(font-lock-beg (car region))
(font-lock-end (cdr region)))
(nxml-extend-region)
(cons font-lock-beg font-lock-end)))
(defun nxml-fontify-matcher (bound)
"Called as font-lock keyword matcher."
(unless nxml-degraded
(nxml-debug-change "nxml-fontify-matcher" (point) bound)
(when (< (point) nxml-prolog-end)
;; prolog needs to be fontified in one go, and
;; nxml-extend-region makes sure we start at BOB.
(assert (bobp))
(nxml-fontify-prolog)
(goto-char nxml-prolog-end))
(let (xmltok-dependent-regions
xmltok-errors)
(while (and (nxml-tokenize-forward)
(<= (point) bound)) ; intervals are open-ended
(nxml-apply-fontify-rule)))
(setq nxml-last-fontify-end (point)))
;; Since we did the fontification internally, tell font-lock to not
;; do anything itself.
nil)
(defun nxml-fontify-prolog () (defun nxml-fontify-prolog ()
"Fontify the prolog. "Fontify the prolog.
@ -906,7 +930,6 @@ The buffer is assumed to be prepared for fontification.
This does not set the fontified property, but it does clear This does not set the fontified property, but it does clear
faces appropriately." faces appropriately."
(let ((regions nxml-prolog-regions)) (let ((regions nxml-prolog-regions))
(nxml-clear-face (point-min) nxml-prolog-end)
(while regions (while regions
(let ((region (car regions))) (let ((region (car regions)))
(nxml-apply-fontify-rule (aref region 0) (nxml-apply-fontify-rule (aref region 0)
@ -914,17 +937,6 @@ faces appropriately."
(aref region 2))) (aref region 2)))
(setq regions (cdr regions))))) (setq regions (cdr regions)))))
(defun nxml-do-fontify (&optional bound)
"Fontify at least as far as bound.
Leave point after last fontified position."
(unless bound (setq bound (point-max)))
(let (xmltok-dependent-regions
xmltok-errors)
(while (and (< (point) bound)
(nxml-tokenize-forward))
(nxml-clear-face xmltok-start (point))
(nxml-apply-fontify-rule))))
;; Vectors identify a substring of the token to be highlighted in some face. ;; Vectors identify a substring of the token to be highlighted in some face.
;; Token types returned by xmltok-forward. ;; Token types returned by xmltok-forward.
@ -2574,13 +2586,7 @@ With a prefix argument, inserts the character directly."
(> (prefix-numeric-value arg) 0)))) (> (prefix-numeric-value arg) 0))))
(when (not (eq new nxml-char-ref-extra-display)) (when (not (eq new nxml-char-ref-extra-display))
(setq nxml-char-ref-extra-display new) (setq nxml-char-ref-extra-display new)
(save-excursion (font-lock-fontify-buffer))))
(save-restriction
(widen)
(if nxml-char-ref-extra-display
(nxml-with-unmodifying-text-property-changes
(nxml-clear-fontified (point-min) (point-max)))
(nxml-clear-char-ref-extra-display (point-min) (point-max))))))))
(put 'nxml-char-ref 'evaporate t) (put 'nxml-char-ref 'evaporate t)

View file

@ -110,9 +110,11 @@ There must be no nxml-inside properties after nxml-scan-end.")
(get-text-property pos 'nxml-inside)) (get-text-property pos 'nxml-inside))
(defsubst nxml-clear-inside (start end) (defsubst nxml-clear-inside (start end)
(nxml-debug-clear-inside start end)
(remove-text-properties start end '(nxml-inside nil))) (remove-text-properties start end '(nxml-inside nil)))
(defsubst nxml-set-inside (start end type) (defsubst nxml-set-inside (start end type)
(nxml-debug-set-inside start end)
(put-text-property start end 'nxml-inside type)) (put-text-property start end 'nxml-inside type))
(defun nxml-inside-end (pos) (defun nxml-inside-end (pos)
@ -137,12 +139,10 @@ Return nil if the character at POS is not inside."
"Restore `nxml-scan-end' invariants after a change. "Restore `nxml-scan-end' invariants after a change.
The change happened between START and END. The change happened between START and END.
Return position after which lexical state is unchanged. Return position after which lexical state is unchanged.
END must be > nxml-prolog-end." END must be > nxml-prolog-end. START must be outside
any 'inside' regions and at the beginning of a token."
(if (>= start nxml-scan-end) (if (>= start nxml-scan-end)
nxml-scan-end nxml-scan-end
(goto-char start)
(nxml-move-outside-backwards)
(setq start (point))
(let ((inside-remove-start start) (let ((inside-remove-start start)
xmltok-errors xmltok-errors
xmltok-dependent-regions) xmltok-dependent-regions)
@ -214,7 +214,7 @@ END must be > nxml-prolog-end."
(setq adjusted-start ostart))))) (setq adjusted-start ostart)))))
(setq overlays (cdr overlays))) (setq overlays (cdr overlays)))
adjusted-start)) adjusted-start))
(defun nxml-mark-parse-dependent-regions () (defun nxml-mark-parse-dependent-regions ()
(while xmltok-dependent-regions (while xmltok-dependent-regions
(apply 'nxml-mark-parse-dependent-region (apply 'nxml-mark-parse-dependent-region
@ -300,6 +300,20 @@ Sets variables like `nxml-token-after'."
(set-marker nxml-scan-end (point))) (set-marker nxml-scan-end (point)))
xmltok-type)) xmltok-type))
(defun nxml-move-tag-backwards (bound)
"Move point backwards outside any 'inside' regions or tags, up
to nxml-prolog-end. Point will either be at bound or a '<'
character starting a tag outside any 'inside' regions. Ignores
dependent regions. As a precondition, point must be >= bound."
(nxml-move-outside-backwards)
(when (not (equal (char-after) ?<))
(if (search-backward "<" bound t)
(progn
(nxml-move-outside-backwards)
(when (not (equal (char-after) ?<))
(search-backward "<" bound t)))
(goto-char bound))))
(defun nxml-move-outside-backwards () (defun nxml-move-outside-backwards ()
"Move point to first character of the containing special thing. "Move point to first character of the containing special thing.
Leave point unmoved if it is not inside anything special." Leave point unmoved if it is not inside anything special."

View file

@ -24,6 +24,35 @@
;;; Code: ;;; Code:
(defconst nxml-debug nil
"enable nxml debugging. effective only at compile time")
(eval-when-compile
(require 'cl))
(defsubst nxml-debug (format &rest args)
(when nxml-debug
(apply #'message format args)))
(defmacro nxml-debug-change (name start end)
(when nxml-debug
`(nxml-debug "%s: %S" ,name
(buffer-substring-no-properties ,start ,end))))
(defmacro nxml-debug-set-inside (start end)
(when nxml-debug
`(let ((overlay (make-overlay ,start ,end)))
(overlay-put overlay 'face '(:background "red"))
(overlay-put overlay 'nxml-inside-debug t)
(nxml-debug-change "nxml-set-inside" ,start ,end))))
(defmacro nxml-debug-clear-inside (start end)
(when nxml-debug
`(loop for overlay in (overlays-in ,start ,end)
if (overlay-get overlay 'nxml-inside-debug)
do (delete-overlay overlay)
finally (nxml-debug-change "nxml-clear-inside" ,start ,end))))
(defun nxml-make-namespace (str) (defun nxml-make-namespace (str)
"Return a symbol for the namespace URI STR. "Return a symbol for the namespace URI STR.
STR must be a string. If STR is the empty string, return nil. STR must be a string. If STR is the empty string, return nil.
@ -37,12 +66,21 @@ Otherwise, return the symbol whose name is STR prefixed with a colon."
This is the inverse of `nxml-make-namespace'." This is the inverse of `nxml-make-namespace'."
(and ns (substring (symbol-name ns) 1))) (and ns (substring (symbol-name ns) 1)))
(defconst nxml-xml-namespace-uri (defconst nxml-xml-namespace-uri
(nxml-make-namespace "http://www.w3.org/XML/1998/namespace")) (nxml-make-namespace "http://www.w3.org/XML/1998/namespace"))
(defconst nxml-xmlns-namespace-uri (defconst nxml-xmlns-namespace-uri
(nxml-make-namespace "http://www.w3.org/2000/xmlns/")) (nxml-make-namespace "http://www.w3.org/2000/xmlns/"))
(defmacro nxml-with-degradation-on-error (context &rest body)
(if (not nxml-debug)
(let ((error-symbol (make-symbol "err")))
`(condition-case ,error-symbol
(progn ,@body)
(error
(nxml-degrade ,context ,error-symbol))))
`(progn ,@body)))
(defmacro nxml-with-unmodifying-text-property-changes (&rest body) (defmacro nxml-with-unmodifying-text-property-changes (&rest body)
"Evaluate BODY without any text property changes modifying the buffer. "Evaluate BODY without any text property changes modifying the buffer.
Any text properties changes happen as usual but the changes are not treated as Any text properties changes happen as usual but the changes are not treated as