aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWilhelm H Kirschbaum2023-03-12 17:08:50 +0200
committerEli Zaretskii2023-03-12 17:40:06 +0200
commit802e64922bcee40c8362b9627aa33a0de0c068d7 (patch)
tree058a1aa497056b49763b8edfa8b9171ac18c0a67
parentd19416d15c29368112fba9a7437930abcec9af3b (diff)
downloademacs-802e64922bcee40c8362b9627aa33a0de0c068d7.tar.gz
emacs-802e64922bcee40c8362b9627aa33a0de0c068d7.zip
Add heex-ts-mode (Bug#61996)
* etc/NEWS: Mention the new mode. * lisp/progmodes/heex-ts-mode.el: New file. * test/lisp/progmodes/heex-ts-mode-tests.el: New file. * test/lisp/progmodes/heex-ts-mode-resources/indent.erts: New file. * admin/notes/tree-sitter/build-module/batch.sh: * admin/notes/tree-sitter/build-module/build.sh: Add HEEx support.
-rwxr-xr-xadmin/notes/tree-sitter/build-module/batch.sh1
-rwxr-xr-xadmin/notes/tree-sitter/build-module/build.sh3
-rw-r--r--etc/NEWS3
-rw-r--r--lisp/progmodes/heex-ts-mode.el185
-rw-r--r--test/lisp/progmodes/heex-ts-mode-resources/indent.erts47
-rw-r--r--test/lisp/progmodes/heex-ts-mode-tests.el9
6 files changed, 248 insertions, 0 deletions
diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh
index 58272c74549..8b0072782e8 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -10,6 +10,7 @@ languages=(
10 'dockerfile' 10 'dockerfile'
11 'go' 11 'go'
12 'go-mod' 12 'go-mod'
13 'heex'
13 'html' 14 'html'
14 'javascript' 15 'javascript'
15 'json' 16 'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh
index 9dc674237ca..78ecfb5bc82 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -36,6 +36,9 @@ case "${lang}" in
36 lang="gomod" 36 lang="gomod"
37 org="camdencheek" 37 org="camdencheek"
38 ;; 38 ;;
39 "heex")
40 org="phoenixframework"
41 ;;
39 "typescript") 42 "typescript")
40 sourcedir="tree-sitter-typescript/typescript/src" 43 sourcedir="tree-sitter-typescript/typescript/src"
41 grammardir="tree-sitter-typescript/typescript" 44 grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index e43aac614c9..682928afa8e 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -248,6 +248,9 @@ following to you init file:
248An optional major mode based on the tree-sitter library for editing 248An optional major mode based on the tree-sitter library for editing
249HTML files. 249HTML files.
250 250
251*** New major mode heex-ts-mode'.
252A major mode based on the tree-sitter library for editing HEEx files.
253
251--- 254---
252** The highly accessible Modus themes collection has six items. 255** The highly accessible Modus themes collection has six items.
253The 'modus-operandi' and 'modus-vivendi' are the main themes that have 256The 'modus-operandi' and 'modus-vivendi' are the main themes that have
diff --git a/lisp/progmodes/heex-ts-mode.el b/lisp/progmodes/heex-ts-mode.el
new file mode 100644
index 00000000000..68a537b9229
--- /dev/null
+++ b/lisp/progmodes/heex-ts-mode.el
@@ -0,0 +1,185 @@
1;;; heex-ts-mode.el --- Major mode for Heex with tree-sitter support -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2022-2023 Free Software Foundation, Inc.
4
5;; Author: Wilhelm H Kirschbaum <wkirschbaum@gmail.com>
6;; Created: November 2022
7;; Keywords: elixir languages tree-sitter
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software: you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
23
24;;; Commentary:
25;;
26;; This package provides `heex-ts-mode' which is a major mode for editing
27;; HEEx files that uses Tree Sitter to parse the language.
28;;
29;; This package is compatible with and was tested against the tree-sitter grammar
30;; for HEEx found at https://github.com/phoenixframework/tree-sitter-heex.
31
32;;; Code:
33
34(require 'treesit)
35(eval-when-compile (require 'rx))
36
37(declare-function treesit-parser-create "treesit.c")
38(declare-function treesit-node-child "treesit.c")
39(declare-function treesit-node-type "treesit.c")
40(declare-function treesit-node-start "treesit.c")
41
42(defgroup heex-ts nil
43 "Major mode for editing HEEx code."
44 :prefix "heex-ts-"
45 :group 'langauges)
46
47(defcustom heex-ts-indent-offset 2
48 "Indentation of HEEx statements."
49 :version "30.1"
50 :type 'integer
51 :safe 'integerp
52 :group 'heex-ts)
53
54(defconst heex-ts--sexp-regexp
55 (rx bol
56 (or "directive" "tag" "component" "slot"
57 "attribute" "attribute_value" "quoted_attribute_value")
58 eol))
59
60;; There seems to be no parent directive block for tree-sitter-heex,
61;; so we ignore them for now until we learn how to query them.
62;; https://github.com/phoenixframework/tree-sitter-heex/issues/28
63(defvar heex-ts--indent-rules
64 (let ((offset heex-ts-indent-offset))
65 `((heex
66 ((parent-is "fragment")
67 (lambda (node parent &rest _)
68 ;; If HEEx is embedded indent to parent
69 ;; otherwise indent to the bol.
70 (if (eq (treesit-language-at (point-min)) 'heex)
71 (point-min)
72 (save-excursion
73 (goto-char (treesit-node-start parent))
74 (back-to-indentation)
75 (point))
76 )) 0)
77 ((node-is "end_tag") parent-bol 0)
78 ((node-is "end_component") parent-bol 0)
79 ((node-is "end_slot") parent-bol 0)
80 ((node-is "/>") parent-bol 0)
81 ((node-is ">") parent-bol 0)
82 ((parent-is "comment") prev-adaptive-prefix 0)
83 ((parent-is "component") parent-bol ,offset)
84 ((parent-is "tag") parent-bol ,offset)
85 ((parent-is "start_tag") parent-bol ,offset)
86 ((parent-is "component") parent-bol ,offset)
87 ((parent-is "start_component") parent-bol ,offset)
88 ((parent-is "slot") parent-bol ,offset)
89 ((parent-is "start_slot") parent-bol ,offset)
90 ((parent-is "self_closing_tag") parent-bol ,offset)
91 (no-node parent-bol ,offset)))))
92
93(defvar heex-ts--font-lock-settings
94 (when (treesit-available-p)
95 (treesit-font-lock-rules
96 :language 'heex
97 :feature 'heex-comment
98 '((comment) @font-lock-comment-face)
99 :language 'heex
100 :feature 'heex-doctype
101 '((doctype) @font-lock-doc-face)
102 :language 'heex
103 :feature 'heex-tag
104 `([(tag_name) (slot_name)] @font-lock-function-name-face)
105 :language 'heex
106 :feature 'heex-attribute
107 `((attribute_name) @font-lock-variable-name-face)
108 :language 'heex
109 :feature 'heex-keyword
110 `((special_attribute_name) @font-lock-keyword-face)
111 :language 'heex
112 :feature 'heex-string
113 `([(attribute_value) (quoted_attribute_value)] @font-lock-constant-face)
114 :language 'heex
115 :feature 'heex-component
116 `([
117 (component_name) @font-lock-function-name-face
118 (module) @font-lock-keyword-face
119 (function) @font-lock-keyword-face
120 "." @font-lock-keyword-face
121 ])))
122 "Tree-sitter font-lock settings.")
123
124(defun heex-ts--defun-name (node)
125 "Return the name of the defun NODE.
126Return nil if NODE is not a defun node or doesn't have a name."
127 (pcase (treesit-node-type node)
128 ((or "component" "slot" "tag")
129 (string-trim
130 (treesit-node-text
131 (treesit-node-child (treesit-node-child node 0) 1) nil)))
132 (_ nil)))
133
134(defun heex-ts--forward-sexp (&optional arg)
135 "Move forward across one balanced expression (sexp).
136With ARG, do it many times. Negative ARG means move backward."
137 (or arg (setq arg 1))
138 (funcall
139 (if (> arg 0) #'treesit-end-of-thing #'treesit-beginning-of-thing)
140 heex-ts--sexp-regexp
141 (abs arg)))
142
143;;;###autoload
144(define-derived-mode heex-ts-mode html-mode "HEEx"
145 "Major mode for editing HEEx, powered by tree-sitter."
146 :group 'heex-ts
147
148 (when (treesit-ready-p 'heex)
149 (treesit-parser-create 'heex)
150
151 ;; Comments
152 (setq-local treesit-text-type-regexp
153 (regexp-opt '("comment" "text")))
154
155 (setq-local forward-sexp-function #'heex-ts--forward-sexp)
156
157 ;; Navigation.
158 (setq-local treesit-defun-type-regexp
159 (rx bol (or "component" "tag" "slot") eol))
160 (setq-local treesit-defun-name-function #'heex-ts--defun-name)
161
162 ;; Imenu
163 (setq-local treesit-simple-imenu-settings
164 '(("Component" "\\`component\\'" nil nil)
165 ("Slot" "\\`slot\\'" nil nil)
166 ("Tag" "\\`tag\\'" nil nil)))
167
168 (setq-local treesit-font-lock-settings heex-ts--font-lock-settings)
169
170 (setq-local treesit-simple-indent-rules heex-ts--indent-rules)
171
172 (setq-local treesit-font-lock-feature-list
173 '(( heex-comment heex-keyword heex-doctype )
174 ( heex-component heex-tag heex-attribute heex-string )
175 () ()))
176
177 (treesit-major-mode-setup)))
178
179(if (treesit-ready-p 'heex)
180 ;; Both .heex and the deprecated .leex files should work
181 ;; with the tree-sitter-heex grammar.
182 (add-to-list 'auto-mode-alist '("\\.[hl]?eex\\'" . heex-ts-mode)))
183
184(provide 'heex-ts-mode)
185;;; heex-ts-mode.el ends here
diff --git a/test/lisp/progmodes/heex-ts-mode-resources/indent.erts b/test/lisp/progmodes/heex-ts-mode-resources/indent.erts
new file mode 100644
index 00000000000..500ddb2b536
--- /dev/null
+++ b/test/lisp/progmodes/heex-ts-mode-resources/indent.erts
@@ -0,0 +1,47 @@
1Code:
2 (lambda ()
3 (setq indent-tabs-mode nil)
4 (heex-ts-mode)
5 (indent-region (point-min) (point-max)))
6
7Point-Char: $
8
9Name: Tag
10
11=-=
12 <div>
13 div
14 </div>
15=-=
16<div>
17 div
18</div>
19=-=-=
20
21Name: Component
22
23=-=
24 <Foo>
25 foobar
26 </Foo>
27=-=
28<Foo>
29 foobar
30</Foo>
31=-=-=
32
33Name: Slots
34
35=-=
36 <Foo>
37 <:bar>
38 foobar
39 </:bar>
40 </Foo>
41=-=
42<Foo>
43 <:bar>
44 foobar
45 </:bar>
46</Foo>
47=-=-=
diff --git a/test/lisp/progmodes/heex-ts-mode-tests.el b/test/lisp/progmodes/heex-ts-mode-tests.el
new file mode 100644
index 00000000000..b59126e136a
--- /dev/null
+++ b/test/lisp/progmodes/heex-ts-mode-tests.el
@@ -0,0 +1,9 @@
1(require 'ert)
2(require 'ert-x)
3(require 'treesit)
4
5(ert-deftest heex-ts-mode-test-indentation ()
6 (skip-unless (treesit-ready-p 'heex))
7 (ert-test-erts-file (ert-resource-file "indent.erts")))
8
9(provide 'heex-ts-mode-tests)