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

From: Vincenzo Pupillo <[email-masked]>
Date: Sun, 09 Feb 2025 15:35:44 +0100

Ciao Eli,
This attached patch is the third version. Adapted to support the latest 
changes introduced in treesit.el and js.el (as described in bug#75456).
@Jury, @Yuan what do you think about this version?

Thanks.

Vincenzo

In data sabato 8 febbraio 2025 10:01:48 Ora standard dell’Europa centrale, Eli 
Zaretskii ha scritto:
> 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

>From 70fa27e4910f10e5b371954f44717ca2fd81d2e1 Mon Sep 17 00:00:00 2001
From: Vincenzo Pupillo <[email-masked]>
Date: Sun, 9 Feb 2025 15:19:21 +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                       | 29 ++++++++++
 lisp/progmodes/js.el           | 72 +++++++++++++++----------
 lisp/textmodes/css-mode.el     | 47 ++++++++++------
 lisp/textmodes/html-ts-mode.el | 45 ++++++++++------
 lisp/treesit.el                | 99 ++++++++++++++++++++++++++++++++++
 5 files changed, 233 insertions(+), 59 deletions(-)

diff --git a/etc/NEWS b/etc/NEWS
index 4ab24df14d2..41927d1b44d 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1124,6 +1124,12 @@ runs its body, and removes the current buffer from
 
 
 * 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
@@ -1268,6 +1274,29 @@ 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 3168395acf1..f2acf9f40d6 100644
--- a/lisp/progmodes/js.el
+++ b/lisp/progmodes/js.el
@@ -3920,6 +3920,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.
@@ -3951,29 +3989,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
@@ -3983,17 +4007,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/treesit.el b/lisp/treesit.el
index 9e3c75519f9..ad3b8843407 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -1317,6 +1317,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.
 
@@ -2464,6 +2498,71 @@ treesit-add-simple-indent-rules
               (append rules existing-rules)))))
     (setf (alist-get language treesit-simple-indent-rules) new-rules)))
 
+(defun treesit-modify-indent-rules (lang new-rules rules &optional how)
+  "Modify a copy of 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."
+  (cond
+   ((not (alist-get lang rules))
+    (error "No rules for language %s in RULES" lang))
+   ((not (alist-get lang new-rules))
+    (error "No rules for language %s in NEW-RULES" lang))
+   (t (let* ((copy-of-rules (copy-tree js--treesit-indent-rules))
+	     (lang-rules (alist-get lang copy-of-rules))
+	     (lang-new-rules (alist-get lang new-rules)))
+	(cond
+	 ((eq how :prepend)
+	  (setf (alist-get lang copy-of-rules)
+		(append lang-new-rules lang-rules)))
+	 ((eq how :append)
+	  (setf (alist-get lang copy-of-rules)
+		(append lang-rules lang-new-rules)))
+	 ((or (eq how :replace) t)
+	  (let ((tail-new-rules lang-new-rules)
+		(tail-rules lang-rules)
+		(new-rule nil)
+		(rule nil))
+	    (while (setq new-rule (car tail-new-rules))
+	      (while (setq rule (car tail-rules))
+		(when (equal (nth 0 new-rule) (nth 0 rule))
+		  (setf (car tail-rules) new-rule))
+		(setq tail-rules (cdr tail-rules)))
+	      (setq tail-new-rules (cdr tail-new-rules))))))
+	copy-of-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