From 241481cf1e034a990200fe8d3df98a128cb13105 Mon Sep 17 00:00:00 2001 From: Yuan Fu Date: Mon, 27 Apr 2026 01:16:10 -0700 Subject: [PATCH] Add treesit-query-with-fallback * lisp/treesit.el (treesit-font-lock-rules): Handle treesit-query-with-fallback. (treesit-query-with-fallback): New function. * doc/lispref/modes.texi (Parser-based Font Lock): Mention treesit-query-with-fallback. --- doc/lispref/modes.texi | 8 ++++++++ etc/NEWS | 11 +++++++++++ lisp/treesit.el | 27 ++++++++++++++++++++++----- 3 files changed, 41 insertions(+), 5 deletions(-) diff --git a/doc/lispref/modes.texi b/doc/lispref/modes.texi index 13a8ec46a8a..5f0e9a0fade 100644 --- a/doc/lispref/modes.texi +++ b/doc/lispref/modes.texi @@ -4447,6 +4447,14 @@ arguments as optional arguments for future extensibility. If a capture name is both a face and a function, the face takes priority. If a capture name is neither a face nor a function, it is ignored. + +@findex treesit-query-with-optional +@findex treesit-query-with-fallback +Sometimes, to support different versions of the same grammar, it's +useful to conditionally include some optional @var{query}, or choose the +first valid query from a list of queries. Functions like +@code{treesit-query-with-optional} and +@code{treesit-query-with-fallback} can come in handy. @end defun @c FIXME: Cross-ref treesit-font-lock-level to user manual. diff --git a/etc/NEWS b/etc/NEWS index 43e858352ef..abe49005af8 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -1295,6 +1295,17 @@ information. Multi-language major modes can rely on the default return value from 'treesit-language-at' that uses the new function 'treesit-parsers-at'. ++++ +*** New function 'treesit-query-with-optional'. +When used in 'treesit-font-lock-rules', 'treesit-query-with-optional' +returns a default query plus the valid queries from a list of optional +queries. + ++++ +*** New function 'treesit-query-with-fallback'. +When used in 'treesit-font-lock-rules', 'treesit-query-with-fallback' +selects the first valid query from a list. + +++ *** Tree-sitter thing functions now work better with multiple parsers. The following functions now handle better the case when there are diff --git a/lisp/treesit.el b/lisp/treesit.el index 2b2740e2fb4..b0fd9d404e2 100644 --- a/lisp/treesit.el +++ b/lisp/treesit.el @@ -1660,7 +1660,12 @@ name, it is ignored." ;; The list this function returns. (result nil)) (while query-specs - (let ((token (pop query-specs))) + (let ((token (pop query-specs)) + (reset (lambda () + (setq current-language nil + current-override nil + current-feature nil + current-reversed nil)))) (pcase token ;; (1) Process keywords. (:default-language @@ -1719,10 +1724,10 @@ name, it is ignored." lang) result) ;; Clears any configurations set for this query. - (setq current-language nil - current-override nil - current-feature nil - current-reversed nil))) + (funcall reset))) + ;; (3) Skip this query, for whatever reason. Currently only + ;; utilized by `treesit-query-with-fallback' and undocumented. + ('nil (funcall reset)) (_ (signal 'treesit-font-lock-error `("Unexpected value" ,token)))))) (nreverse result)))) @@ -1743,6 +1748,18 @@ Use LANGUAGE for validating queries." (push query optional)))) (append mandatory optional))) +(defun treesit-query-with-fallback (language &rest queries) + "Return the first valid query in QUERIES. + +Return nil if no query is valid. Use LANGUAGE for validating queries." + (declare (indent 1)) + (catch 'return + (dolist (query queries) + (ignore-errors + (when (treesit--compile-query-with-cache language query) + (throw 'return query)))) + nil)) + ;; `font-lock-fontify-region-function' has the LOUDLY argument, but ;; `jit-lock-functions' doesn't pass that argument. So even if we set ;; `font-lock-verbose' to t, if jit-lock is enabled (and it's almost