Subject: Re: bug#74610: 31.0.50; Submitting mhtml-ts-mode, treesitter alternative to mhtml-mode

From: Eli Zaretskii <[email-masked]>
Date: Sat, 08 Feb 2025 11:01:48 +0200

Ping! Can we please make progress with this issue?  is the patch ready
to be installed?

> From: Vincenzo Pupillo <[email-masked]>
> Cc: Juri Linkov <[email-masked]>, [email-masked],
>  Eli Zaretskii <[email-masked]>
> Date: Sun, 19 Jan 2025 18:09:31 +0100
> 
> Ciao Yuan,
> In data sabato 18 gennaio 2025 02:37:53 Ora standard dell’Europa centrale, 
> Yuan Fu ha scritto:
> > > On Jan 14, 2025, at 1:41 PM, Vincenzo Pupillo <[email-masked]> wrote:
> > > 
> > > Ciao Yuan and Juri,
> > > this is an updated version of mhtml-ts-mode.
> > > I have tried to reduce as much as possible copies of parts of the major
> > > modes from which it is derived.
> > > To do this, I had to move some values that were assigned directly to
> > > treesit's own variables  (in ccs-mode.el, in js.el, and in
> > > html-ts-mode.el) into new variables.
> > > I also added three new functions to treesit.el to make it easier to
> > > combine
> > > parts derived from the other major modes. So now any changes to these new
> > > variables are directly reflected in the behavior of mhtml-ts-mode.
> > > There are a few things I would like to highlight:
> > > 1. treesit-font-lock-feature-lists are not defined per parser, so simply
> > > merging the different lists will cause display differences from the
> > > original major-modes; for example “function” is defined at level 3 in
> > > css-ts-mode but at level 4 in js-ts-mode.
> > 
> > Yeah, that’s not pretty, I don’t have a good solution for now. Technically a
> > major mode can use treesit-font-lock-recompute-features after
> > treesit-major-mode-setup to do fine adjustments, but it’s ugly.
> > > 2. treesit-defun-type-regexp has the same problem as treesit-font-lock-
> > > feature-list, so I had to define it myself.
> > 
> > Maybe you can use treesit-thing-settings instead?
> Ok, done.
> 
> > 
> > > But other than that, it works pretty well.
> > > 
> > > IMHO, a global "list" where you can define "font-lock", "indent-list",
> > > "font- lock-feature", etc. by language (perhaps with getter and setter
> > > methods) might make it easier to define new multilingual major-modes. It
> > > could improve the decoupling between multilingual major-modes and the
> > > major-modes they are derived from. It could also better decouple the
> > > internal implementation of treesit.el from the treesitter-based
> > > major-modes.
> > > 
> > > Let me know what you think.
> > 
> > That’s what I had in mind. That’s also why I added a new variable
> > treesit-aggregated-simple-imenu-settings instead of piggybacking on
> > treesit-simple-imenu-settings. A separate variable is simpler to borrow
> > settings from.
> Yes, in this new patch I used treesit-aggregated-simple-imenu-settings.
> 
> > But I didn’t have time to think it though yet. (By language, or by mode and
> > language? Should packages set it on load, or define a function that loads
> > these settings? Etc.)
> >
> I would say by mode and language, both mhtml-ts mode and html-ts mode have the 
> same primary parser. For the second question, I don't know. I don't have 
> enough experience with it to know what the pros and cons are.
> 
> > Yuan
> 
> Vincenzo
> 
> From 199af27f1d761d100f6a8a6163c27e35e6ab2ea0 Mon Sep 17 00:00:00 2001
> From: Vincenzo Pupillo <[email-masked]>
> Date: Sun, 19 Jan 2025 17:49:30 +0100
> Subject: [PATCH] Add mhtml-ts-mode.
> 
> New major-mode alternative to mhtml-mode, based on treesitter, for
> editing files containing html, javascript and css.
> 
> * etc/NEWS: Mention the new mode and new functions.
> * lisp/textmodes/mhtml-ts-mode.el: New file.
> * lisp/progmodes/js.el (js--treesit-thing-settings): New variable.
> (js--treesit-font-lock-feature-list); New variable.
> (js--treesit-simple-imenu-settings): New variable.
> (js--treesit-defun-type-regexp): New variable.
> (js--treesit-jsdoc-comment-regexp): New variable.
> (js-ts-mode): Use of new variables instead of direct assignment of
> values.
> * lisp/textmodes/css-mode.el (css-mode--menu): New variable.
> (css-mode-map): Use new variable.
> (css--treesit-font-lock-feature-list): New variable.
> (css--treesit-simple-imenu-settings): New variable.
> (css--treesit-defun-type-regexp): New variable.
> (cs-ts-mode): Use of new variables instead of direct assignment of
> values.
> * lisp/textmodes/html-ts-mode.el
> (html-ts-mode--treesit-things-settings): New variable.
> (html-ts-mode--treesit-font-lock-feature-list): New variable.
> (html-ts-mode--treesit-simple-imenu-settings): New variable.
> (html-ts-mode--treesit-defun-type-regexp): New variable.
> (html-ts-mode): Use of new variables instead of direct assignment of
> values.
> * lisp/treesit.el
> (treesit-merge-font-lock-feature-list): New fuction.
> (treesit-replace-font-lock-feature-settings): New fuction.
> (treesit-modify-indent-rules): New function.
> ---
>  etc/NEWS                        |  28 ++
>  lisp/progmodes/js.el            |  72 ++--
>  lisp/textmodes/css-mode.el      |  47 ++-
>  lisp/textmodes/html-ts-mode.el  |  45 ++-
>  lisp/textmodes/mhtml-ts-mode.el | 600 ++++++++++++++++++++++++++++++++
>  lisp/treesit.el                 |  65 ++++
>  6 files changed, 798 insertions(+), 59 deletions(-)
>  create mode 100644 lisp/textmodes/mhtml-ts-mode.el
> 
> diff --git a/etc/NEWS b/etc/NEWS
> index 0b849dec450..6360a082e9f 100644
> --- a/etc/NEWS
> +++ b/etc/NEWS
> @@ -965,6 +965,12 @@ destination window is chosen using 'display-buffer-alist'.  Example:
>  
>  
>  * New Modes and Packages in Emacs 31.1
> +** New major modes based on the tree-sitter library
> +
> +*** New major mode 'mhtml-ts-mode'.
> +An optional major mode based on the tree-sitter library for editing html
> +files. This mode handles indentation, fontification, and commenting for
> +embedded JavaScript and CSS.
>  
>  
>  * Incompatible Lisp Changes in Emacs 31.1
> @@ -1098,6 +1104,28 @@ language symbol.  For example, 'cpp' is translated to "C++".  A new
>  variable 'treesit-language-display-name-alist' holds the translations of
>  language symbols where that translation is not trivial.
>  
> ++++
> +*** New function 'treesit-merge-font-lock-feature-list'.
> +This function the merge two tree-sitter font lock feature lists.
> +Returns a new font lock feature list with no duplicates in the same level.
> +It can be used to merge font lock feature lists in a multi-language major mode.
> +
> ++++
> +*** New function 'treesit-replace-font-lock-feature-settings'.
> +Given two treesit-font-lock-settings replaces the feature in the second
> +font-lock-settings with the same feature in the first
> +font-lock-settings. In a multi-linguage major mode it is sometimes
> +necessary to replace features from one of the major modes, with others
> +that are better suited to the new multilingual context.
> +
> ++++
> +*** New function 'treesit-modify-indent-rules'.
> +Given two treesit ident rules, it replaces, adds, or prepends the new
> +rules to the old ones, then returns a new treesit indent rules.
> +In a multi-linguage major mode it is sometimes necessary to modify rules
> +from one of the major modes, with others that are better suited to the
> +new multilingual context.
> +
>  +++
>  *** New command 'treesit-explore'
>  This command replaces 'treesit-explore-mode'.  It turns on
> diff --git a/lisp/progmodes/js.el b/lisp/progmodes/js.el
> index 101b882c718..be9d6f64aef 100644
> --- a/lisp/progmodes/js.el
> +++ b/lisp/progmodes/js.el
> @@ -3901,6 +3901,44 @@ js--treesit-list-nodes
>  (defvar js--treesit-jsdoc-beginning-regexp (rx bos "/**")
>    "Regular expression matching the beginning of a jsdoc block comment.")
>  
> +(defvar js--treesit-thing-settings
> +  `((javascript
> +     (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
> +     (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
> +     (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
> +     (text ,(js--regexp-opt-symbol '("comment"
> +                                     "string_fragment")))))
> +  "Settings for `treesit-thing-settings'.")
> +
> +(defvar js--treesit-font-lock-feature-list
> +  '(( comment document definition)
> +    ( keyword string)
> +    ( assignment constant escape-sequence jsx number
> +      pattern string-interpolation)
> +    ( bracket delimiter function operator property))
> +  "Settings for `treesit-font-lock-feature-list'.")
> +
> +(defvar js--treesit-simple-imenu-settings
> +  `(("Function" "\\`function_declaration\\'" nil nil)
> +    ("Variable" "\\`lexical_declaration\\'"
> +     js--treesit-valid-imenu-entry nil)
> +    ("Class" ,(rx bos (or "class_declaration"
> +                          "method_definition")
> +                  eos)
> +     nil nil))
> +  "Settings for `treesit-simple-imenu'.")
> +
> +(defvar js--treesit-defun-type-regexp
> +  (rx (or "class_declaration"
> +          "method_definition"
> +          "function_declaration"
> +          "lexical_declaration"))
> +  "Settings for `treesit-defun-type-regexp'.")
> +
> +(defvar js--treesit-jsdoc-comment-regexp
> +  (rx (or "comment" "line_comment" "block_comment" "description"))
> +  "Regexp for `c-ts-common--comment-regexp'.")
> +
>  ;;;###autoload
>  (define-derived-mode js-ts-mode js-base-mode "JavaScript"
>    "Major mode for editing JavaScript.
> @@ -3931,29 +3969,15 @@ js-ts-mode
>      ;; Indent.
>      (setq-local treesit-simple-indent-rules js--treesit-indent-rules)
>      ;; Navigation.
> -    (setq-local treesit-defun-type-regexp
> -                (rx (or "class_declaration"
> -                        "method_definition"
> -                        "function_declaration"
> -                        "lexical_declaration")))
> +    (setq-local treesit-defun-type-regexp js--treesit-defun-type-regexp)
> +
>      (setq-local treesit-defun-name-function #'js--treesit-defun-name)
>  
> -    (setq-local treesit-thing-settings
> -                `((javascript
> -                   (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
> -                   (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
> -                   (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
> -                   (text ,(js--regexp-opt-symbol '("comment"
> -                                                   "string_fragment"))))))
> +    (setq-local treesit-thing-settings js--treesit-thing-settings)
>  
>      ;; Fontification.
>      (setq-local treesit-font-lock-settings js--treesit-font-lock-settings)
> -    (setq-local treesit-font-lock-feature-list
> -                '(( comment document definition)
> -                  ( keyword string)
> -                  ( assignment constant escape-sequence jsx number
> -                    pattern string-interpolation)
> -                  ( bracket delimiter function operator property)))
> +    (setq-local treesit-font-lock-feature-list js--treesit-font-lock-feature-list)
>  
>      (when (treesit-ready-p 'jsdoc t)
>        (setq-local treesit-range-settings
> @@ -3963,17 +3987,11 @@ js-ts-mode
>                     :local t
>                     `(((comment) @capture (:match ,js--treesit-jsdoc-beginning-regexp @capture)))))
>  
> -      (setq c-ts-common--comment-regexp (rx (or "comment" "line_comment" "block_comment" "description"))))
> +      (setq c-ts-common--comment-regexp js--treesit-jsdoc-comment-regexp))
>  
>      ;; Imenu
> -    (setq-local treesit-simple-imenu-settings
> -                `(("Function" "\\`function_declaration\\'" nil nil)
> -                  ("Variable" "\\`lexical_declaration\\'"
> -                   js--treesit-valid-imenu-entry nil)
> -                  ("Class" ,(rx bos (or "class_declaration"
> -                                        "method_definition")
> -                                eos)
> -                   nil nil)))
> +    (setq-local treesit-simple-imenu-settings js--treesit-simple-imenu-settings)
> +
>      (treesit-major-mode-setup)
>  
>      (add-to-list 'auto-mode-alist
> diff --git a/lisp/textmodes/css-mode.el b/lisp/textmodes/css-mode.el
> index 53340195386..35c61e4f66d 100644
> --- a/lisp/textmodes/css-mode.el
> +++ b/lisp/textmodes/css-mode.el
> @@ -893,13 +893,7 @@ css-mode-syntax-table
>      (modify-syntax-entry ?? "." st)
>      st))
>  
> -(defvar-keymap css-mode-map
> -  :doc "Keymap used in `css-mode'."
> -  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
> -  ;; `info-complete-symbol' is not used.
> -  "<remap> <complete-symbol>" #'completion-at-point
> -  "C-c C-f" #'css-cycle-color-format
> -  :menu
> +(defvar css-mode--menu
>    '("CSS"
>      :help "CSS-specific features"
>      ["Reformat block" fill-paragraph
> @@ -910,7 +904,17 @@ css-mode-map
>      ["Describe symbol" css-lookup-symbol
>       :help "Display documentation for a CSS symbol"]
>      ["Complete symbol" completion-at-point
> -     :help "Complete symbol before point"]))
> +     :help "Complete symbol before point"])
> +    "Menu bar for `css-mode'")
> +
> +(defvar-keymap css-mode-map
> +  :doc "Keymap used in `css-mode'."
> +  "<remap> <info-lookup-symbol>" #'css-lookup-symbol
> +  ;; `info-complete-symbol' is not used.
> +  "<remap> <complete-symbol>" #'completion-at-point
> +  "C-c C-f" #'css-cycle-color-format
> +  :menu
> +  css-mode--menu)
>  
>  (eval-and-compile
>    (defconst css--uri-re
> @@ -1771,6 +1775,21 @@ css--extract-index-name
>                (replace-regexp-in-string "[\n ]+" " " s)))
>             res)))))))
>  
> +(defvar css--treesit-font-lock-feature-list
> +  '((selector comment query keyword)
> +    (property constant string)
> +    (error variable function operator bracket))
> +  "Settings for `treesit-font-lock-feature-list'.")
> +
> +(defvar css--treesit-simple-imenu-settings
> +  `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
> +      nil nil))
> +  "Settings for `treesit-simple-imenu'.")
> +
> +(defvar css--treesit-defun-type-regexp
> +  "rule_set"
> +  "Settings for `treesit-defun-type-regexp'.")
> +
>  (define-derived-mode css-base-mode prog-mode "CSS"
>    "Generic mode to edit Cascading Style Sheets (CSS).
>  
> @@ -1825,16 +1844,12 @@ css-ts-mode
>      ;; Tree-sitter specific setup.
>      (setq treesit-primary-parser (treesit-parser-create 'css))
>      (setq-local treesit-simple-indent-rules css--treesit-indent-rules)
> -    (setq-local treesit-defun-type-regexp "rule_set")
> +    (setq-local treesit-defun-type-regexp css--treesit-defun-type-regexp)
>      (setq-local treesit-defun-name-function #'css--treesit-defun-name)
>      (setq-local treesit-font-lock-settings css--treesit-settings)
> -    (setq-local treesit-font-lock-feature-list
> -                '((selector comment query keyword)
> -                  (property constant string)
> -                  (error variable function operator bracket)))
> -    (setq-local treesit-simple-imenu-settings
> -                `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
> -                    nil nil)))
> +    (setq-local treesit-font-lock-feature-list css--treesit-font-lock-feature-list)
> +    (setq-local treesit-simple-imenu-settings css--treesit-simple-imenu-settings)
> +
>      (treesit-major-mode-setup)
>  
>      (add-to-list 'auto-mode-alist '("\\.css\\'" . css-ts-mode))))
> diff --git a/lisp/textmodes/html-ts-mode.el b/lisp/textmodes/html-ts-mode.el
> index dad49b7ed4c..5700c2b406b 100644
> --- a/lisp/textmodes/html-ts-mode.el
> +++ b/lisp/textmodes/html-ts-mode.el
> @@ -87,6 +87,31 @@ html-ts-mode--font-lock-settings
>     `((attribute_name) @font-lock-variable-name-face))
>    "Tree-sitter font-lock settings for `html-ts-mode'.")
>  
> +(defvar html-ts-mode--treesit-things-settings
> +  `((html
> +     (sexp ,(regexp-opt '("element"
> +                          "text"
> +                          "attribute"
> +                          "value")))
> +     (list ,(regexp-opt '("element")) 'symbols)
> +     (sentence "tag")
> +     (text ,(regexp-opt '("comment" "text")))))
> +  "Settings for `treesit-thing-settings'.")
> +
> +(defvar html-ts-mode--treesit-font-lock-feature-list
> +  '((comment keyword definition)
> +    (property string)
> +    () ())
> +  "Settings for `treesit-font-lock-feature-list'.")
> +
> +(defvar html-ts-mode--treesit-simple-imenu-settings
> +  '(("Element" "\\`tag_name\\'" nil nil))
> +  "Settings for `treesit-simple-imenu'.")
> +
> +(defvar html-ts-mode--treesit-defun-type-regexp
> +  "element"
> +  "Settings for `treesit-defun-type-regexp'.")
> +
>  (defun html-ts-mode--defun-name (node)
>    "Return the defun name of NODE.
>  Return nil if there is no name or if NODE is not a defun node."
> @@ -107,30 +132,18 @@ html-ts-mode
>    (setq-local treesit-simple-indent-rules html-ts-mode--indent-rules)
>  
>    ;; Navigation.
> -  (setq-local treesit-defun-type-regexp "element")
> +  (setq-local treesit-defun-type-regexp html-ts-mode--treesit-defun-type-regexp)
>  
>    (setq-local treesit-defun-name-function #'html-ts-mode--defun-name)
>  
> -  (setq-local treesit-thing-settings
> -              `((html
> -                 (sexp ,(regexp-opt '("element"
> -                                      "text"
> -                                      "attribute"
> -                                      "value")))
> -                 (list ,(regexp-opt '("element")) 'symbols)
> -                 (sentence "tag")
> -                 (text ,(regexp-opt '("comment" "text"))))))
> +  (setq-local treesit-thing-settings html-ts-mode--treesit-things-settings)
>  
>    ;; Font-lock.
>    (setq-local treesit-font-lock-settings html-ts-mode--font-lock-settings)
> -  (setq-local treesit-font-lock-feature-list
> -              '((comment keyword definition)
> -                (property string)
> -                () ()))
> +  (setq-local treesit-font-lock-feature-list html-ts-mode--treesit-font-lock-feature-list)
>  
>    ;; Imenu.
> -  (setq-local treesit-simple-imenu-settings
> -              '(("Element" "\\`tag_name\\'" nil nil)))
> +  (setq-local treesit-simple-imenu-settings html-ts-mode--treesit-simple-imenu-settings)
>  
>    ;; Outline minor mode.
>    (setq-local treesit-outline-predicate "\\`element\\'")
> diff --git a/lisp/textmodes/mhtml-ts-mode.el b/lisp/textmodes/mhtml-ts-mode.el
> new file mode 100644
> index 00000000000..272d00a1ef6
> --- /dev/null
> +++ b/lisp/textmodes/mhtml-ts-mode.el
> @@ -0,0 +1,600 @@
> +;;; mhtml-ts-mode.el --- Major mode for HTML using tree-sitter -*- lexical-binding: t; -*-
> +
> +;; Copyright (C) 2024 Free Software Foundation, Inc.
> +
> +;; Author: Vincenzo Pupillo <[email-masked]>
> +;; Maintainer: Vincenzo Pupillo <[email-masked]>
> +;; Created: Nov 2024
> +;; Keywords: HTML languages hypermedia tree-sitter
> +
> +;; This file is part of GNU Emacs.
> +
> +;; GNU Emacs is free software: you can redistribute it and/or modify
> +;; it under the terms of the GNU General Public License as published by
> +;; the Free Software Foundation, either version 3 of the License, or
> +;; (at your option) any later version.
> +
> +;; GNU Emacs is distributed in the hope that it will be useful,
> +;; but WITHOUT ANY WARRANTY; without even the implied warranty of
> +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> +;; GNU General Public License for more details.
> +
> +;; You should have received a copy of the GNU General Public License
> +;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
> +
> +;;; Commentary:
> +;;
> +;; This package provides `mhtml-ts-mode' which is a major mode
> +;; for editing HTML files with embedded JavaScript and CSS.
> +;; Tree Sitter is used to parse each of these languages.
> +;;
> +;; Please note that this package requires `html-ts-mode', which
> +;; registers itself as the major mode for editing HTML.
> +;;
> +;; This package is compatible and has been tested with the following
> +;; tree-sitter grammars:
> +;; * https://github.com/tree-sitter/tree-sitter-html
> +;; * https://github.com/tree-sitter/tree-sitter-javascript
> +;; * https://github.com/tree-sitter/tree-sitter-jsdoc
> +;; * https://github.com/tree-sitter/tree-sitter-css
> +;;
> +;; Features
> +;;
> +;; * Indent
> +;; * Flymake
> +;; * IMenu
> +;; * Navigation
> +;; * Which-function
> +;; * Tree-sitter parser installation helper
> +
> +;;; Code:
> +
> +(require 'treesit)
> +(require 'html-ts-mode)
> +(require 'css-mode) ;; for embed css into html
> +(require 'js) ;; for embed javascript into html
> +
> +(eval-when-compile
> +  (require 'rx))
> +
> +;; This tells the byte-compiler where the functions are defined.
> +;; Is only needed when a file needs to be able to byte-compile
> +;; in a Emacs not built with tree-sitter library.
> +(treesit-declare-unavailable-functions)
> +
> +;; In a multi-language major mode can be useful to have an "installer" to
> +;; simplify the installation of the grammars supported by the major-mode.
> +(defvar mhtml-ts-mode--language-source-alist
> +  '((html . ("https://github.com/tree-sitter/tree-sitter-html"  "v0.23.2"))
> +    (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript" "v0.23.1"))
> +    (jsdoc . ("https://github.com/tree-sitter/tree-sitter-jsdoc" "v0.23.2"))
> +    (css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.23.1")))
> +  "Treesitter language parsers required by `mhtml-ts-mode'.
> +You can customize this variable if you want to stick to a specific
> +commit and/or use different parsers.")
> +
> +(defun mhtml-ts-mode-install-parsers ()
> +  "Install all the required treesitter parsers.
> +`mhtml-ts-mode--language-source-alist' defines which parsers to install."
> +  (interactive)
> +  (let ((treesit-language-source-alist mhtml-ts-mode--language-source-alist))
> +    (dolist (item mhtml-ts-mode--language-source-alist)
> +      (treesit-install-language-grammar (car item)))))
> +
> +;;; Custom variables
> +
> +(defgroup mhtml-ts-mode nil
> +  "Major mode for editing HTML files, based on `html-ts-mode'.
> +Works with JS and CSS and for that use `js-ts-mode' and `css-ts-mode'."
> +  :prefix "mhtml-ts-mode-"
> +  ;; :group 'languages
> +  :group 'html)
> +
> +(defcustom mhtml-ts-mode-js-css-indent-offset 2
> +  "JavaScript and CSS indent spaces related to the <script> and <style> HTML tags.
> +By default should have same value as `html-ts-mode-indent-offset'."
> +  :tag "HTML javascript or css indent offset"
> +  :version "31.1"
> +  :type 'integer
> +  :safe 'integerp)
> +
> +(defcustom mhtml-ts-mode-pretty-print-command
> +  ;; prefer tidy because it's used by sgml-mode
> +  (let ((executable nil))
> +    (cond ((setq executable (executable-find "tidy"))
> +           (format
> +            "%s --gnu-emacs yes --wrap 0 --indent-spaces %s -q -i -"
> +            executable html-ts-mode-indent-offset))
> +          ((setq executable (executable-find "xmllint"))
> +           (format "%s --html --quiet --format -" executable))
> +          (t "Install tidy, ore some other HTML pretty print tool, and set `mhtml-ts-mode-pretty-print-command'.")))
> +  "The command to pretty print the current HTML buffer."
> +  :type 'string
> +  :version "31.1")
> +
> +(defvar mhtml-ts-mode--js-css-indent-offset
> +  mhtml-ts-mode-js-css-indent-offset
> +  "Internal copy of `mhtml-ts-mode-js-css-indent-offset'.
> +The value changes, by `mhtml-ts-mode--tag-relative-indent-offset' according to
> +the value of `mhtml-ts-mode-tag-relative-indent'.")
> +
> +(defun mhtml-ts-mode--tag-relative-indent-offset (sym val)
> +  "Custom setter for `mhtml-ts-mode-tag-relative-indent'.
> +Apart from setting the default value of SYM to VAL, also change the
> +value of SYM in `mhtml-ts-mode' buffers to VAL.  SYM should be
> +`mhtml-ts-mode-tag-relative-indent', and VAL should be t, nil or
> +`ignore'.  When sym is `mhtml-ts-mode-tag-relative-indent' set the
> +value of `mhtml-ts-mode--js-css-indent-offset' to 0 if VAL is t,
> +otherwise to `mhtml-ts-mode-js-css-indent-offset'."
> +  (set-default sym val)
> +  (when (eq sym 'mhtml-ts-mode-tag-relative-indent)
> +    (setq
> +     mhtml-ts-mode--js-css-indent-offset
> +     (if (eq val t)
> +         mhtml-ts-mode-js-css-indent-offset
> +       0))))
> +
> +(defcustom mhtml-ts-mode-tag-relative-indent t
> +  "How <script> and <style> bodies are indented relative to the tag.
> +
> +When t, indentation looks like:
> +
> +  <script>
> +    code();
> +  </script>
> +
> +When nil, indentation of the tag body starts just below the
> +tag, like:
> +
> +  <script>
> +  code();
> +  </script>
> +
> +When `ignore', the tag body starts in the first column, like:
> +
> +  <script>
> +code();
> +  </script>"
> +  :type '(choice (const nil) (const t) (const ignore))
> +  :safe 'symbolp
> +  :set #'mhtml-ts-mode--tag-relative-indent-offset
> +  :version "31.1")
> +
> +(defcustom mhtml-ts-mode-css-fontify-colors t
> +  "Whether CSS colors should be fontified using the color as the background.
> +If non-nil, text representing a CSS color will be fontified
> +such that its background is the color itself.
> +Works like `css--fontify-region'."
> +  :tag "HTML colors the CSS properties values."
> +  :version "31.1"
> +  :type 'boolean
> +  :safe 'booleanp)
> +
> +(defvar mhtml-ts-mode-saved-pretty-print-command nil
> +  "The command last used to pretty print in this buffer.")
> +
> +(defun mhtml-ts-mode-pretty-print (command)
> +  "Prettify the current buffer.
> +Argument COMMAND The command to use."
> +  (interactive
> +   (list (read-string
> +          "Prettify command: "
> +          (or mhtml-ts-mode-saved-pretty-print-command
> +              (concat mhtml-ts-mode-pretty-print-command " ")))))
> +  (setq mhtml-ts-mode-saved-pretty-print-command command)
> +  (save-excursion
> +    (shell-command-on-region
> +     (point-min) (point-max)
> +     command (buffer-name) t
> +     "*mhtml-ts-mode-pretty-pretty-print-errors*" t)))
> +
> +(defun mhtml-ts-mode--switch-fill-defun (&rest arguments)
> +  "Switch between `fill-paragraph' and `prog-fill-reindent-defun'.
> +In an HTML region it calls `fill-paragraph' as does `html-ts-mode',
> +otherwise it calls `prog-fill-reindent-defun'.
> +Optional ARGUMENTS to to be passed to it."
> +  (interactive)
> +  (if (eq (treesit-language-at (point)) 'html)
> +      (funcall-interactively #'fill-paragraph arguments)
> +    (funcall-interactively #'prog-fill-reindent-defun arguments)))
> +
> +(defvar-keymap mhtml-ts-mode-map
> +  :doc "Keymap for `mhtml-ts-mode' buffers."
> +  :parent html-mode-map
> +  ;; `mhtml-ts-mode' derive from `html-ts-mode' so the keymap is the
> +  ;; same, we need to add some mapping from others languages.
> +  "C-c C-f" #'css-cycle-color-format
> +  "M-q" #'mhtml-ts-mode--switch-fill-defun)
> +
> +;; Place the CSS menu in the menu bar as well.
> +(easy-menu-define mhtml-ts-mode-menu mhtml-ts-mode-map
> +  "Menu bar for `mhtml-ts-mode'."
> +  css-mode--menu)
> +
> +;; Not used at the moment.
> +(defun mthml-ts-mode--js-language-at-point (point)
> +  "Return the language at POINT assuming the point is within a Javascript region."
> +  (let* ((node (treesit-node-at point 'javascript))
> +         (node-type (treesit-node-type node))
> +         (node-start (treesit-node-start node))
> +         (node-end (treesit-node-end node)))
> +    (if (not (treesit-ready-p 'jsdoc t))
> +        'javascript
> +      (if (equal node-type "comment")
> +          (save-excursion
> +            ;; (message "node start = %s , end = %s" node-start node-end)
> +            (goto-char node-start)
> +            (if (search-forward "/**" node-end t)
> +                'jsdoc
> +              'javascript))
> +        'javascript))))
> +
> +;; To enable some basic treesiter functionality, you should define
> +;; a function that recognizes which grammar is used at-point.
> +;; This function should be assigned to `treesit-language-at-point-function'
> +(defun mhtml-ts-mode--language-at-point (point)
> +  "Return the language at POINT assuming the point is within a HTML buffer."
> +  (let* ((node (treesit-node-at point 'html))
> +         (parent (treesit-node-parent node))
> +         (node-query (format "(%s (%s))"
> +                             (treesit-node-type parent)
> +                             (treesit-node-type node))))
> +    (cond
> +     ((equal "(script_element (raw_text))" node-query) 'javascript)
> +     ;; ((equal "(script_element (raw_text))" node-query) 'mthml-ts-mode--js-language-at-point)
> +     ((equal "(style_element (raw_text))" node-query) 'css)
> +     (t 'html))))
> +
> +;; Custom font-lock function that's used to apply color to css color
> +;; The signature of the function should be conforming to signature
> +;; QUERY-SPEC required by `treesit-font-lock-rules'.
> +(defun mhtml-ts-mode--colorize-css-value (node override start end &rest _)
> +  "Colorize CSS property value like `css--fontify-region'.
> +For NODE, OVERRIDE, START, and END, see `treesit-font-lock-rules'."
> +  (if (and mhtml-ts-mode-css-fontify-colors
> +           (string-equal "plain_value" (treesit-node-type node)))
> +      (let ((color (css--compute-color start (treesit-node-text node t))))
> +        (when color
> +          (with-silent-modifications
> +            (add-text-properties
> +             (treesit-node-start node) (treesit-node-end node)
> +             (list 'face (list :background color
> +                               :foreground (readable-foreground-color
> +                                            color)
> +                               :box '(:line-width -1)))))))
> +    (treesit-fontify-with-override
> +     (treesit-node-start node) (treesit-node-end node)
> +     'font-lock-variable-name-face
> +     override start end)))
> +
> +;; Embedded languages ​​should be indented according to the language
> +;; that embeds them.
> +;; This function signature complies with `treesit-simple-indent-rules'
> +;; ANCHOR.
> +(defun mhtml-ts-mode--js-css-tag-bol (_node _parent &rest _)
> +  "Find the first non-space characters of html tags <script> or <style>.
> +Return `line-beginning-position' when `treesit-node-at' is html, or
> +`mhtml-ts-mode-tag-relative-indent' is equal to ignore.
> +NODE and PARENT are ignored."
> +  (if (or (eq (treesit-language-at (point)) 'html)
> +          (eq mhtml-ts-mode-tag-relative-indent 'ignore))
> +      (line-beginning-position)
> +    ;; Ok, we are in js or css block.
> +    (save-excursion
> +      (re-search-backward "<script.*>\\|<style.*>" nil t))))
> +
> +;; Treesit supports 4 level of decoration, `treesit-font-lock-level'
> +;; define which level to use.  Major modes categorize their fontification
> +;; features, these categories are defined by `treesit-font-lock-rules' of
> +;; each major-mode using :feature keyword.
> +;; In a multiple language Major mode it's a good idea to provide, for each
> +;; level, the union of the :feature of the same level.
> +;; TODO: Since the feature-list is not defined per "parser" (like, for
> +;; example, the thing-settings), the same feature can appear in
> +;; different levels, so the appearance of a multiple main mode can be
> +;; different from the main mode used.  For e.g the feature "function" is
> +;; at level 4 for Javascript while it is at level 3 for CSS.
> +(defvar mhtml-ts-mode--treesit-font-lock-feature-list
> +  (treesit-merge-font-lock-feature-list
> +   html-ts-mode--treesit-font-lock-feature-list
> +   (treesit-merge-font-lock-feature-list
> +    js--treesit-font-lock-feature-list
> +    css--treesit-font-lock-feature-list))
> +  "Settings for `treesit-font-lock-feature-list'.")
> +
> +(defvar mhtml-ts-mode--treesit-font-lock-settings
> +  (append html-ts-mode--font-lock-settings
> +          js--treesit-font-lock-settings
> +          ;; Let's replace a css rule with a new one that adds color to
> +          ;; the css value.
> +          (treesit-replace-font-lock-feature-settings
> +           (treesit-font-lock-rules
> +            :language 'css
> +            :override t
> +            :feature 'variable
> +            '((plain_value) @font-lock-variable-name-face
> +              (plain_value) @mhtml-ts-mode--colorize-css-value))
> +           css--treesit-settings))
> +  "Settings for `treesit-font-lock-settings'.")
> +
> +(defvar mhtml-ts-mode--treesit-thing-settings
> +  ;; In addition to putting together the various definitions, we need to
> +  ;; add 'defun' which is used to support `imenu' and 'which-function'.
> +  (list
> +   ;; HTML thing settings
> +   (append
> +    (car html-ts-mode--treesit-things-settings)
> +    `((defun ,(regexp-opt (list html-ts-mode--treesit-defun-type-regexp)))))
> +   ;; Javascript thing settings
> +   (append
> +    (car js--treesit-thing-settings)
> +    `((defun ,js--treesit-defun-type-regexp)))
> +   ;; CSS thing settings
> +   `(css
> +     (defun ,(regexp-opt (list css--treesit-defun-type-regexp)))))
> +  "Settings for `treesit-thing-settings'.")
> +
> +(defvar mhtml-ts-mode--treesit-indent-rules
> +  (treesit--indent-rules-optimize
> +   (append html-ts-mode--indent-rules
> +           ;; Extended rules for js and css, to
> +           ;; indent appropriately when injected
> +           ;; into html
> +           (treesit-modify-indent-rules
> +            `((javascript ((parent-is "program")
> +                           mhtml-ts-mode--js-css-tag-bol
> +                           mhtml-ts-mode--js-css-indent-offset)))
> +            js--treesit-indent-rules
> +            :replace)
> +           (treesit-modify-indent-rules
> +            `((css ((parent-is "stylesheet")
> +                    mhtml-ts-mode--js-css-tag-bol
> +                    mhtml-ts-mode--js-css-indent-offset)))
> +            css--treesit-indent-rules 'prepend)
> +           :replace))
> +  "Settings for `treesit-simple-indent-rules'.")
> +
> +(defvar mhtml-ts-mode--treesit-aggregated-simple-imenu-settings
> +  `((html ,@html-ts-mode--treesit-simple-imenu-settings)
> +    (javascript ,@js--treesit-simple-imenu-settings)
> +    (css ,@css--treesit-simple-imenu-settings))
> +  "Settings for `treesit-simple-imenu'.")
> +
> +;; TODO: treesit-defun-type-regexp should have an aggregated version,
> +;; like treesit-aggregated-simple-imenu-settings. Otherwise we can't
> +;; reuse the regex defined in the major mode we use.
> +(defvar mhtml-ts-mode--treesit-defun-type-regexp
> +  (regexp-opt '("class_declaration"
> +                "method_definition"
> +                "function_declaration"
> +                "lexical_declaration"
> +                "element"
> +                "rule_set"))
> +  "Settings for `treesit-defun-type-regexp'.")
> +
> +;; In order to support `prettify-symbols-mode', just `append' the prettify
> +;; alist of all the languages. In our case only javascript defined this alist.
> +(defvar mhtml-ts-mode--prettify-symbols-alist js--prettify-symbols-alist
> +  "Alist of symbol prettifications for various supported languages.")
> +
> +;; In order to support `which-fuction-mode' we should define
> +;; a function that return the defun name.
> +;; In a multilingual treesit mode, this can be implemented simply by
> +;; calling language-specific functions.
> +(defun mhtml-ts-mode--defun-name (node)
> +  "Return the defun name of NODE.
> +Return nil if there is no name or if NODE is not a defun node."
> +    (let ((html-name (html-ts-mode--defun-name node))
> +          (js-name (js--treesit-defun-name node))
> +          (css-name (css--treesit-defun-name node)))
> +      (cond
> +       (html-name html-name)
> +       (js-name js-name)
> +       (css-name css-name))))
> +
> +;;; Flymake integration
> +
> +(defvar-local mhtml-ts-mode--flymake-process nil
> +  "Store the Flymake process.")
> +
> +(defun mhtml-ts-mode-flymake-mhtml (report-fn &rest _args)
> +  "MHTML backend for Flymake.
> +Calls REPORT-FN directly.  Requires tidy."
> +  (when (process-live-p mhtml-ts-mode--flymake-process)
> +    (kill-process mhtml-ts-mode--flymake-process))
> +  (let ((tidy (executable-find "tidy"))
> +        (source (current-buffer))
> +        (diagnostics-pattern (eval-when-compile
> +                               (rx bol
> +                                   "line " (group (+ num))    ;; :1 line
> +                                   " column " (group (+ num)) ;; :2 column
> +                                   " - " (group (+? nonl))    ;; :3 type
> +                                   ": " (group (+? nonl))     ;; :4 msg
> +                                   eol))))
> +    (if (not tidy)
> +        (error "Unable to find tidy command")
> +      (save-restriction
> +        (widen)
> +        (setq mhtml-ts-mode--flymake-process
> +              (make-process
> +               :name "mhtml-ts-mode-flymake"
> +               :noquery t
> +               :connection-type 'pipe
> +               :buffer (generate-new-buffer "*mhtml-ts-mode-flymake*")
> +               :command `(,tidy "--gnu-emacs" "yes" "-e" "-q")
> +               :sentinel
> +               (lambda (proc _event)
> +                 (when (eq 'exit (process-status proc))
> +                   (unwind-protect
> +                       (if (with-current-buffer source
> +                             (eq proc mhtml-ts-mode--flymake-process))
> +                           (with-current-buffer (process-buffer proc)
> +                             (goto-char (point-min))
> +                             (let (diags)
> +                               (while (search-forward-regexp diagnostics-pattern nil t)
> +                                 (let* ((pos
> +                                         (flymake-diag-region
> +                                          source
> +                                          (string-to-number (match-string 1))
> +                                          (string-to-number (match-string 2)))) ;; line and column
> +                                        (type (cond ((equal (match-string 3) "Warning") :warning)
> +                                                    ((equal (match-string 3) "Error") :error))) ;; type of message
> +                                        (msg (match-string 4))) ;; message
> +                                   (push (flymake-make-diagnostic source (car pos) (cdr pos) type msg)
> +                                         diags)))
> +                               (funcall report-fn diags)))
> +                         (flymake-log :warning "Canceling obsolete check %s" proc))
> +                     (kill-buffer (process-buffer proc)))))))
> +        (process-send-region mhtml-ts-mode--flymake-process (point-min) (point-max))
> +        (process-send-eof mhtml-ts-mode--flymake-process)))))
> +
> +(define-derived-mode mhtml-ts-mode html-ts-mode
> +  '("HTML+" (:eval (let ((lang (mhtml-ts-mode--language-at-point (point))))
> +                     (cond ((eq lang 'html) "")
> +                           ((eq lang 'javascript) "JS")
> +                           ((eq lang 'css) "CSS")))))
> +  "Major mode for editing HTML with embedded JavaScript and CSS.
> +Powered by tree-sitter."
> +  (if (not (and
> +            (treesit-ready-p 'html)
> +            (treesit-ready-p 'javascript)
> +            (treesit-ready-p 'css)))
> +      (error "Tree-sitter parsers for HTML isn't available.  You can
> +    install the parsers with M-x `mhtml-ts-mode-install-parsers'")
> +
> +    ;; When an language is embedded, you should initialize some variable
> +    ;; just like it's done in the original mode.
> +
> +    ;; Comment.
> +    ;; indenting settings for js-ts-mode.
> +    (c-ts-common-comment-setup)
> +    (setq-local comment-multi-line t)
> +
> +    ;; Font-lock.
> +
> +    ;; There are two ways to handle embedded code:
> +    ;; 1. Use a single parser for all the embedded code in the buffer. In
> +    ;; this case, the embedded code blocks are concatenated together and are
> +    ;; seen as a single continuous document to the parser.
> +    ;; 2. Each embedded code block gets its own parser. Each parser only sees
> +    ;; that particular code block.
> +
> +    ;; If you go with 2 for a language, the local parsers are created and
> +    ;; destroyed automatically by Emacs. So don't create a global parser for
> +    ;; that embedded language here.
> +
> +    ;; Create the parsers, only the global ones.
> +    ;; jsdoc is a local parser, don't create a parser for it.
> +    (treesit-parser-create 'css)
> +    (treesit-parser-create 'javascript)
> +
> +    ;; Multi-language modes must set the  primary parser.
> +    (setq-local treesit-primary-parser (treesit-parser-create 'html))
> +
> +    (setq-local treesit-range-settings
> +                (treesit-range-rules
> +                 :embed 'javascript
> +                 :host 'html
> +                 '((script_element
> +                    (start_tag (tag_name))
> +                    (raw_text) @cap))
> +
> +                 ;; Another rule could be added that when it matches an
> +                 ;; attribute_value that has as its parent an
> +                 ;; attribute_name "style" it captures it and then
> +                 ;; passes it to the css parser.
> +                 :embed 'css
> +                 :host 'html
> +                 '((style_element
> +                    (start_tag (tag_name))
> +                    (raw_text) @cap))))
> +
> +    ;; jsdoc is not mandatory for js-ts-mode, so we respect this by
> +    ;; adding jsdoc range rules only when jsdoc is available.
> +    (when (treesit-ready-p 'jsdoc t)
> +      (setq-local treesit-range-settings
> +                  (append treesit-range-settings
> +                          (treesit-range-rules
> +                           :embed 'jsdoc
> +                           :host 'javascript
> +                           :local t
> +                           `(((comment) @cap
> +                              (:match ,js--treesit-jsdoc-beginning-regexp @cap))))))
> +      (setq-local c-ts-common--comment-regexp
> +                  js--treesit-jsdoc-comment-regexp))
> +
> +
> +    ;; Many treesit fuctions need to know the language at-point.
> +    ;; So you should define such a function.
> +    (setq-local treesit-language-at-point-function #'mhtml-ts-mode--language-at-point)
> +    (setq-local prettify-symbols-alist mhtml-ts-mode--prettify-symbols-alist)
> +
> +    ;; Indent.
> +
> +    ;; Since `mhtml-ts-mode' inherits indentation rules from `html-ts-mode', `js-ts-mode'
> +    ;; and `css-ts-mode', if you want to change the offset you have to act on the
> +    ;; *-offset variables defined for those languages.
> +
> +    ;; JavaScript and CSS must be indented relative to their code block.
> +    ;; This is done by inserting a special rule before the normal
> +    ;; indentation rules of these languages.
> +    ;; The value of `mhtml-ts-mode--js-css-indent-offset' changes based on
> +    ;; `mhtml-ts-mode-tag-relative-indent' and can be used to indent
> +    ;; JavaScript and CSS code relative to the HTML that contains them,
> +    ;; just like in mhtml-mode.
> +    (setq-local treesit-simple-indent-rules mhtml-ts-mode--treesit-indent-rules)
> +
> +    ;; Navigation.
> +
> +    ;; This is for which-function-mode.
> +    ;; Since mhtml-ts-mode is derived from html-ts-mode, which sets
> +    ;; the value of `treesit-defun-type-regexp', you have to reset it to nil
> +    ;; otherwise `imenu' and `which-function-mode' will not work.
> +    (setq-local treesit-defun-type-regexp nil)
> +
> +    ;; This is for finding defun name, it's used by IMenu as default
> +    ;; function no specific functions are defined.
> +    (setq-local treesit-defun-name-function #'mhtml-ts-mode--defun-name)
> +
> +    ;; Define what are 'thing' for treesit.
> +    ;; 'Thing' is a symbol representing the thing, like `defun', `sexp', or
> +    ;; `sentence'.
> +    ;; As an alternative, if you want just defun, you can define a `treesit-defun-type-regexp'.
> +    (setq-local treesit-thing-settings mhtml-ts-mode--treesit-thing-settings)
> +
> +    ;; Font-lock.
> +
> +    ;; In a multi-language scenario, font lock settings are usually a
> +    ;; concatenation of language rules. As you can see, it is possible
> +    ;; to extend/modify the default rule or use a different set of
> +    ;; rules. See `php-ts-mode--custom-html-font-lock-settings' for more
> +    ;; advanced usage.
> +    (setq-local treesit-font-lock-settings mhtml-ts-mode--treesit-font-lock-settings)
> +
> +    ;; Tells treesit the list of features to fontify.
> +    (setq-local treesit-font-lock-feature-list mhtml-ts-mode--treesit-font-lock-feature-list)
> +
> +    ;; Imenu
> +
> +    ;; Setup Imenu: if no function is specified, try to find an object
> +    ;; using `treesit-defun-name-function'.
> +    (setq-local treesit-aggregated-simple-imenu-settings
> +                mhtml-ts-mode--treesit-aggregated-simple-imenu-settings)
> +
> +    (treesit-major-mode-setup)
> +
> +    ;; This is sort of a prog-mode as well as a text mode.
> +    (run-mode-hooks 'prog-mode-hook)
> +
> +    ;; Flymake
> +    (add-hook 'flymake-diagnostic-functions #'mhtml-ts-mode-flymake-mhtml nil 'local)))
> +
> +;; Add nome extra parents.
> +(derived-mode-add-parents 'mhtml-ts-mode '(css-mode js-mode))
> +
> +(when (and (treesit-ready-p 'html) (treesit-ready-p 'javascript) (treesit-ready-p 'css))
> +  (add-to-list
> +   'auto-mode-alist '("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" . mhtml-ts-mode)))
> +
> +(provide 'mhtml-ts-mode)
> +;;; mhtml-ts-mode.el ends here
> diff --git a/lisp/treesit.el b/lisp/treesit.el
> index 8d86d142e3f..00b7d266e74 100644
> --- a/lisp/treesit.el
> +++ b/lisp/treesit.el
> @@ -1297,6 +1297,40 @@ treesit-font-lock-recompute-features
>                         ((memq feature remove-list) nil)
>                         (t current-value))))))
>  
> +(defun treesit-merge-font-lock-feature-list (features-list-1 features-list-2)
> +  "Merge two tree-sitter font lock feature lists.
> +Returns a new font lock feature list with no duplicates in the same level.
> +It can be used to merge font lock feature lists in a multi-language major mode.
> +FEATURES-LIST-1 and FEATURES-LIST-2 are list of lists of feature symbols."
> +    (let ((result nil)
> +	(features-1 (car features-list-1))
> +	(features-2 (car features-list-2)))
> +    (while (or features-1 features-2)
> +      (cond
> +       ((and features-1 (not features-2)) (push features-1 result))
> +       ((and (not features-1) features-2) (push features-2 result))
> +       ((and features-1 features-2) (push (cl-union features-1 features-2) result)))
> +      (setq features-list-1 (cdr features-list-1)
> +	    features-list-2 (cdr features-list-2)
> +	    features-1 (car features-list-1)
> +            features-2 (car features-list-2)))
> +    (nreverse result)))
> +
> +(defun treesit-replace-font-lock-feature-settings (new-settings settings)
> +  "Replaces :feature in SETTINGS with :feature from NEW-SETTINGS.
> +Both SETTINGS and NEW-SETTINGS must be a value suitable for
> +`treesit-font-lock-settings'.
> +Return a value suitable for `treesit-font-lock-settings'"
> +  (let ((result nil))
> +    (dolist (new-setting new-settings)
> +      (let ((new-feature (treesit-font-lock-setting-feature new-setting)))
> +	(dolist (setting settings)
> +	  (let ((feature (treesit-font-lock-setting-feature setting)))
> +	    (if (eq new-feature feature)
> +		(push new-setting result)
> +	      (push setting result))))))
> +    (nreverse result)))
> +
>  (defun treesit-add-font-lock-rules (rules &optional how feature)
>    "Add font-lock RULES to the current buffer.
>  
> @@ -2401,6 +2435,37 @@ treesit--indent-rules-optimize
>                              offset)))))
>               (cons lang (mapcar #'optimize-rule indent-rules)))))
>  
> +(defun treesit-modify-indent-rules (new-rules rules &optional how)
> +  "Modify RULES using NEW-RULES.
> +As default replace rules with the same anchor.
> +When HOW is :prepend NEW-RULES are prepend to RULES, when
> +:append NEW-RULES are appended to RULES, when :replace (the default)
> +NEW-RULES replace rule in RULES which the same anchor."
> +  (let ((n-lang (car (car new-rules)))
> +	(lang (car (car rules))))
> +    (when (not (eq n-lang lang))
> +      (error "The language must be the same"))
> +    (let* ((nr (cdr (car new-rules)))
> +           (r (cdr (car rules)))
> +           (tmp nil)
> +           (result
> +            (cond
> +             ((eq how :prepend)
> +	      (append nr r))
> +             ((eq how :append)
> +              (append r nr))
> +             ((or (eq how :replace) t)
> +              (nreverse
> +               (progn
> +                 (dolist (new-rule nr)
> +	           (dolist (rule r)
> +	             (if (equal (nth 0 new-rule) (nth 0 rule))
> +		         (push new-rule tmp)
> +		       (push rule tmp))))
> +                 tmp))))))
> +      (push lang result)
> +      `(,result))))
> +
>  ;;; Search
>  
>  (defun treesit-search-forward-goto
> -- 
> 2.48.1
>