]> git.eshelyaron.com Git - emacs.git/commitdiff
Add heex-ts-mode (Bug#61996)
authorWilhelm H Kirschbaum <wkirschbaum@gmail.com>
Sun, 12 Mar 2023 15:08:50 +0000 (17:08 +0200)
committerEli Zaretskii <eliz@gnu.org>
Sun, 12 Mar 2023 15:40:06 +0000 (17:40 +0200)
* 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.

admin/notes/tree-sitter/build-module/batch.sh
admin/notes/tree-sitter/build-module/build.sh
etc/NEWS
lisp/progmodes/heex-ts-mode.el [new file with mode: 0644]
test/lisp/progmodes/heex-ts-mode-resources/indent.erts [new file with mode: 0644]
test/lisp/progmodes/heex-ts-mode-tests.el [new file with mode: 0644]

index 58272c7454948482e213d66877446dc6cfbaa5c6..8b0072782e818336fa7d85d20d299c13ba99e823 100755 (executable)
@@ -10,6 +10,7 @@ languages=(
     'dockerfile'
     'go'
     'go-mod'
+    'heex'
     'html'
     'javascript'
     'json'
index 9dc674237cac1620f0823d9e41e20d15dcd8b237..78ecfb5bc8209e491077e0752d81b7a31fa80420 100755 (executable)
@@ -36,6 +36,9 @@ case "${lang}" in
         lang="gomod"
         org="camdencheek"
         ;;
+    "heex")
+        org="phoenixframework"
+        ;;
     "typescript")
         sourcedir="tree-sitter-typescript/typescript/src"
         grammardir="tree-sitter-typescript/typescript"
index e43aac614c97973b2e6062a4a4ea6825695a4449..682928afa8e21f370b266993dc529b59e6d04bec 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -248,6 +248,9 @@ following to you init file:
 An optional major mode based on the tree-sitter library for editing
 HTML files.
 
+*** New major mode heex-ts-mode'.
+A major mode based on the tree-sitter library for editing HEEx files.
+
 ---
 ** The highly accessible Modus themes collection has six items.
 The '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 (file)
index 0000000..68a537b
--- /dev/null
@@ -0,0 +1,185 @@
+;;; heex-ts-mode.el --- Major mode for Heex with tree-sitter support -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2022-2023 Free Software Foundation, Inc.
+
+;; Author: Wilhelm H Kirschbaum <wkirschbaum@gmail.com>
+;; Created: November 2022
+;; Keywords: elixir languages 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 `heex-ts-mode' which is a major mode for editing
+;; HEEx files that uses Tree Sitter to parse the language.
+;;
+;; This package is compatible with and was tested against the tree-sitter grammar
+;; for HEEx found at https://github.com/phoenixframework/tree-sitter-heex.
+
+;;; Code:
+
+(require 'treesit)
+(eval-when-compile (require 'rx))
+
+(declare-function treesit-parser-create "treesit.c")
+(declare-function treesit-node-child "treesit.c")
+(declare-function treesit-node-type "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+
+(defgroup heex-ts nil
+  "Major mode for editing HEEx code."
+  :prefix "heex-ts-"
+  :group 'langauges)
+
+(defcustom heex-ts-indent-offset 2
+  "Indentation of HEEx statements."
+  :version "30.1"
+  :type 'integer
+  :safe 'integerp
+  :group 'heex-ts)
+
+(defconst heex-ts--sexp-regexp
+  (rx bol
+      (or "directive" "tag" "component" "slot"
+          "attribute" "attribute_value" "quoted_attribute_value")
+      eol))
+
+;; There seems to be no parent directive block for tree-sitter-heex,
+;; so we ignore them for now until we learn how to query them.
+;; https://github.com/phoenixframework/tree-sitter-heex/issues/28
+(defvar heex-ts--indent-rules
+  (let ((offset heex-ts-indent-offset))
+    `((heex
+       ((parent-is "fragment")
+        (lambda (node parent &rest _)
+          ;; If HEEx is embedded indent to parent
+          ;; otherwise indent to the bol.
+          (if (eq (treesit-language-at (point-min)) 'heex)
+              (point-min)
+            (save-excursion
+              (goto-char (treesit-node-start parent))
+              (back-to-indentation)
+              (point))
+            )) 0)
+       ((node-is "end_tag") parent-bol 0)
+       ((node-is "end_component") parent-bol 0)
+       ((node-is "end_slot") parent-bol 0)
+       ((node-is "/>") parent-bol 0)
+       ((node-is ">") parent-bol 0)
+       ((parent-is "comment") prev-adaptive-prefix 0)
+       ((parent-is "component") parent-bol ,offset)
+       ((parent-is "tag") parent-bol ,offset)
+       ((parent-is "start_tag") parent-bol ,offset)
+       ((parent-is "component") parent-bol ,offset)
+       ((parent-is "start_component") parent-bol ,offset)
+       ((parent-is "slot") parent-bol ,offset)
+       ((parent-is "start_slot") parent-bol ,offset)
+       ((parent-is "self_closing_tag") parent-bol ,offset)
+       (no-node parent-bol ,offset)))))
+
+(defvar heex-ts--font-lock-settings
+  (when (treesit-available-p)
+    (treesit-font-lock-rules
+     :language 'heex
+     :feature 'heex-comment
+     '((comment) @font-lock-comment-face)
+     :language 'heex
+     :feature 'heex-doctype
+     '((doctype) @font-lock-doc-face)
+     :language 'heex
+     :feature 'heex-tag
+     `([(tag_name) (slot_name)] @font-lock-function-name-face)
+     :language 'heex
+     :feature 'heex-attribute
+     `((attribute_name) @font-lock-variable-name-face)
+     :language 'heex
+     :feature 'heex-keyword
+     `((special_attribute_name) @font-lock-keyword-face)
+     :language 'heex
+     :feature 'heex-string
+     `([(attribute_value) (quoted_attribute_value)] @font-lock-constant-face)
+     :language 'heex
+     :feature 'heex-component
+     `([
+        (component_name) @font-lock-function-name-face
+        (module) @font-lock-keyword-face
+        (function) @font-lock-keyword-face
+        "." @font-lock-keyword-face
+        ])))
+  "Tree-sitter font-lock settings.")
+
+(defun heex-ts--defun-name (node)
+  "Return the name of the defun NODE.
+Return nil if NODE is not a defun node or doesn't have a name."
+  (pcase (treesit-node-type node)
+    ((or "component" "slot" "tag")
+     (string-trim
+      (treesit-node-text
+       (treesit-node-child (treesit-node-child node 0) 1) nil)))
+    (_ nil)))
+
+(defun heex-ts--forward-sexp (&optional arg)
+  "Move forward across one balanced expression (sexp).
+With ARG, do it many times.  Negative ARG means move backward."
+  (or arg (setq arg 1))
+  (funcall
+   (if (> arg 0) #'treesit-end-of-thing #'treesit-beginning-of-thing)
+   heex-ts--sexp-regexp
+   (abs arg)))
+
+;;;###autoload
+(define-derived-mode heex-ts-mode html-mode "HEEx"
+  "Major mode for editing HEEx, powered by tree-sitter."
+  :group 'heex-ts
+
+  (when (treesit-ready-p 'heex)
+    (treesit-parser-create 'heex)
+
+    ;; Comments
+    (setq-local treesit-text-type-regexp
+                (regexp-opt '("comment" "text")))
+
+    (setq-local forward-sexp-function #'heex-ts--forward-sexp)
+
+    ;; Navigation.
+    (setq-local treesit-defun-type-regexp
+                (rx bol (or "component" "tag" "slot") eol))
+    (setq-local treesit-defun-name-function #'heex-ts--defun-name)
+
+    ;; Imenu
+    (setq-local treesit-simple-imenu-settings
+                '(("Component" "\\`component\\'" nil nil)
+                  ("Slot" "\\`slot\\'" nil nil)
+                  ("Tag" "\\`tag\\'" nil nil)))
+
+    (setq-local treesit-font-lock-settings heex-ts--font-lock-settings)
+
+    (setq-local treesit-simple-indent-rules heex-ts--indent-rules)
+
+    (setq-local treesit-font-lock-feature-list
+                '(( heex-comment heex-keyword heex-doctype )
+                  ( heex-component heex-tag heex-attribute heex-string )
+                  () ()))
+
+    (treesit-major-mode-setup)))
+
+(if (treesit-ready-p 'heex)
+    ;; Both .heex and the deprecated .leex files should work
+    ;; with the tree-sitter-heex grammar.
+    (add-to-list 'auto-mode-alist '("\\.[hl]?eex\\'" . heex-ts-mode)))
+
+(provide 'heex-ts-mode)
+;;; 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 (file)
index 0000000..500ddb2
--- /dev/null
@@ -0,0 +1,47 @@
+Code:
+  (lambda ()
+    (setq indent-tabs-mode nil)
+    (heex-ts-mode)
+    (indent-region (point-min) (point-max)))
+
+Point-Char: $
+
+Name: Tag
+
+=-=
+   <div>
+ div
+    </div>
+=-=
+<div>
+  div
+</div>
+=-=-=
+
+Name: Component
+
+=-=
+   <Foo>
+     foobar
+      </Foo>
+=-=
+<Foo>
+  foobar
+</Foo>
+=-=-=
+
+Name: Slots
+
+=-=
+   <Foo>
+   <:bar>
+     foobar
+      </:bar>
+      </Foo>
+=-=
+<Foo>
+  <:bar>
+    foobar
+  </:bar>
+</Foo>
+=-=-=
diff --git a/test/lisp/progmodes/heex-ts-mode-tests.el b/test/lisp/progmodes/heex-ts-mode-tests.el
new file mode 100644 (file)
index 0000000..b59126e
--- /dev/null
@@ -0,0 +1,9 @@
+(require 'ert)
+(require 'ert-x)
+(require 'treesit)
+
+(ert-deftest heex-ts-mode-test-indentation ()
+  (skip-unless (treesit-ready-p 'heex))
+  (ert-test-erts-file (ert-resource-file "indent.erts")))
+
+(provide 'heex-ts-mode-tests)