1
Fork 0
mirror of git://git.sv.gnu.org/emacs.git synced 2025-12-17 19:30:38 -08:00

Add :override flag for tree-sitter font-lock

* doc/lispref/modes.texi (Parser-based Font Lock): Update manual.
* lisp/treesit.el (treesit-font-lock-settings): Update docstring.
(treesit-font-lock-rules): Handle :override.
(treesit-font-lock-fontify-region): Handle :override.  Also set
inhibit-point-motion-hooks to t.
This commit is contained in:
Yuan Fu 2022-10-04 13:28:46 -07:00
parent 7a4380b905
commit 23bb724c92
No known key found for this signature in database
GPG key ID: 56E19BC57664A442
2 changed files with 77 additions and 20 deletions

View file

@ -3911,6 +3911,7 @@ a value that @var{treesit-font-lock-settings} accepts. An example:
@group @group
(treesit-font-lock-rules (treesit-font-lock-rules
:language 'javascript :language 'javascript
:override t
'((true) @@font-lock-constant-face '((true) @@font-lock-constant-face
(false) @@font-lock-constant-face) (false) @@font-lock-constant-face)
:language 'html :language 'html
@ -3919,9 +3920,19 @@ a value that @var{treesit-font-lock-settings} accepts. An example:
@end example @end example
This function takes a list of text or s-exp queries. Before each This function takes a list of text or s-exp queries. Before each
query, there are @var{:keyword} and @var{value} pairs that configures query, there are @var{:keyword} and @var{value} pairs that configure
that query. The @code{:lang} keyword sets the querys language, and is that query. The @code{:lang} keyword sets the querys language and
currently the only recognized keyword. every query must specify the language. Other keywords are optional:
@multitable @columnfractions .15 .15 .6
@headitem Keyword @tab Value @tab Description
@item @code{:override} @tab nil
@tab If the region already has a face, discard the new face
@item @tab t @tab Always apply the new face
@item @tab @code{append} @tab Append the new face to existing ones
@item @tab @code{prepend} @tab Prepend the new face to existing ones
@item @tab @code{keep} @tab Fill-in regions without an existing face
@end multitable
Capture names in @var{query} should be face names like Capture names in @var{query} should be face names like
@code{font-lock-keyword-face}. The captured node will be fontified @code{font-lock-keyword-face}. The captured node will be fontified

View file

@ -277,6 +277,10 @@ in-order. START and END are passed to each range function."
;;; Font-lock ;;; Font-lock
(define-error 'treesit-font-lock-error
"Generic tree-sitter font-lock error"
'treesit-error)
(defvar-local treesit-font-lock-settings nil (defvar-local treesit-font-lock-settings nil
"A list of SETTINGs for treesit-based fontification. "A list of SETTINGs for treesit-based fontification.
@ -285,7 +289,7 @@ should always use `treesit-font-lock-rules' to set this variable.
Each SETTING is of form Each SETTING is of form
(LANGUAGE QUERY) (LANGUAGE QUERY OVERRIDE)
Each SETTING controls one parser (often of different language). Each SETTING controls one parser (often of different language).
LANGUAGE is the language symbol. See Info node `(elisp)Language LANGUAGE is the language symbol. See Info node `(elisp)Language
@ -296,7 +300,11 @@ query. See Info node `(elisp)Pattern Matching' for how to write
a query in either string or s-expression form. When using a query in either string or s-expression form. When using
repeatedly, a compiled query is much faster than a string or sexp repeatedly, a compiled query is much faster than a string or sexp
one, so it is recommend to compile your queries if it will be one, so it is recommend to compile your queries if it will be
used over and over.") used over and over.
OVERRIDE is the override flag for this query. Its value can be
t, nil, append, prepend, keep. See more in
`treesit-font-lock-rules'.")
(defun treesit-font-lock-rules (&rest args) (defun treesit-font-lock-rules (&rest args)
"Return a value suitable for `treesit-font-lock-settings'. "Return a value suitable for `treesit-font-lock-settings'.
@ -311,13 +319,22 @@ configure the query (and only that query). For example,
(treesit-font-lock-rules (treesit-font-lock-rules
:language \\='javascript :language \\='javascript
:override t
\\='((true) @font-lock-constant-face \\='((true) @font-lock-constant-face
(false) @font-lock-constant-face) (false) @font-lock-constant-face)
:language \\='html :language \\='html
\"(script_element) @font-lock-builtin-face\") \"(script_element) @font-lock-builtin-face\")
For each QUERY, a :language keyword is required. Currently the For each QUERY, a :language keyword is required. Other keywords
only recognized keyword is :language. include:
KEYWORD VALUE DESCRIPTION
:override nil If the region already has a face,
discard the new face
t Always apply the new face
append Append the new face to existing ones
prepend Prepend the new face to existing ones
keep Fill-in regions without an existing face
Capture names in QUERY should be face names like Capture names in QUERY should be face names like
`font-lock-keyword-face'. The captured node will be fontified `font-lock-keyword-face'. The captured node will be fontified
@ -333,6 +350,8 @@ ignored.
(let (;; Tracks the current language that following queries will (let (;; Tracks the current language that following queries will
;; apply to. ;; apply to.
(current-language nil) (current-language nil)
;; Tracks :override flag.
(current-override nil)
;; The list this function returns. ;; The list this function returns.
(result nil)) (result nil))
(while args (while args
@ -343,30 +362,41 @@ ignored.
(when (or (not (symbolp lang)) (null lang)) (when (or (not (symbolp lang)) (null lang))
(signal 'wrong-type-argument `(symbolp ,lang))) (signal 'wrong-type-argument `(symbolp ,lang)))
(setq current-language lang))) (setq current-language lang)))
(:override
(let ((flag (pop args)))
(when (not (memq flag '(t nil append prepend keep)))
(signal 'wrong-type-argument
`((or t nil append prepend keep)
,flag)))
(setq current-override flag)))
((pred treesit-query-p) ((pred treesit-query-p)
(when (null current-language) (when (null current-language)
(signal 'treesit-error (signal 'treesit-font-lock-error
`("Language unspecified, use :language keyword to specify a language for this query" ,token))) `("Language unspecified, use :language keyword to specify a language for this query" ,token)))
(if (treesit-compiled-query-p token) (if (treesit-compiled-query-p token)
(push `(,current-language token) result) (push `(,current-language token) result)
(push `(,current-language (push `(,current-language
,(treesit-query-compile current-language token)) ,(treesit-query-compile current-language token)
,current-override)
result)) result))
;; Clears any configurations set for this query. ;; Clears any configurations set for this query.
(setq current-language nil)) (setq current-language nil
(_ (signal 'treesit-error current-override nil))
(_ (signal 'treesit-font-lock-error
`("Unexpected value" token)))))) `("Unexpected value" token))))))
(nreverse result))) (nreverse result)))
(defun treesit-font-lock-fontify-region (start end &optional loudly) (defun treesit-font-lock-fontify-region
(start end &optional loudly)
"Fontify the region between START and END. "Fontify the region between START and END.
If LOUDLY is non-nil, message some debugging information." If LOUDLY is non-nil, message some debugging information."
(treesit-update-ranges start end) (treesit-update-ranges start end)
(font-lock-unfontify-region start end) (font-lock-unfontify-region start end)
(dolist (setting treesit-font-lock-settings) (dolist (setting treesit-font-lock-settings)
(when-let* ((language (nth 0 setting)) (let* ((language (nth 0 setting))
(match-pattern (nth 1 setting)) (match-pattern (nth 1 setting))
(parser (treesit-parser-create language))) (override (nth 2 setting))
(parser (treesit-parser-create language)))
(when-let ((node (treesit-node-on start end parser))) (when-let ((node (treesit-node-on start end parser)))
(let ((captures (treesit-query-capture (let ((captures (treesit-query-capture
node match-pattern node match-pattern
@ -374,17 +404,33 @@ If LOUDLY is non-nil, message some debugging information."
;; often than not, NODE will be the root ;; often than not, NODE will be the root
;; node, and if we don't specify the range, ;; node, and if we don't specify the range,
;; we are basically querying the whole file. ;; we are basically querying the whole file.
start end))) start end))
(inhibit-point-motion-hooks t))
(with-silent-modifications (with-silent-modifications
(dolist (capture captures) (dolist (capture captures)
(let* ((face (car capture)) (let* ((face (car capture))
(node (cdr capture)) (node (cdr capture))
(start (treesit-node-start node)) (start (treesit-node-start node))
(end (treesit-node-end node))) (end (treesit-node-end node)))
(cond ((facep face) (cond
(put-text-property start end 'face face)) ((facep face)
((functionp face) (pcase override
(funcall face start end node))) ('nil (unless (text-property-not-all
start end 'face nil)
(put-text-property start end 'face face)))
('t (put-text-property start end 'face face))
('append (font-lock-append-text-property
start end 'face face))
('prepend (font-lock-prepend-text-property
start end 'face face))
('keep (font-lock-fillin-text-property
start end 'face face))
(_ (signal 'treesit-font-lock-error
(list
"Unrecognized value of :override option"
override)))))
((functionp face)
(funcall face start end node)))
;; Don't raise an error if FACE is neither a face nor ;; Don't raise an error if FACE is neither a face nor
;; a function. This is to allow intermediate capture ;; a function. This is to allow intermediate capture
;; names used for #match and #eq. ;; names used for #match and #eq.