aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVincenzo Pupillo2025-02-14 18:38:51 +0100
committerJuri Linkov2025-02-17 09:22:22 +0200
commit05a96fd39809f11a3820e2164b23ebf9df192b13 (patch)
tree2078ff791d548605d552aeca32dc002f982c01a3
parent0e4d08f3dc7c76008da9cd912933a931c6e3e8d9 (diff)
downloademacs-05a96fd39809f11a3820e2164b23ebf9df192b13.tar.gz
emacs-05a96fd39809f11a3820e2164b23ebf9df192b13.zip
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.
-rw-r--r--etc/NEWS29
-rw-r--r--lisp/progmodes/js.el72
-rw-r--r--lisp/textmodes/css-mode.el47
-rw-r--r--lisp/textmodes/html-ts-mode.el54
-rw-r--r--lisp/textmodes/mhtml-ts-mode.el594
-rw-r--r--lisp/treesit.el68
6 files changed, 801 insertions, 63 deletions
diff --git a/etc/NEWS b/etc/NEWS
index a0d0f8b9818..a0d81608103 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -1217,6 +1217,12 @@ runs its body, and removes the current buffer from
1217 1217
1218 1218
1219* New Modes and Packages in Emacs 31.1 1219* New Modes and Packages in Emacs 31.1
1220** New major modes based on the tree-sitter library
1221
1222*** New major mode 'mhtml-ts-mode'.
1223An optional major mode based on the tree-sitter library for editing html
1224files. This mode handles indentation, fontification, and commenting for
1225embedded JavaScript and CSS.
1220 1226
1221 1227
1222* Incompatible Lisp Changes in Emacs 31.1 1228* Incompatible Lisp Changes in Emacs 31.1
@@ -1371,6 +1377,29 @@ variable 'treesit-language-display-name-alist' holds the translations of
1371language symbols where that translation is not trivial. 1377language symbols where that translation is not trivial.
1372 1378
1373+++ 1379+++
1380++++
1381*** New function 'treesit-merge-font-lock-feature-list'.
1382This function the merge two tree-sitter font lock feature lists.
1383Returns a new font lock feature list with no duplicates in the same level.
1384It can be used to merge font lock feature lists in a multi-language major mode.
1385
1386+++
1387*** New function 'treesit-replace-font-lock-feature-settings'.
1388Given two treesit-font-lock-settings replaces the feature in the second
1389font-lock-settings with the same feature in the first
1390font-lock-settings. In a multi-linguage major mode it is sometimes
1391necessary to replace features from one of the major modes, with others
1392that are better suited to the new multilingual context.
1393
1394+++
1395*** New function 'treesit-modify-indent-rules'.
1396Given two treesit ident rules, it replaces, adds, or prepends the new
1397rules to the old ones, then returns a new treesit indent rules.
1398In a multi-linguage major mode it is sometimes necessary to modify rules
1399from one of the major modes, with others that are better suited to the
1400new multilingual context.
1401
1402+++
1374*** New command 'treesit-explore'. 1403*** New command 'treesit-explore'.
1375This command replaces 'treesit-explore-mode'. It turns on 1404This command replaces 'treesit-explore-mode'. It turns on
1376'treesit-explore-mode' if it’s not on, and pops up the explorer buffer 1405'treesit-explore-mode' if it’s not on, and pops up the explorer buffer
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 @@ See `treesit-thing-settings' for more information.")
3920(defvar js--treesit-jsdoc-beginning-regexp (rx bos "/**") 3920(defvar js--treesit-jsdoc-beginning-regexp (rx bos "/**")
3921 "Regular expression matching the beginning of a jsdoc block comment.") 3921 "Regular expression matching the beginning of a jsdoc block comment.")
3922 3922
3923(defvar js--treesit-thing-settings
3924 `((javascript
3925 (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
3926 (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
3927 (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
3928 (text ,(js--regexp-opt-symbol '("comment"
3929 "string_fragment")))))
3930 "Settings for `treesit-thing-settings'.")
3931
3932(defvar js--treesit-font-lock-feature-list
3933 '(( comment document definition)
3934 ( keyword string)
3935 ( assignment constant escape-sequence jsx number
3936 pattern string-interpolation)
3937 ( bracket delimiter function operator property))
3938 "Settings for `treesit-font-lock-feature-list'.")
3939
3940(defvar js--treesit-simple-imenu-settings
3941 `(("Function" "\\`function_declaration\\'" nil nil)
3942 ("Variable" "\\`lexical_declaration\\'"
3943 js--treesit-valid-imenu-entry nil)
3944 ("Class" ,(rx bos (or "class_declaration"
3945 "method_definition")
3946 eos)
3947 nil nil))
3948 "Settings for `treesit-simple-imenu'.")
3949
3950(defvar js--treesit-defun-type-regexp
3951 (rx (or "class_declaration"
3952 "method_definition"
3953 "function_declaration"
3954 "lexical_declaration"))
3955 "Settings for `treesit-defun-type-regexp'.")
3956
3957(defvar js--treesit-jsdoc-comment-regexp
3958 (rx (or "comment" "line_comment" "block_comment" "description"))
3959 "Regexp for `c-ts-common--comment-regexp'.")
3960
3923;;;###autoload 3961;;;###autoload
3924(define-derived-mode js-ts-mode js-base-mode "JavaScript" 3962(define-derived-mode js-ts-mode js-base-mode "JavaScript"
3925 "Major mode for editing JavaScript. 3963 "Major mode for editing JavaScript.
@@ -3951,29 +3989,15 @@ See `treesit-thing-settings' for more information.")
3951 ;; Indent. 3989 ;; Indent.
3952 (setq-local treesit-simple-indent-rules js--treesit-indent-rules) 3990 (setq-local treesit-simple-indent-rules js--treesit-indent-rules)
3953 ;; Navigation. 3991 ;; Navigation.
3954 (setq-local treesit-defun-type-regexp 3992 (setq-local treesit-defun-type-regexp js--treesit-defun-type-regexp)
3955 (rx (or "class_declaration" 3993
3956 "method_definition"
3957 "function_declaration"
3958 "lexical_declaration")))
3959 (setq-local treesit-defun-name-function #'js--treesit-defun-name) 3994 (setq-local treesit-defun-name-function #'js--treesit-defun-name)
3960 3995
3961 (setq-local treesit-thing-settings 3996 (setq-local treesit-thing-settings js--treesit-thing-settings)
3962 `((javascript
3963 (sexp ,(js--regexp-opt-symbol js--treesit-sexp-nodes))
3964 (list ,(js--regexp-opt-symbol js--treesit-list-nodes))
3965 (sentence ,(js--regexp-opt-symbol js--treesit-sentence-nodes))
3966 (text ,(js--regexp-opt-symbol '("comment"
3967 "string_fragment"))))))
3968 3997
3969 ;; Fontification. 3998 ;; Fontification.
3970 (setq-local treesit-font-lock-settings js--treesit-font-lock-settings) 3999 (setq-local treesit-font-lock-settings js--treesit-font-lock-settings)
3971 (setq-local treesit-font-lock-feature-list 4000 (setq-local treesit-font-lock-feature-list js--treesit-font-lock-feature-list)
3972 '(( comment document definition)
3973 ( keyword string)
3974 ( assignment constant escape-sequence jsx number
3975 pattern string-interpolation)
3976 ( bracket delimiter function operator property)))
3977 4001
3978 (when (treesit-ready-p 'jsdoc t) 4002 (when (treesit-ready-p 'jsdoc t)
3979 (setq-local treesit-range-settings 4003 (setq-local treesit-range-settings
@@ -3983,17 +4007,11 @@ See `treesit-thing-settings' for more information.")
3983 :local t 4007 :local t
3984 `(((comment) @capture (:match ,js--treesit-jsdoc-beginning-regexp @capture))))) 4008 `(((comment) @capture (:match ,js--treesit-jsdoc-beginning-regexp @capture)))))
3985 4009
3986 (setq c-ts-common--comment-regexp (rx (or "comment" "line_comment" "block_comment" "description")))) 4010 (setq c-ts-common--comment-regexp js--treesit-jsdoc-comment-regexp))
3987 4011
3988 ;; Imenu 4012 ;; Imenu
3989 (setq-local treesit-simple-imenu-settings 4013 (setq-local treesit-simple-imenu-settings js--treesit-simple-imenu-settings)
3990 `(("Function" "\\`function_declaration\\'" nil nil) 4014
3991 ("Variable" "\\`lexical_declaration\\'"
3992 js--treesit-valid-imenu-entry nil)
3993 ("Class" ,(rx bos (or "class_declaration"
3994 "method_definition")
3995 eos)
3996 nil nil)))
3997 (treesit-major-mode-setup) 4015 (treesit-major-mode-setup)
3998 4016
3999 (add-to-list 'auto-mode-alist 4017 (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 @@ cannot be completed sensibly: `custom-ident',
893 (modify-syntax-entry ?? "." st) 893 (modify-syntax-entry ?? "." st)
894 st)) 894 st))
895 895
896(defvar-keymap css-mode-map 896(defvar css-mode--menu
897 :doc "Keymap used in `css-mode'."
898 "<remap> <info-lookup-symbol>" #'css-lookup-symbol
899 ;; `info-complete-symbol' is not used.
900 "<remap> <complete-symbol>" #'completion-at-point
901 "C-c C-f" #'css-cycle-color-format
902 :menu
903 '("CSS" 897 '("CSS"
904 :help "CSS-specific features" 898 :help "CSS-specific features"
905 ["Reformat block" fill-paragraph 899 ["Reformat block" fill-paragraph
@@ -910,7 +904,17 @@ cannot be completed sensibly: `custom-ident',
910 ["Describe symbol" css-lookup-symbol 904 ["Describe symbol" css-lookup-symbol
911 :help "Display documentation for a CSS symbol"] 905 :help "Display documentation for a CSS symbol"]
912 ["Complete symbol" completion-at-point 906 ["Complete symbol" completion-at-point
913 :help "Complete symbol before point"])) 907 :help "Complete symbol before point"])
908 "Menu bar for `css-mode'")
909
910(defvar-keymap css-mode-map
911 :doc "Keymap used in `css-mode'."
912 "<remap> <info-lookup-symbol>" #'css-lookup-symbol
913 ;; `info-complete-symbol' is not used.
914 "<remap> <complete-symbol>" #'completion-at-point
915 "C-c C-f" #'css-cycle-color-format
916 :menu
917 css-mode--menu)
914 918
915(eval-and-compile 919(eval-and-compile
916 (defconst css--uri-re 920 (defconst css--uri-re
@@ -1771,6 +1775,21 @@ rgb()/rgba()."
1771 (replace-regexp-in-string "[\n ]+" " " s))) 1775 (replace-regexp-in-string "[\n ]+" " " s)))
1772 res))))))) 1776 res)))))))
1773 1777
1778(defvar css--treesit-font-lock-feature-list
1779 '((selector comment query keyword)
1780 (property constant string)
1781 (error variable function operator bracket))
1782 "Settings for `treesit-font-lock-feature-list'.")
1783
1784(defvar css--treesit-simple-imenu-settings
1785 `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
1786 nil nil))
1787 "Settings for `treesit-simple-imenu'.")
1788
1789(defvar css--treesit-defun-type-regexp
1790 "rule_set"
1791 "Settings for `treesit-defun-type-regexp'.")
1792
1774(define-derived-mode css-base-mode prog-mode "CSS" 1793(define-derived-mode css-base-mode prog-mode "CSS"
1775 "Generic mode to edit Cascading Style Sheets (CSS). 1794 "Generic mode to edit Cascading Style Sheets (CSS).
1776 1795
@@ -1825,16 +1844,12 @@ can also be used to fill comments.
1825 ;; Tree-sitter specific setup. 1844 ;; Tree-sitter specific setup.
1826 (setq treesit-primary-parser (treesit-parser-create 'css)) 1845 (setq treesit-primary-parser (treesit-parser-create 'css))
1827 (setq-local treesit-simple-indent-rules css--treesit-indent-rules) 1846 (setq-local treesit-simple-indent-rules css--treesit-indent-rules)
1828 (setq-local treesit-defun-type-regexp "rule_set") 1847 (setq-local treesit-defun-type-regexp css--treesit-defun-type-regexp)
1829 (setq-local treesit-defun-name-function #'css--treesit-defun-name) 1848 (setq-local treesit-defun-name-function #'css--treesit-defun-name)
1830 (setq-local treesit-font-lock-settings css--treesit-settings) 1849 (setq-local treesit-font-lock-settings css--treesit-settings)
1831 (setq-local treesit-font-lock-feature-list 1850 (setq-local treesit-font-lock-feature-list css--treesit-font-lock-feature-list)
1832 '((selector comment query keyword) 1851 (setq-local treesit-simple-imenu-settings css--treesit-simple-imenu-settings)
1833 (property constant string) 1852
1834 (error variable function operator bracket)))
1835 (setq-local treesit-simple-imenu-settings
1836 `(( nil ,(rx bos (or "rule_set" "media_statement") eos)
1837 nil nil)))
1838 (treesit-major-mode-setup) 1853 (treesit-major-mode-setup)
1839 1854
1840 (add-to-list 'auto-mode-alist '("\\.css\\'" . css-ts-mode)))) 1855 (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 0f07fbedeed..26efe1be726 100644
--- a/lisp/textmodes/html-ts-mode.el
+++ b/lisp/textmodes/html-ts-mode.el
@@ -88,6 +88,35 @@
88 `((attribute_name) @font-lock-variable-name-face)) 88 `((attribute_name) @font-lock-variable-name-face))
89 "Tree-sitter font-lock settings for `html-ts-mode'.") 89 "Tree-sitter font-lock settings for `html-ts-mode'.")
90 90
91(defvar html-ts-mode--treesit-things-settings
92 `((html
93 (sexp ,(regexp-opt '("element"
94 "text"
95 "attribute"
96 "value")))
97 (list ,(rx (or
98 ;; Also match script_element and style_element
99 "element"
100 ;; HTML comments have the element syntax
101 "comment")))
102 (sentence ,(rx (and bos (or "tag_name" "attribute") eos)))
103 (text ,(regexp-opt '("comment" "text")))))
104 "Settings for `treesit-thing-settings'.")
105
106(defvar html-ts-mode--treesit-font-lock-feature-list
107 '((comment keyword definition)
108 (property string)
109 () ())
110 "Settings for `treesit-font-lock-feature-list'.")
111
112(defvar html-ts-mode--treesit-simple-imenu-settings
113 '((nil "element" nil nil))
114 "Settings for `treesit-simple-imenu'.")
115
116(defvar html-ts-mode--treesit-defun-type-regexp
117 "element"
118 "Settings for `treesit-defun-type-regexp'.")
119
91(defun html-ts-mode--defun-name (node) 120(defun html-ts-mode--defun-name (node)
92 "Return the defun name of NODE. 121 "Return the defun name of NODE.
93Return nil if there is no name or if NODE is not a defun node." 122Return nil if there is no name or if NODE is not a defun node."
@@ -120,33 +149,18 @@ Return nil if there is no name or if NODE is not a defun node."
120 (setq-local treesit-simple-indent-rules html-ts-mode--indent-rules) 149 (setq-local treesit-simple-indent-rules html-ts-mode--indent-rules)
121 150
122 ;; Navigation. 151 ;; Navigation.
123 (setq-local treesit-defun-type-regexp "element") 152 (setq-local treesit-defun-type-regexp html-ts-mode--treesit-defun-type-regexp)
153
124 (setq-local treesit-defun-name-function #'html-ts-mode--defun-name) 154 (setq-local treesit-defun-name-function #'html-ts-mode--defun-name)
125 155
126 (setq-local treesit-thing-settings 156 (setq-local treesit-thing-settings html-ts-mode--treesit-things-settings)
127 `((html
128 (sexp ,(regexp-opt '("element"
129 "text"
130 "attribute"
131 "value")))
132 (list ,(rx (or
133 ;; Also match script_element and style_element
134 "element"
135 ;; HTML comments have the element syntax
136 "comment")))
137 (sentence ,(rx (and bos (or "tag_name" "attribute") eos)))
138 (text ,(regexp-opt '("comment" "text"))))))
139 157
140 ;; Font-lock. 158 ;; Font-lock.
141 (setq-local treesit-font-lock-settings html-ts-mode--font-lock-settings) 159 (setq-local treesit-font-lock-settings html-ts-mode--font-lock-settings)
142 (setq-local treesit-font-lock-feature-list 160 (setq-local treesit-font-lock-feature-list html-ts-mode--treesit-font-lock-feature-list)
143 '((comment keyword definition)
144 (property string)
145 () ()))
146 161
147 ;; Imenu. 162 ;; Imenu.
148 (setq-local treesit-simple-imenu-settings 163 (setq-local treesit-simple-imenu-settings html-ts-mode--treesit-simple-imenu-settings)
149 '((nil "element" nil nil)))
150 164
151 ;; Outline minor mode. 165 ;; Outline minor mode.
152 (setq-local treesit-outline-predicate #'html-ts-mode--outline-predicate) 166 (setq-local treesit-outline-predicate #'html-ts-mode--outline-predicate)
diff --git a/lisp/textmodes/mhtml-ts-mode.el b/lisp/textmodes/mhtml-ts-mode.el
new file mode 100644
index 00000000000..9be1a14c257
--- /dev/null
+++ b/lisp/textmodes/mhtml-ts-mode.el
@@ -0,0 +1,594 @@
1;;; mhtml-ts-mode.el --- Major mode for HTML using tree-sitter -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2024 Free Software Foundation, Inc.
4
5;; Author: Vincenzo Pupillo <v.pupillo@gmail.com>
6;; Maintainer: Vincenzo Pupillo <v.pupillo@gmail.com>
7;; Created: Nov 2024
8;; Keywords: HTML languages hypermedia tree-sitter
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software: you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
24
25;;; Commentary:
26;;
27;; This package provides `mhtml-ts-mode' which is a major mode
28;; for editing HTML files with embedded JavaScript and CSS.
29;; Tree Sitter is used to parse each of these languages.
30;;
31;; Please note that this package requires `html-ts-mode', which
32;; registers itself as the major mode for editing HTML.
33;;
34;; This package is compatible and has been tested with the following
35;; tree-sitter grammars:
36;; * https://github.com/tree-sitter/tree-sitter-html
37;; * https://github.com/tree-sitter/tree-sitter-javascript
38;; * https://github.com/tree-sitter/tree-sitter-jsdoc
39;; * https://github.com/tree-sitter/tree-sitter-css
40;;
41;; Features
42;;
43;; * Indent
44;; * Flymake
45;; * IMenu
46;; * Navigation
47;; * Which-function
48;; * Tree-sitter parser installation helper
49
50;;; Code:
51
52(require 'treesit)
53(require 'html-ts-mode)
54(require 'css-mode) ;; for embed css into html
55(require 'js) ;; for embed javascript into html
56
57(eval-when-compile
58 (require 'rx))
59
60;; This tells the byte-compiler where the functions are defined.
61;; Is only needed when a file needs to be able to byte-compile
62;; in a Emacs not built with tree-sitter library.
63(treesit-declare-unavailable-functions)
64
65;; In a multi-language major mode can be useful to have an "installer" to
66;; simplify the installation of the grammars supported by the major-mode.
67(defvar mhtml-ts-mode--language-source-alist
68 '((html . ("https://github.com/tree-sitter/tree-sitter-html" "v0.23.2"))
69 (javascript . ("https://github.com/tree-sitter/tree-sitter-javascript" "v0.23.1"))
70 (jsdoc . ("https://github.com/tree-sitter/tree-sitter-jsdoc" "v0.23.2"))
71 (css . ("https://github.com/tree-sitter/tree-sitter-css" "v0.23.1")))
72 "Treesitter language parsers required by `mhtml-ts-mode'.
73You can customize this variable if you want to stick to a specific
74commit and/or use different parsers.")
75
76(defun mhtml-ts-mode-install-parsers ()
77 "Install all the required treesitter parsers.
78`mhtml-ts-mode--language-source-alist' defines which parsers to install."
79 (interactive)
80 (let ((treesit-language-source-alist mhtml-ts-mode--language-source-alist))
81 (dolist (item mhtml-ts-mode--language-source-alist)
82 (treesit-install-language-grammar (car item)))))
83
84;;; Custom variables
85
86(defgroup mhtml-ts-mode nil
87 "Major mode for editing HTML files, based on `html-ts-mode'.
88Works with JS and CSS and for that use `js-ts-mode' and `css-ts-mode'."
89 :prefix "mhtml-ts-mode-"
90 ;; :group 'languages
91 :group 'html)
92
93(defcustom mhtml-ts-mode-js-css-indent-offset 2
94 "JavaScript and CSS indent spaces related to the <script> and <style> HTML tags.
95By default should have same value as `html-ts-mode-indent-offset'."
96 :tag "HTML javascript or css indent offset"
97 :version "31.1"
98 :type 'integer
99 :safe 'integerp)
100
101(defcustom mhtml-ts-mode-pretty-print-command
102 ;; prefer tidy because it's used by sgml-mode
103 (let ((executable nil))
104 (cond ((setq executable (executable-find "tidy"))
105 (format
106 "%s --gnu-emacs yes --wrap 0 --indent-spaces %s -q -i -"
107 executable html-ts-mode-indent-offset))
108 ((setq executable (executable-find "xmllint"))
109 (format "%s --html --quiet --format -" executable))
110 (t "Install tidy, ore some other HTML pretty print tool, and set `mhtml-ts-mode-pretty-print-command'.")))
111 "The command to pretty print the current HTML buffer."
112 :type 'string
113 :version "31.1")
114
115(defvar mhtml-ts-mode--js-css-indent-offset
116 mhtml-ts-mode-js-css-indent-offset
117 "Internal copy of `mhtml-ts-mode-js-css-indent-offset'.
118The value changes, by `mhtml-ts-mode--tag-relative-indent-offset' according to
119the value of `mhtml-ts-mode-tag-relative-indent'.")
120
121(defun mhtml-ts-mode--tag-relative-indent-offset (sym val)
122 "Custom setter for `mhtml-ts-mode-tag-relative-indent'.
123Apart from setting the default value of SYM to VAL, also change the
124value of SYM in `mhtml-ts-mode' buffers to VAL. SYM should be
125`mhtml-ts-mode-tag-relative-indent', and VAL should be t, nil or
126`ignore'. When sym is `mhtml-ts-mode-tag-relative-indent' set the
127value of `mhtml-ts-mode--js-css-indent-offset' to 0 if VAL is t,
128otherwise to `mhtml-ts-mode-js-css-indent-offset'."
129 (set-default sym val)
130 (when (eq sym 'mhtml-ts-mode-tag-relative-indent)
131 (setq
132 mhtml-ts-mode--js-css-indent-offset
133 (if (eq val t)
134 mhtml-ts-mode-js-css-indent-offset
135 0))))
136
137(defcustom mhtml-ts-mode-tag-relative-indent t
138 "How <script> and <style> bodies are indented relative to the tag.
139
140When t, indentation looks like:
141
142 <script>
143 code();
144 </script>
145
146When nil, indentation of the tag body starts just below the
147tag, like:
148
149 <script>
150 code();
151 </script>
152
153When `ignore', the tag body starts in the first column, like:
154
155 <script>
156code();
157 </script>"
158 :type '(choice (const nil) (const t) (const ignore))
159 :safe 'symbolp
160 :set #'mhtml-ts-mode--tag-relative-indent-offset
161 :version "31.1")
162
163(defcustom mhtml-ts-mode-css-fontify-colors t
164 "Whether CSS colors should be fontified using the color as the background.
165If non-nil, text representing a CSS color will be fontified
166such that its background is the color itself.
167Works like `css--fontify-region'."
168 :tag "HTML colors the CSS properties values."
169 :version "31.1"
170 :type 'boolean
171 :safe 'booleanp)
172
173(defvar mhtml-ts-mode-saved-pretty-print-command nil
174 "The command last used to pretty print in this buffer.")
175
176(defun mhtml-ts-mode-pretty-print (command)
177 "Prettify the current buffer.
178Argument COMMAND The command to use."
179 (interactive
180 (list (read-string
181 "Prettify command: "
182 (or mhtml-ts-mode-saved-pretty-print-command
183 (concat mhtml-ts-mode-pretty-print-command " ")))))
184 (setq mhtml-ts-mode-saved-pretty-print-command command)
185 (save-excursion
186 (shell-command-on-region
187 (point-min) (point-max)
188 command (buffer-name) t
189 "*mhtml-ts-mode-pretty-pretty-print-errors*" t)))
190
191(defun mhtml-ts-mode--switch-fill-defun (&rest arguments)
192 "Switch between `fill-paragraph' and `prog-fill-reindent-defun'.
193In an HTML region it calls `fill-paragraph' as does `html-ts-mode',
194otherwise it calls `prog-fill-reindent-defun'.
195Optional ARGUMENTS to to be passed to it."
196 (interactive)
197 (if (eq (treesit-language-at (point)) 'html)
198 (funcall-interactively #'fill-paragraph arguments)
199 (funcall-interactively #'prog-fill-reindent-defun arguments)))
200
201(defvar-keymap mhtml-ts-mode-map
202 :doc "Keymap for `mhtml-ts-mode' buffers."
203 :parent html-mode-map
204 ;; `mhtml-ts-mode' derive from `html-ts-mode' so the keymap is the
205 ;; same, we need to add some mapping from others languages.
206 "C-c C-f" #'css-cycle-color-format
207 "M-q" #'mhtml-ts-mode--switch-fill-defun)
208
209;; Place the CSS menu in the menu bar as well.
210(easy-menu-define mhtml-ts-mode-menu mhtml-ts-mode-map
211 "Menu bar for `mhtml-ts-mode'."
212 css-mode--menu)
213
214;; To enable some basic treesiter functionality, you should define
215;; a function that recognizes which grammar is used at-point.
216;; This function should be assigned to `treesit-language-at-point-function'
217(defun mhtml-ts-mode--language-at-point (point)
218 "Return the language at POINT assuming the point is within a HTML buffer."
219 (let* ((node (treesit-node-at point 'html))
220 (parent (treesit-node-parent node))
221 (node-query (format "(%s (%s))"
222 (treesit-node-type parent)
223 (treesit-node-type node))))
224 (cond
225 ((equal "(script_element (raw_text))" node-query) (js--treesit-language-at-point point))
226 ((equal "(style_element (raw_text))" node-query) 'css)
227 (t 'html))))
228
229;; Custom font-lock function that's used to apply color to css color
230;; The signature of the function should be conforming to signature
231;; QUERY-SPEC required by `treesit-font-lock-rules'.
232(defun mhtml-ts-mode--colorize-css-value (node override start end &rest _)
233 "Colorize CSS property value like `css--fontify-region'.
234For NODE, OVERRIDE, START, and END, see `treesit-font-lock-rules'."
235 (if (and mhtml-ts-mode-css-fontify-colors
236 (string-equal "plain_value" (treesit-node-type node)))
237 (let ((color (css--compute-color start (treesit-node-text node t))))
238 (when color
239 (with-silent-modifications
240 (add-text-properties
241 (treesit-node-start node) (treesit-node-end node)
242 (list 'face (list :background color
243 :foreground (readable-foreground-color
244 color)
245 :box '(:line-width -1)))))))
246 (treesit-fontify-with-override
247 (treesit-node-start node) (treesit-node-end node)
248 'font-lock-variable-name-face
249 override start end)))
250
251;; Embedded languages ​​should be indented according to the language
252;; that embeds them.
253;; This function signature complies with `treesit-simple-indent-rules'
254;; ANCHOR.
255(defun mhtml-ts-mode--js-css-tag-bol (_node _parent &rest _)
256 "Find the first non-space characters of html tags <script> or <style>.
257Return `line-beginning-position' when `treesit-node-at' is html, or
258`mhtml-ts-mode-tag-relative-indent' is equal to ignore.
259NODE and PARENT are ignored."
260 (if (or (eq (treesit-language-at (point)) 'html)
261 (eq mhtml-ts-mode-tag-relative-indent 'ignore))
262 (line-beginning-position)
263 ;; Ok, we are in js or css block.
264 (save-excursion
265 (re-search-backward "<script.*>\\|<style.*>" nil t))))
266
267;; Treesit supports 4 level of decoration, `treesit-font-lock-level'
268;; define which level to use. Major modes categorize their fontification
269;; features, these categories are defined by `treesit-font-lock-rules' of
270;; each major-mode using :feature keyword.
271;; In a multiple language Major mode it's a good idea to provide, for each
272;; level, the union of the :feature of the same level.
273;; TODO: Since the feature-list is not defined per "parser" (like, for
274;; example, the thing-settings), the same feature can appear in
275;; different levels, so the appearance of a multiple main mode can be
276;; different from the main mode used. For e.g the feature "function" is
277;; at level 4 for Javascript while it is at level 3 for CSS.
278(defvar mhtml-ts-mode--treesit-font-lock-feature-list
279 (treesit-merge-font-lock-feature-list
280 html-ts-mode--treesit-font-lock-feature-list
281 (treesit-merge-font-lock-feature-list
282 js--treesit-font-lock-feature-list
283 css--treesit-font-lock-feature-list))
284 "Settings for `treesit-font-lock-feature-list'.")
285
286(defvar mhtml-ts-mode--treesit-font-lock-settings
287 (append html-ts-mode--font-lock-settings
288 js--treesit-font-lock-settings
289 ;; Let's replace a css rule with a new one that adds color to
290 ;; the css value.
291 (treesit-replace-font-lock-feature-settings
292 (treesit-font-lock-rules
293 :language 'css
294 :override t
295 :feature 'variable
296 '((plain_value) @font-lock-variable-name-face
297 (plain_value) @mhtml-ts-mode--colorize-css-value))
298 css--treesit-settings))
299 "Settings for `treesit-font-lock-settings'.")
300
301(defvar mhtml-ts-mode--treesit-thing-settings
302 ;; In addition to putting together the various definitions, we need to
303 ;; add 'defun' which is used to support `imenu' and 'which-function'.
304 (list
305 ;; HTML thing settings
306 (append
307 (car html-ts-mode--treesit-things-settings)
308 `((defun ,(regexp-opt (list html-ts-mode--treesit-defun-type-regexp)))))
309 ;; Javascript thing settings
310 (append
311 (car js--treesit-thing-settings)
312 `((defun ,js--treesit-defun-type-regexp)))
313 ;; CSS thing settings
314 `(css
315 (defun ,(regexp-opt (list css--treesit-defun-type-regexp)))))
316 "Settings for `treesit-thing-settings'.")
317
318(defvar mhtml-ts-mode--treesit-indent-rules
319 (treesit--indent-rules-optimize
320 (append html-ts-mode--indent-rules
321 ;; Extended rules for js and css, to
322 ;; indent appropriately when injected
323 ;; into html
324 (treesit-modify-indent-rules
325 'javascript
326 `((javascript ((parent-is "program")
327 mhtml-ts-mode--js-css-tag-bol
328 mhtml-ts-mode--js-css-indent-offset)))
329 js--treesit-indent-rules
330 :replace)
331 (treesit-modify-indent-rules
332 'css
333 `((css ((parent-is "stylesheet")
334 mhtml-ts-mode--js-css-tag-bol
335 mhtml-ts-mode--js-css-indent-offset)))
336 css--treesit-indent-rules 'prepend)
337 :replace))
338 "Settings for `treesit-simple-indent-rules'.")
339
340(defvar mhtml-ts-mode--treesit-aggregated-simple-imenu-settings
341 `((html ,@html-ts-mode--treesit-simple-imenu-settings)
342 (javascript ,@js--treesit-simple-imenu-settings)
343 (css ,@css--treesit-simple-imenu-settings))
344 "Settings for `treesit-simple-imenu'.")
345
346;; TODO: treesit-defun-type-regexp should have an aggregated version,
347;; like treesit-aggregated-simple-imenu-settings. Otherwise we can't
348;; reuse the regex defined in the major mode we use.
349(defvar mhtml-ts-mode--treesit-defun-type-regexp
350 (regexp-opt '("class_declaration"
351 "method_definition"
352 "function_declaration"
353 "lexical_declaration"
354 "element"
355 "rule_set"))
356 "Settings for `treesit-defun-type-regexp'.")
357
358;; In order to support `prettify-symbols-mode', just `append' the prettify
359;; alist of all the languages. In our case only javascript defined this alist.
360(defvar mhtml-ts-mode--prettify-symbols-alist js--prettify-symbols-alist
361 "Alist of symbol prettifications for various supported languages.")
362
363(defun mhtml-ts-mode--html-defun-name (node)
364 "Return the defun name of NODE.
365Return nil if there is no name or if NODE is not a defun node."
366 (when (string-match-p "element" (treesit-node-type node))
367 (treesit-node-text
368 node
369 ;; (treesit-search-subtree node "\\`tag_name\\'" nil nil 2)
370 t)))
371
372;; In order to support `which-fuction-mode' we should define
373;; a function that return the defun name.
374;; In a multilingual treesit mode, this can be implemented simply by
375;; calling language-specific functions.
376(defun mhtml-ts-mode--defun-name (node)
377 "Return the defun name of NODE.
378Return nil if there is no name or if NODE is not a defun node."
379 (let ((html-name (html-ts-mode--defun-name node))
380 (js-name (js--treesit-defun-name node))
381 (css-name (css--treesit-defun-name node)))
382 (cond
383 (html-name html-name)
384 (js-name js-name)
385 (css-name css-name))))
386
387;;; Flymake integration
388
389(defvar-local mhtml-ts-mode--flymake-process nil
390 "Store the Flymake process.")
391
392(defun mhtml-ts-mode-flymake-mhtml (report-fn &rest _args)
393 "MHTML backend for Flymake.
394Calls REPORT-FN directly. Requires tidy."
395 (when (process-live-p mhtml-ts-mode--flymake-process)
396 (kill-process mhtml-ts-mode--flymake-process))
397 (let ((tidy (executable-find "tidy"))
398 (source (current-buffer))
399 (diagnostics-pattern (eval-when-compile
400 (rx bol
401 "line " (group (+ num)) ;; :1 line
402 " column " (group (+ num)) ;; :2 column
403 " - " (group (+? nonl)) ;; :3 type
404 ": " (group (+? nonl)) ;; :4 msg
405 eol))))
406 (if (not tidy)
407 (error "Unable to find tidy command")
408 (save-restriction
409 (widen)
410 (setq mhtml-ts-mode--flymake-process
411 (make-process
412 :name "mhtml-ts-mode-flymake"
413 :noquery t
414 :connection-type 'pipe
415 :buffer (generate-new-buffer "*mhtml-ts-mode-flymake*")
416 :command `(,tidy "--gnu-emacs" "yes" "-e" "-q")
417 :sentinel
418 (lambda (proc _event)
419 (when (eq 'exit (process-status proc))
420 (unwind-protect
421 (if (with-current-buffer source
422 (eq proc mhtml-ts-mode--flymake-process))
423 (with-current-buffer (process-buffer proc)
424 (goto-char (point-min))
425 (let (diags)
426 (while (search-forward-regexp diagnostics-pattern nil t)
427 (let* ((pos
428 (flymake-diag-region
429 source
430 (string-to-number (match-string 1))
431 (string-to-number (match-string 2)))) ;; line and column
432 (type (cond ((equal (match-string 3) "Warning") :warning)
433 ((equal (match-string 3) "Error") :error))) ;; type of message
434 (msg (match-string 4))) ;; message
435 (push (flymake-make-diagnostic source (car pos) (cdr pos) type msg)
436 diags)))
437 (funcall report-fn diags)))
438 (flymake-log :warning "Canceling obsolete check %s" proc))
439 (kill-buffer (process-buffer proc)))))))
440 (process-send-region mhtml-ts-mode--flymake-process (point-min) (point-max))
441 (process-send-eof mhtml-ts-mode--flymake-process)))))
442
443(define-derived-mode mhtml-ts-mode html-ts-mode
444 '("HTML+" (:eval (let ((lang (mhtml-ts-mode--language-at-point (point))))
445 (cond ((eq lang 'html) "")
446 ((eq lang 'javascript) "JS")
447 ((eq lang 'css) "CSS")))))
448 "Major mode for editing HTML with embedded JavaScript and CSS.
449Powered by tree-sitter."
450 (if (not (and
451 (treesit-ready-p 'html)
452 (treesit-ready-p 'javascript)
453 (treesit-ready-p 'css)))
454 (error "Tree-sitter parsers for HTML isn't available. You can
455 install the parsers with M-x `mhtml-ts-mode-install-parsers'")
456
457 ;; When an language is embedded, you should initialize some variable
458 ;; just like it's done in the original mode.
459
460 ;; Comment.
461 ;; indenting settings for js-ts-mode.
462 (c-ts-common-comment-setup)
463 (setq-local comment-multi-line t)
464
465 ;; Font-lock.
466
467 ;; There are two ways to handle embedded code:
468 ;; 1. Use a single parser for all the embedded code in the buffer. In
469 ;; this case, the embedded code blocks are concatenated together and are
470 ;; seen as a single continuous document to the parser.
471 ;; 2. Each embedded code block gets its own parser. Each parser only sees
472 ;; that particular code block.
473
474 ;; If you go with 2 for a language, the local parsers are created and
475 ;; destroyed automatically by Emacs. So don't create a global parser for
476 ;; that embedded language here.
477
478 ;; Create the parsers, only the global ones.
479 ;; jsdoc is a local parser, don't create a parser for it.
480 (treesit-parser-create 'css)
481 (treesit-parser-create 'javascript)
482
483 ;; Multi-language modes must set the primary parser.
484 (setq-local treesit-primary-parser (treesit-parser-create 'html))
485
486 (setq-local treesit-range-settings
487 (treesit-range-rules
488 :embed 'javascript
489 :host 'html
490 '((script_element
491 (start_tag (tag_name))
492 (raw_text) @cap))
493
494 ;; Another rule could be added that when it matches an
495 ;; attribute_value that has as its parent an
496 ;; attribute_name "style" it captures it and then
497 ;; passes it to the css parser.
498 :embed 'css
499 :host 'html
500 '((style_element
501 (start_tag (tag_name))
502 (raw_text) @cap))))
503
504 ;; jsdoc is not mandatory for js-ts-mode, so we respect this by
505 ;; adding jsdoc range rules only when jsdoc is available.
506 (when (treesit-ready-p 'jsdoc t)
507 (setq-local treesit-range-settings
508 (append treesit-range-settings
509 (treesit-range-rules
510 :embed 'jsdoc
511 :host 'javascript
512 :local t
513 `(((comment) @cap
514 (:match ,js--treesit-jsdoc-beginning-regexp @cap))))))
515 (setq-local c-ts-common--comment-regexp
516 js--treesit-jsdoc-comment-regexp))
517
518
519 ;; Many treesit fuctions need to know the language at-point.
520 ;; So you should define such a function.
521 (setq-local treesit-language-at-point-function #'mhtml-ts-mode--language-at-point)
522 (setq-local prettify-symbols-alist mhtml-ts-mode--prettify-symbols-alist)
523
524 ;; Indent.
525
526 ;; Since `mhtml-ts-mode' inherits indentation rules from `html-ts-mode', `js-ts-mode'
527 ;; and `css-ts-mode', if you want to change the offset you have to act on the
528 ;; *-offset variables defined for those languages.
529
530 ;; JavaScript and CSS must be indented relative to their code block.
531 ;; This is done by inserting a special rule before the normal
532 ;; indentation rules of these languages.
533 ;; The value of `mhtml-ts-mode--js-css-indent-offset' changes based on
534 ;; `mhtml-ts-mode-tag-relative-indent' and can be used to indent
535 ;; JavaScript and CSS code relative to the HTML that contains them,
536 ;; just like in mhtml-mode.
537 (setq-local treesit-simple-indent-rules mhtml-ts-mode--treesit-indent-rules)
538
539 ;; Navigation.
540
541 ;; This is for which-function-mode.
542 ;; Since mhtml-ts-mode is derived from html-ts-mode, which sets
543 ;; the value of `treesit-defun-type-regexp', you have to reset it to nil
544 ;; otherwise `imenu' and `which-function-mode' will not work.
545 (setq-local treesit-defun-type-regexp nil)
546
547 ;; This is for finding defun name, it's used by IMenu as default
548 ;; function no specific functions are defined.
549 (setq-local treesit-defun-name-function #'mhtml-ts-mode--defun-name)
550
551 ;; Define what are 'thing' for treesit.
552 ;; 'Thing' is a symbol representing the thing, like `defun', `sexp', or
553 ;; `sentence'.
554 ;; As an alternative, if you want just defun, you can define a `treesit-defun-type-regexp'.
555 (setq-local treesit-thing-settings mhtml-ts-mode--treesit-thing-settings)
556
557 ;; Font-lock.
558
559 ;; In a multi-language scenario, font lock settings are usually a
560 ;; concatenation of language rules. As you can see, it is possible
561 ;; to extend/modify the default rule or use a different set of
562 ;; rules. See `php-ts-mode--custom-html-font-lock-settings' for more
563 ;; advanced usage.
564 (setq-local treesit-font-lock-settings mhtml-ts-mode--treesit-font-lock-settings)
565
566 ;; Tells treesit the list of features to fontify.
567 (setq-local treesit-font-lock-feature-list mhtml-ts-mode--treesit-font-lock-feature-list)
568
569 ;; Imenu
570
571 ;; Setup Imenu: if no function is specified, try to find an object
572 ;; using `treesit-defun-name-function'.
573 (setq-local treesit-aggregated-simple-imenu-settings
574 mhtml-ts-mode--treesit-aggregated-simple-imenu-settings)
575
576 ;; (setq-local treesit-outline-predicate nil)
577
578 (treesit-major-mode-setup)
579
580 ;; This is sort of a prog-mode as well as a text mode.
581 (run-mode-hooks 'prog-mode-hook)
582
583 ;; Flymake
584 (add-hook 'flymake-diagnostic-functions #'mhtml-ts-mode-flymake-mhtml nil 'local)))
585
586;; Add nome extra parents.
587(derived-mode-add-parents 'mhtml-ts-mode '(css-mode js-mode))
588
589(when (and (treesit-ready-p 'html) (treesit-ready-p 'javascript) (treesit-ready-p 'css))
590 (add-to-list
591 'auto-mode-alist '("\\.[sx]?html?\\(\\.[a-zA-Z_]+\\)?\\'" . mhtml-ts-mode)))
592
593(provide 'mhtml-ts-mode)
594;;; mhtml-ts-mode.el ends here
diff --git a/lisp/treesit.el b/lisp/treesit.el
index b923545d50c..30fe3798bd9 100644
--- a/lisp/treesit.el
+++ b/lisp/treesit.el
@@ -1317,6 +1317,40 @@ and leave settings for other languages unchanged."
1317 ((memq feature remove-list) nil) 1317 ((memq feature remove-list) nil)
1318 (t current-value)))))) 1318 (t current-value))))))
1319 1319
1320(defun treesit-merge-font-lock-feature-list (features-list-1 features-list-2)
1321 "Merge two tree-sitter font lock feature lists.
1322Returns a new font lock feature list with no duplicates in the same level.
1323It can be used to merge font lock feature lists in a multi-language major mode.
1324FEATURES-LIST-1 and FEATURES-LIST-2 are list of lists of feature symbols."
1325 (let ((result nil)
1326 (features-1 (car features-list-1))
1327 (features-2 (car features-list-2)))
1328 (while (or features-1 features-2)
1329 (cond
1330 ((and features-1 (not features-2)) (push features-1 result))
1331 ((and (not features-1) features-2) (push features-2 result))
1332 ((and features-1 features-2) (push (cl-union features-1 features-2) result)))
1333 (setq features-list-1 (cdr features-list-1)
1334 features-list-2 (cdr features-list-2)
1335 features-1 (car features-list-1)
1336 features-2 (car features-list-2)))
1337 (nreverse result)))
1338
1339(defun treesit-replace-font-lock-feature-settings (new-settings settings)
1340 "Replaces :feature in SETTINGS with :feature from NEW-SETTINGS.
1341Both SETTINGS and NEW-SETTINGS must be a value suitable for
1342`treesit-font-lock-settings'.
1343Return a value suitable for `treesit-font-lock-settings'"
1344 (let ((result nil))
1345 (dolist (new-setting new-settings)
1346 (let ((new-feature (treesit-font-lock-setting-feature new-setting)))
1347 (dolist (setting settings)
1348 (let ((feature (treesit-font-lock-setting-feature setting)))
1349 (if (eq new-feature feature)
1350 (push new-setting result)
1351 (push setting result))))))
1352 (nreverse result)))
1353
1320(defun treesit-add-font-lock-rules (rules &optional how feature) 1354(defun treesit-add-font-lock-rules (rules &optional how feature)
1321 "Add font-lock RULES to the current buffer. 1355 "Add font-lock RULES to the current buffer.
1322 1356
@@ -2506,6 +2540,40 @@ end of existing rules."
2506 (append rules existing-rules))))) 2540 (append rules existing-rules)))))
2507 (setf (alist-get language treesit-simple-indent-rules) new-rules))) 2541 (setf (alist-get language treesit-simple-indent-rules) new-rules)))
2508 2542
2543(defun treesit-modify-indent-rules (lang new-rules rules &optional how)
2544 "Modify a copy of RULES using NEW-RULES.
2545As default replace rules with the same anchor.
2546When HOW is :prepend NEW-RULES are prepend to RULES, when
2547:append NEW-RULES are appended to RULES, when :replace (the default)
2548NEW-RULES replace rule in RULES which the same anchor."
2549 (cond
2550 ((not (alist-get lang rules))
2551 (error "No rules for language %s in RULES" lang))
2552 ((not (alist-get lang new-rules))
2553 (error "No rules for language %s in NEW-RULES" lang))
2554 (t (let* ((copy-of-rules (copy-tree js--treesit-indent-rules))
2555 (lang-rules (alist-get lang copy-of-rules))
2556 (lang-new-rules (alist-get lang new-rules)))
2557 (cond
2558 ((eq how :prepend)
2559 (setf (alist-get lang copy-of-rules)
2560 (append lang-new-rules lang-rules)))
2561 ((eq how :append)
2562 (setf (alist-get lang copy-of-rules)
2563 (append lang-rules lang-new-rules)))
2564 ((or (eq how :replace) t)
2565 (let ((tail-new-rules lang-new-rules)
2566 (tail-rules lang-rules)
2567 (new-rule nil)
2568 (rule nil))
2569 (while (setq new-rule (car tail-new-rules))
2570 (while (setq rule (car tail-rules))
2571 (when (equal (nth 0 new-rule) (nth 0 rule))
2572 (setf (car tail-rules) new-rule))
2573 (setq tail-rules (cdr tail-rules)))
2574 (setq tail-new-rules (cdr tail-new-rules))))))
2575 copy-of-rules))))
2576
2509;;; Search 2577;;; Search
2510 2578
2511(defun treesit-search-forward-goto 2579(defun treesit-search-forward-goto