emacs-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

feature/android a517c24697d 2/2: Merge remote-tracking branch 'origin/ma


From: Po Lu
Subject: feature/android a517c24697d 2/2: Merge remote-tracking branch 'origin/master' into feature/android
Date: Sun, 12 Mar 2023 19:52:29 -0400 (EDT)

branch: feature/android
commit a517c24697d080475e2d531c8ce1d433aa44a9c6
Merge: 08a3749794b 75f04848a65
Author: Po Lu <luangruo@yahoo.com>
Commit: Po Lu <luangruo@yahoo.com>

    Merge remote-tracking branch 'origin/master' into feature/android
---
 admin/notes/tree-sitter/build-module/batch.sh      |   2 +
 admin/notes/tree-sitter/build-module/build.sh      |   6 +
 doc/lispref/lists.texi                             |  13 -
 etc/NEWS                                           |  14 +-
 lisp/emacs-lisp/bytecomp.el                        |  38 +-
 lisp/emacs-lisp/shortdoc.el                        |  35 +-
 lisp/net/tramp-adb.el                              |  51 +-
 lisp/net/tramp-archive.el                          |   4 +-
 lisp/net/tramp-crypt.el                            |  25 +-
 lisp/net/tramp-fuse.el                             |  29 +-
 lisp/net/tramp-gvfs.el                             |  23 +-
 lisp/net/tramp-sh.el                               |  72 +--
 lisp/net/tramp-smb.el                              |  26 +-
 lisp/net/tramp-sudoedit.el                         |  39 +-
 lisp/progmodes/eglot.el                            |   2 +-
 lisp/progmodes/elixir-ts-mode.el                   | 634 +++++++++++++++++++++
 lisp/progmodes/heex-ts-mode.el                     | 185 ++++++
 lisp/subr.el                                       |  55 --
 test/lisp/emacs-lisp/bytecomp-tests.el             |  28 +
 test/lisp/emacs-lisp/shortdoc-tests.el             |  15 +
 .../progmodes/elixir-ts-mode-resources/indent.erts | 308 ++++++++++
 test/lisp/progmodes/elixir-ts-mode-tests.el        |  31 +
 .../progmodes/heex-ts-mode-resources/indent.erts   |  47 ++
 test/lisp/progmodes/heex-ts-mode-tests.el          |   9 +
 test/lisp/subr-tests.el                            |  26 -
 25 files changed, 1483 insertions(+), 234 deletions(-)

diff --git a/admin/notes/tree-sitter/build-module/batch.sh 
b/admin/notes/tree-sitter/build-module/batch.sh
index 58272c74549..1d4076564dc 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -8,8 +8,10 @@ languages=(
     'css'
     'c-sharp'
     'dockerfile'
+    'elixir'
     'go'
     'go-mod'
+    'heex'
     'html'
     'javascript'
     'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh 
b/admin/notes/tree-sitter/build-module/build.sh
index 9dc674237ca..0832875168b 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -31,11 +31,17 @@ case "${lang}" in
     "cmake")
         org="uyha"
         ;;
+    "elixir")
+        org="elixir-lang"
+        ;;
     "go-mod")
         # The parser is called "gomod".
         lang="gomod"
         org="camdencheek"
         ;;
+    "heex")
+        org="phoenixframework"
+        ;;
     "typescript")
         sourcedir="tree-sitter-typescript/typescript/src"
         grammardir="tree-sitter-typescript/typescript"
diff --git a/doc/lispref/lists.texi b/doc/lispref/lists.texi
index 3478049c84f..a509325854f 100644
--- a/doc/lispref/lists.texi
+++ b/doc/lispref/lists.texi
@@ -708,19 +708,6 @@ non-@code{nil}, it copies vectors too (and operates 
recursively on
 their elements).  This function cannot cope with circular lists.
 @end defun
 
-@defun safe-copy-tree tree &optional vecp
-This function returns a copy of the tree @var{tree}.  If @var{tree} is
-a cons cell, this make a new cons cell with the same @sc{car} and
-@sc{cdr}, then recursively copies the @sc{car} and @sc{cdr} in the
-same way.
-
-Normally, when @var{tree} is anything other than a cons cell,
-@code{copy-tree} simply returns @var{tree}.  However, if @var{vecp} is
-non-@code{nil}, it copies vectors and records too (and operates
-recursively on their elements).  This function handles circular lists
-and vectors, and is thus slower than @code{copy-tree} for typical cases.
-@end defun
-
 @defun flatten-tree tree
 This function returns a ``flattened'' copy of @var{tree}, that is,
 a list containing all the non-@code{nil} terminal nodes, or leaves, of
diff --git a/etc/NEWS b/etc/NEWS
index 716376e1d99..2de8fb885a6 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -260,6 +260,15 @@ 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.
+
+---
+*** New major mode elixir-ts-mode'.
+A major mode based on the tree-sitter library for editing Elixir
+files.
+
 ---
 ** The highly accessible Modus themes collection has six items.
 The 'modus-operandi' and 'modus-vivendi' are the main themes that have
@@ -413,11 +422,6 @@ was to catch all errors, add an explicit handler for 
'error', or use
 This warning can be suppressed using 'with-suppressed-warnings' with
 the warning name 'suspicious'.
 
-+++
-** New function 'safe-copy-tree'
-This function is a version of copy-tree which handles circular lists
-and circular vectors/records.
-
 +++
 ** New function 'file-user-uid'.
 This function is like 'user-uid', but is aware of file name handlers,
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el
index 12850c27b88..a122e81ba3c 100644
--- a/lisp/emacs-lisp/bytecomp.el
+++ b/lisp/emacs-lisp/bytecomp.el
@@ -495,6 +495,42 @@ Return the compile-time value of FORM."
                     (cdr form)))
     (funcall non-toplevel-case form)))
 
+
+(defvar bytecomp--copy-tree-seen)
+
+(defun bytecomp--copy-tree-1 (tree)
+  ;; TREE must be a cons.
+  (or (gethash tree bytecomp--copy-tree-seen)
+      (let* ((next (cdr tree))
+             (result (cons nil next))
+             (copy result))
+        (while (progn
+                 (puthash tree copy bytecomp--copy-tree-seen)
+                 (let ((a (car tree)))
+                   (setcar copy (if (consp a)
+                                    (bytecomp--copy-tree-1 a)
+                                  a)))
+                 (and (consp next)
+                      (let ((tail (gethash next bytecomp--copy-tree-seen)))
+                        (if tail
+                            (progn (setcdr copy tail)
+                                   nil)
+                          (setq tree next)
+                          (setq next (cdr next))
+                          (let ((prev copy))
+                            (setq copy (cons nil next))
+                            (setcdr prev copy)
+                            t))))))
+        result)))
+
+(defun bytecomp--copy-tree (tree)
+  "Make a copy of TREE, preserving any circular structure therein.
+Only conses are traversed and duplicated, not arrays or any other structure."
+  (if (consp tree)
+      (let ((bytecomp--copy-tree-seen (make-hash-table :test #'eq)))
+        (bytecomp--copy-tree-1 tree))
+    tree))
+
 (defconst byte-compile-initial-macro-environment
   `(
     ;; (byte-compiler-options . (lambda (&rest forms)
@@ -534,7 +570,7 @@ Return the compile-time value of FORM."
                                        form
                                        macroexpand-all-environment)))
                                 (eval (byte-run-strip-symbol-positions
-                                       (safe-copy-tree expanded))
+                                       (bytecomp--copy-tree expanded))
                                       lexical-binding)
                                 expanded)))))
     (with-suppressed-warnings
diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el
index 6e3ebc7c6a2..9a6f5dd12ce 100644
--- a/lisp/emacs-lisp/shortdoc.el
+++ b/lisp/emacs-lisp/shortdoc.el
@@ -1621,13 +1621,38 @@ doesn't has any shortdoc information."
 You can add this function to the `help-fns-describe-function-functions'
 hook to show examples of using FUNCTION in *Help* buffers produced
 by \\[describe-function]."
-  (let ((examples (shortdoc-function-examples function))
-        (times 0))
+  (let* ((examples (shortdoc-function-examples function))
+         (num-examples (length examples))
+         (times 0))
     (dolist (example examples)
       (when (zerop times)
-        (if (eq (length examples) 1)
-            (insert "\n  Example:\n\n")
-          (insert "\n  Examples:\n\n")))
+        (if (> num-examples 1)
+            (insert "\n  Examples:\n\n")
+          ;; Some functions have more than one example per group.
+          ;; Count the number of arrows to know if we need to
+          ;; pluralize "Example".
+          (let* ((text (cdr example))
+                 (count 0)
+                 (pos 0)
+                 (end (length text))
+                 (double-arrow (if (char-displayable-p ?⇒)
+                                   "    ⇒"
+                                 "    =>"))
+                 (double-arrow-example (if (char-displayable-p ?⇒)
+                                           "    e.g. ⇒"
+                                         "    e.g. =>"))
+                 (single-arrow (if (char-displayable-p ?→)
+                                   "    →"
+                                 "    ->")))
+            (while (and (< pos end)
+                        (or (string-match double-arrow text pos)
+                            (string-match double-arrow-example text pos)
+                            (string-match single-arrow text pos)))
+              (setq count (1+ count)
+                    pos (match-end 0)))
+            (if (> count 1)
+                (insert "\n  Examples:\n\n")
+              (insert "\n  Example:\n\n")))))
       (setq times (1+ times))
       (insert "  ")
       (insert (cdr example))
diff --git a/lisp/net/tramp-adb.el b/lisp/net/tramp-adb.el
index f8c38859477..64f45e7958d 100644
--- a/lisp/net/tramp-adb.el
+++ b/lisp/net/tramp-adb.el
@@ -432,31 +432,32 @@ Emacs dired can't find files."
 
 (defun tramp-adb-handle-file-name-all-completions (filename directory)
   "Like `file-name-all-completions' for Tramp files."
-  (all-completions
-   filename
-   (with-parsed-tramp-file-name (expand-file-name directory) nil
-     (with-tramp-file-property v localname "file-name-all-completions"
-       (tramp-adb-send-command
-       v (format "%s -a %s | cat"
-                 (tramp-adb-get-ls-command v)
-                 (tramp-shell-quote-argument localname)))
-       (mapcar
-       (lambda (f)
-         (if (file-directory-p (expand-file-name f directory))
-             (file-name-as-directory f)
-           f))
-       (with-current-buffer (tramp-get-buffer v)
-         (delete-dups
-          (append
-           ;; On some file systems like "sdcard", "." and ".." are
-           ;; not included.  We fix this by `delete-dups'.
-           '("." "..")
-           (delq
-            nil
-            (mapcar
-             (lambda (l)
-               (and (not (string-match-p (rx bol (* blank) eol) l)) l))
-             (split-string (buffer-string) "\n")))))))))))
+  (ignore-error file-missing
+    (all-completions
+     filename
+     (with-parsed-tramp-file-name (expand-file-name directory) nil
+       (with-tramp-file-property v localname "file-name-all-completions"
+        (tramp-adb-send-command
+         v (format "%s -a %s | cat"
+                   (tramp-adb-get-ls-command v)
+                   (tramp-shell-quote-argument localname)))
+        (mapcar
+         (lambda (f)
+           (if (file-directory-p (expand-file-name f directory))
+               (file-name-as-directory f)
+             f))
+         (with-current-buffer (tramp-get-buffer v)
+           (delete-dups
+            (append
+             ;; On some file systems like "sdcard", "." and ".." are
+             ;; not included.  We fix this by `delete-dups'.
+             '("." "..")
+             (delq
+              nil
+              (mapcar
+               (lambda (l)
+                 (and (not (string-match-p (rx bol (* blank) eol) l)) l))
+               (split-string (buffer-string) "\n"))))))))))))
 
 (defun tramp-adb-handle-file-local-copy (filename)
   "Like `file-local-copy' for Tramp files."
diff --git a/lisp/net/tramp-archive.el b/lisp/net/tramp-archive.el
index 97adb36c4af..c2175612fa8 100644
--- a/lisp/net/tramp-archive.el
+++ b/lisp/net/tramp-archive.el
@@ -650,7 +650,9 @@ offered."
 
 (defun tramp-archive-handle-file-name-all-completions (filename directory)
   "Like `file-name-all-completions' for file archives."
-  (file-name-all-completions filename (tramp-archive-gvfs-file-name 
directory)))
+  (ignore-error file-missing
+    (file-name-all-completions
+     filename (tramp-archive-gvfs-file-name directory))))
 
 (defun tramp-archive-handle-file-readable-p (filename)
   "Like `file-readable-p' for file archives."
diff --git a/lisp/net/tramp-crypt.el b/lisp/net/tramp-crypt.el
index afd3166d161..d0f1f1b8184 100644
--- a/lisp/net/tramp-crypt.el
+++ b/lisp/net/tramp-crypt.el
@@ -730,18 +730,19 @@ absolute file names."
 
 (defun tramp-crypt-handle-file-name-all-completions (filename directory)
   "Like `file-name-all-completions' for Tramp files."
-  (all-completions
-   filename
-   (let* (completion-regexp-list
-         tramp-crypt-enabled
-         (directory (file-name-as-directory directory))
-         (enc-dir (tramp-crypt-encrypt-file-name directory)))
-     (mapcar
-      (lambda (x)
-       (substring
-        (tramp-crypt-decrypt-file-name (concat enc-dir x))
-        (length directory)))
-      (file-name-all-completions "" enc-dir)))))
+  (ignore-error file-missing
+    (all-completions
+     filename
+     (let* (completion-regexp-list
+           tramp-crypt-enabled
+           (directory (file-name-as-directory directory))
+           (enc-dir (tramp-crypt-encrypt-file-name directory)))
+       (mapcar
+       (lambda (x)
+         (substring
+          (tramp-crypt-decrypt-file-name (concat enc-dir x))
+          (length directory)))
+       (file-name-all-completions "" enc-dir))))))
 
 (defun tramp-crypt-handle-file-readable-p (filename)
   "Like `file-readable-p' for Tramp files."
diff --git a/lisp/net/tramp-fuse.el b/lisp/net/tramp-fuse.el
index b846caadc18..8112e564a2c 100644
--- a/lisp/net/tramp-fuse.el
+++ b/lisp/net/tramp-fuse.el
@@ -98,20 +98,21 @@
 (defun tramp-fuse-handle-file-name-all-completions (filename directory)
   "Like `file-name-all-completions' for Tramp files."
   (tramp-fuse-remove-hidden-files
-   (all-completions
-    filename
-    (delete-dups
-     (append
-      (file-name-all-completions
-       filename (tramp-fuse-local-file-name directory))
-      ;; Some storage systems do not return "." and "..".
-      (let (result)
-       (dolist (item '(".." ".") result)
-         (when (string-prefix-p filename item)
-           (catch 'match
-             (dolist (elt completion-regexp-list)
-               (unless (string-match-p elt item) (throw 'match nil)))
-             (setq result (cons (concat item "/") result)))))))))))
+   (ignore-error file-missing
+     (all-completions
+      filename
+      (delete-dups
+       (append
+       (file-name-all-completions
+        filename (tramp-fuse-local-file-name directory))
+       ;; Some storage systems do not return "." and "..".
+       (let (result)
+         (dolist (item '(".." ".") result)
+           (when (string-prefix-p filename item)
+             (catch 'match
+               (dolist (elt completion-regexp-list)
+                 (unless (string-match-p elt item) (throw 'match nil)))
+               (setq result (cons (concat item "/") result))))))))))))
 
 ;; This function isn't used.
 (defun tramp-fuse-handle-insert-directory
diff --git a/lisp/net/tramp-gvfs.el b/lisp/net/tramp-gvfs.el
index b9639c1e7f7..266724c587f 100644
--- a/lisp/net/tramp-gvfs.el
+++ b/lisp/net/tramp-gvfs.el
@@ -1418,16 +1418,19 @@ If FILE-SYSTEM is non-nil, return file system 
attributes."
 (defun tramp-gvfs-handle-file-name-all-completions (filename directory)
   "Like `file-name-all-completions' for Tramp files."
   (unless (tramp-compat-string-search "/" filename)
-    (all-completions
-     filename
-     (with-parsed-tramp-file-name (expand-file-name directory) nil
-       (with-tramp-file-property v localname "file-name-all-completions"
-         (let ((result '("./" "../")))
-           ;; Get a list of directories and files.
-          (dolist (item (tramp-gvfs-get-directory-attributes directory) result)
-            (if (string-equal (cdr (assoc "type" item)) "directory")
-                (push (file-name-as-directory (car item)) result)
-              (push (car item) result)))))))))
+    (ignore-error file-missing
+      (all-completions
+       filename
+       (with-parsed-tramp-file-name (expand-file-name directory) nil
+        (with-tramp-file-property v localname "file-name-all-completions"
+           (let ((result '("./" "../")))
+             ;; Get a list of directories and files.
+            (dolist (item
+                     (tramp-gvfs-get-directory-attributes directory)
+                     result)
+              (if (string-equal (cdr (assoc "type" item)) "directory")
+                  (push (file-name-as-directory (car item)) result)
+                (push (car item) result))))))))))
 
 (defun tramp-gvfs-handle-file-notify-add-watch (file-name flags _callback)
   "Like `file-notify-add-watch' for Tramp files."
diff --git a/lisp/net/tramp-sh.el b/lisp/net/tramp-sh.el
index 3ae5208154a..a854ff42b0d 100644
--- a/lisp/net/tramp-sh.el
+++ b/lisp/net/tramp-sh.el
@@ -1767,41 +1767,43 @@ ID-FORMAT valid values are `string' and `integer'."
   (with-parsed-tramp-file-name (expand-file-name directory) nil
     (when (and (not (tramp-compat-string-search "/" filename))
               (tramp-connectable-p v))
-      (all-completions
-       filename
-       (with-tramp-file-property v localname "file-name-all-completions"
-        (let (result)
-          ;; Get a list of directories and files, including reliably
-          ;; tagging the directories with a trailing "/".  Because I
-          ;; rock.  --daniel@danann.net
-          (when (tramp-send-command-and-check
-                 v
-                 (if (tramp-get-remote-perl v)
-                     (progn
-                       (tramp-maybe-send-script
-                        v tramp-perl-file-name-all-completions
-                        "tramp_perl_file_name_all_completions")
-                       (format "tramp_perl_file_name_all_completions %s"
-                               (tramp-shell-quote-argument localname)))
-
-                   (format (concat
-                            "cd %s 2>&1 && %s -a 2>%s"
-                            " | while IFS= read f; do"
-                            " if %s -d \"$f\" 2>%s;"
-                            " then \\echo \"$f/\"; else \\echo \"$f\"; fi;"
-                            " done")
-                           (tramp-shell-quote-argument localname)
-                           (tramp-get-ls-command v)
-                           (tramp-get-remote-null-device v)
-                           (tramp-get-test-command v)
-                           (tramp-get-remote-null-device v))))
-
-            ;; Now grab the output.
-            (with-current-buffer (tramp-get-buffer v)
-              (goto-char (point-max))
-              (while (zerop (forward-line -1))
-                (push (buffer-substring (point) (line-end-position)) result)))
-            result)))))))
+    (unless (tramp-compat-string-search "/" filename)
+      (ignore-error file-missing
+       (all-completions
+        filename
+        (with-tramp-file-property v localname "file-name-all-completions"
+          (let (result)
+            ;; Get a list of directories and files, including
+            ;; reliably tagging the directories with a trailing "/".
+            ;; Because I rock.  --daniel@danann.net
+            (when (tramp-send-command-and-check
+                   v
+                   (if (tramp-get-remote-perl v)
+                       (progn
+                         (tramp-maybe-send-script
+                          v tramp-perl-file-name-all-completions
+                          "tramp_perl_file_name_all_completions")
+                         (format "tramp_perl_file_name_all_completions %s"
+                                 (tramp-shell-quote-argument localname)))
+
+                     (format (concat
+                              "cd %s 2>&1 && %s -a 2>%s"
+                              " | while IFS= read f; do"
+                              " if %s -d \"$f\" 2>%s;"
+                              " then \\echo \"$f/\"; else \\echo \"$f\"; fi;"
+                              " done")
+                             (tramp-shell-quote-argument localname)
+                             (tramp-get-ls-command v)
+                             (tramp-get-remote-null-device v)
+                             (tramp-get-test-command v)
+                             (tramp-get-remote-null-device v))))
+
+              ;; Now grab the output.
+              (with-current-buffer (tramp-get-buffer v)
+                (goto-char (point-max))
+                (while (zerop (forward-line -1))
+                  (push (buffer-substring (point) (line-end-position)) 
result)))
+              result)))))))))
 
 ;; cp, mv and ln
 
diff --git a/lisp/net/tramp-smb.el b/lisp/net/tramp-smb.el
index 2a69465224f..1aa4520eeb6 100644
--- a/lisp/net/tramp-smb.el
+++ b/lisp/net/tramp-smb.el
@@ -976,18 +976,20 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are 
completely ignored."
 ;; files.
 (defun tramp-smb-handle-file-name-all-completions (filename directory)
   "Like `file-name-all-completions' for Tramp files."
-  (all-completions
-   filename
-   (with-parsed-tramp-file-name (expand-file-name directory) nil
-     (with-tramp-file-property v localname "file-name-all-completions"
-       (delete-dups
-       (mapcar
-        (lambda (x)
-          (list
-           (if (tramp-compat-string-search "d" (nth 1 x))
-               (file-name-as-directory (nth 0 x))
-             (nth 0 x))))
-        (tramp-smb-get-file-entries directory)))))))
+  (ignore-error file-missing
+    (all-completions
+     filename
+     (when (file-directory-p directory)
+       (with-parsed-tramp-file-name (expand-file-name directory) nil
+        (with-tramp-file-property v localname "file-name-all-completions"
+          (delete-dups
+           (mapcar
+            (lambda (x)
+              (list
+               (if (tramp-compat-string-search "d" (nth 1 x))
+                   (file-name-as-directory (nth 0 x))
+                 (nth 0 x))))
+            (tramp-smb-get-file-entries directory)))))))))
 
 (defun tramp-smb-handle-file-system-info (filename)
   "Like `file-system-info' for Tramp files."
diff --git a/lisp/net/tramp-sudoedit.el b/lisp/net/tramp-sudoedit.el
index fa1689d6851..abb9afc570b 100644
--- a/lisp/net/tramp-sudoedit.el
+++ b/lisp/net/tramp-sudoedit.el
@@ -460,26 +460,27 @@ the result will be a local, non-Tramp, file name."
 
 (defun tramp-sudoedit-handle-file-name-all-completions (filename directory)
   "Like `file-name-all-completions' for Tramp files."
-  (all-completions
-   filename
-   (with-parsed-tramp-file-name (expand-file-name directory) nil
-     (with-tramp-file-property v localname "file-name-all-completions"
-       (tramp-sudoedit-send-command
-       v "ls" "-a1" "--quoting-style=literal" "--show-control-chars"
-       (if (tramp-string-empty-or-nil-p localname)
-           "" (file-name-unquote localname)))
-       (mapcar
-       (lambda (f)
-         (if (ignore-errors (file-directory-p (expand-file-name f directory)))
-             (file-name-as-directory f)
-           f))
-       (delq
-        nil
+  (ignore-error file-missing
+    (all-completions
+     filename
+     (with-parsed-tramp-file-name (expand-file-name directory) nil
+       (with-tramp-file-property v localname "file-name-all-completions"
+        (tramp-sudoedit-send-command
+         v "ls" "-a1" "--quoting-style=literal" "--show-control-chars"
+         (if (tramp-string-empty-or-nil-p localname)
+             "" (file-name-unquote localname)))
         (mapcar
-         (lambda (l) (and (not (string-match-p (rx bol (* blank) eol) l)) l))
-         (split-string
-          (tramp-get-buffer-string (tramp-get-connection-buffer v))
-          "\n" 'omit))))))))
+         (lambda (f)
+           (if (ignore-errors (file-directory-p (expand-file-name f 
directory)))
+               (file-name-as-directory f)
+             f))
+         (delq
+          nil
+          (mapcar
+           (lambda (l) (and (not (string-match-p (rx bol (* blank) eol) l)) l))
+           (split-string
+            (tramp-get-buffer-string (tramp-get-connection-buffer v))
+            "\n" 'omit)))))))))
 
 (defun tramp-sudoedit-handle-file-readable-p (filename)
   "Like `file-readable-p' for Tramp files."
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el
index 2f8d2002cd3..7b2341f3f49 100644
--- a/lisp/progmodes/eglot.el
+++ b/lisp/progmodes/eglot.el
@@ -221,7 +221,7 @@ chosen (interactively or automatically)."
                                 ((java-mode java-ts-mode) . ("jdtls"))
                                 (dart-mode . ("dart" "language-server"
                                               "--client-id" 
"emacs.eglot-dart"))
-                                (elixir-mode . ("language_server.sh"))
+                                ((elixir-ts-mode elixir-mode) . 
("language_server.sh"))
                                 (ada-mode . ("ada_language_server"))
                                 (scala-mode . ,(eglot-alternatives
                                                 '("metals" "metals-emacs")))
diff --git a/lisp/progmodes/elixir-ts-mode.el b/lisp/progmodes/elixir-ts-mode.el
new file mode 100644
index 00000000000..8adf647b081
--- /dev/null
+++ b/lisp/progmodes/elixir-ts-mode.el
@@ -0,0 +1,634 @@
+;;; elixir-ts-mode.el --- Major mode for Elixir 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 `elixir-ts-mode' which is a major mode for editing
+;; Elixir files and embedded HEEx templates that uses Tree Sitter to parse
+;; the language.
+;;
+;; This package is compatible with and was tested against the tree-sitter 
grammar
+;; for Elixir found at https://github.com/elixir-lang/tree-sitter-elixir.
+;;
+;; Features
+;;
+;; * Indent
+;;
+;; `elixir-ts-mode' tries to replicate the indentation provided by
+;; mix format, but will come with some minor differences.
+;;
+;; * IMenu
+;; * Navigation
+;; * Which-fun
+
+;;; Code:
+
+(require 'treesit)
+(require 'heex-ts-mode)
+(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-child-by-field-name "treesit.c")
+(declare-function treesit-parser-language "treesit.c")
+(declare-function treesit-parser-included-ranges "treesit.c")
+(declare-function treesit-parser-list "treesit.c")
+(declare-function treesit-node-parent "treesit.c")
+(declare-function treesit-node-start "treesit.c")
+(declare-function treesit-query-compile "treesit.c")
+(declare-function treesit-node-eq "treesit.c")
+(declare-function treesit-node-prev-sibling "treesit.c")
+
+(defgroup elixir-ts nil
+  "Major mode for editing Elixir code."
+  :prefix "elixir-ts-"
+  :group 'languages)
+
+(defcustom elixir-ts-indent-offset 2
+  "Indentation of Elixir statements."
+  :version "30.1"
+  :type 'integer
+  :safe 'integerp
+  :group 'elixir-ts)
+
+(defface elixir-ts-font-comment-doc-identifier-face
+  '((t (:inherit font-lock-doc-face)))
+  "Face used for @comment.doc tags in Elixir files.")
+
+(defface elixir-ts-font-comment-doc-attribute-face
+  '((t (:inherit font-lock-doc-face)))
+  "Face used for @comment.doc.__attribute__ tags in Elixir files.")
+
+(defface elixir-ts-font-sigil-name-face
+  '((t (:inherit font-lock-string-face)))
+  "Face used for @__name__ tags in Elixir files.")
+
+(defconst elixir-ts--sexp-regexp
+  (rx bol
+      (or "call" "stab_clause" "binary_operator" "list" "tuple" "map" "pair"
+          "sigil" "string" "atom" "pair" "alias" "arguments" "atom" 
"identifier"
+          "boolean" "quoted_content")
+      eol))
+
+(defconst elixir-ts--test-definition-keywords
+  '("describe" "test"))
+
+(defconst elixir-ts--definition-keywords
+  '("def" "defdelegate" "defexception" "defguard" "defguardp"
+    "defimpl" "defmacro" "defmacrop" "defmodule" "defn" "defnp"
+    "defoverridable" "defp" "defprotocol" "defstruct"))
+
+(defconst elixir-ts--definition-keywords-re
+  (concat "^" (regexp-opt elixir-ts--definition-keywords) "$"))
+
+(defconst elixir-ts--kernel-keywords
+  '("alias" "case" "cond" "else" "for" "if" "import" "quote"
+    "raise" "receive" "require" "reraise" "super" "throw" "try"
+    "unless" "unquote" "unquote_splicing" "use" "with"))
+
+(defconst elixir-ts--kernel-keywords-re
+  (concat "^" (regexp-opt elixir-ts--kernel-keywords) "$"))
+
+(defconst elixir-ts--builtin-keywords
+  '("__MODULE__" "__DIR__" "__ENV__" "__CALLER__" "__STACKTRACE__"))
+
+(defconst elixir-ts--builtin-keywords-re
+  (concat "^" (regexp-opt elixir-ts--builtin-keywords) "$"))
+
+(defconst elixir-ts--doc-keywords
+  '("moduledoc" "typedoc" "doc"))
+
+(defconst elixir-ts--doc-keywords-re
+  (concat "^" (regexp-opt elixir-ts--doc-keywords) "$"))
+
+(defconst elixir-ts--reserved-keywords
+  '("when" "and" "or" "not" "in"
+    "not in" "fn" "do" "end" "catch" "rescue" "after" "else"))
+
+(defconst elixir-ts--reserved-keywords-re
+  (concat "^" (regexp-opt elixir-ts--reserved-keywords) "$"))
+
+(defconst elixir-ts--reserved-keywords-vector
+  (apply #'vector elixir-ts--reserved-keywords))
+
+(defvar elixir-ts--capture-anonymous-function-end
+  (when (treesit-available-p)
+    (treesit-query-compile 'elixir '((anonymous_function "end" @end)))))
+
+(defvar elixir-ts--capture-operator-parent
+  (when (treesit-available-p)
+    (treesit-query-compile 'elixir '((binary_operator operator: _ @val)))))
+
+(defvar elixir-ts--syntax-table
+  (let ((table (make-syntax-table)))
+    (modify-syntax-entry ?| "." table)
+    (modify-syntax-entry ?- "." table)
+    (modify-syntax-entry ?+ "." table)
+    (modify-syntax-entry ?* "." table)
+    (modify-syntax-entry ?/ "." table)
+    (modify-syntax-entry ?< "." table)
+    (modify-syntax-entry ?> "." table)
+    (modify-syntax-entry ?_ "_" table)
+    (modify-syntax-entry ?? "w" table)
+    (modify-syntax-entry ?~ "w" table)
+    (modify-syntax-entry ?! "_" table)
+    (modify-syntax-entry ?' "\"" table)
+    (modify-syntax-entry ?\" "\"" table)
+    (modify-syntax-entry ?# "<" table)
+    (modify-syntax-entry ?\n ">" table)
+    (modify-syntax-entry ?\( "()" table)
+    (modify-syntax-entry ?\) ")(" table)
+    (modify-syntax-entry ?\{ "(}" table)
+    (modify-syntax-entry ?\} "){" table)
+    (modify-syntax-entry ?\[ "(]" table)
+    (modify-syntax-entry ?\] ")[" table)
+    (modify-syntax-entry ?: "'" table)
+    (modify-syntax-entry ?@ "'" table)
+    table)
+  "Syntax table for `elixir-ts-mode'.")
+
+(defun elixir-ts--argument-indent-offset (node _parent &rest _)
+  "Return the argument offset position for NODE."
+  (if (treesit-node-prev-sibling node t) 0 elixir-ts-indent-offset))
+
+(defun elixir-ts--argument-indent-anchor (node parent &rest _)
+  "Return the argument anchor position for NODE and PARENT."
+  (let ((first-sibling (treesit-node-child parent 0 t)))
+    (if (and first-sibling (not (treesit-node-eq first-sibling node)))
+        (treesit-node-start first-sibling)
+      (elixir-ts--parent-expression-start node parent))))
+
+(defun elixir-ts--parent-expression-start (_node parent &rest _)
+  "Return the indentation expression start for NODE and PARENT."
+  ;; If the parent is the first expression on the line return the
+  ;; parent start of node position, otherwise use the parent call
+  ;; start if available.
+  (if (eq (treesit-node-start parent)
+          (save-excursion
+            (goto-char (treesit-node-start parent))
+            (back-to-indentation)
+            (point)))
+      (treesit-node-start parent)
+    (let ((expr-parent
+           (treesit-parent-until
+            parent
+            (lambda (n)
+              (member (treesit-node-type n)
+                      '("call" "binary_operator" "keywords" "list"))))))
+      (save-excursion
+        (goto-char (treesit-node-start expr-parent))
+        (back-to-indentation)
+        (if (looking-at "|>")
+            (point)
+          (treesit-node-start expr-parent))))))
+
+(defvar elixir-ts--indent-rules
+  (let ((offset elixir-ts-indent-offset))
+    `((elixir
+       ((parent-is "^source$") column-0 0)
+       ((parent-is "^string$") parent-bol 0)
+       ((parent-is "^quoted_content$")
+        (lambda (_n parent bol &rest _)
+          (save-excursion
+            (back-to-indentation)
+            (if (bolp)
+                (progn
+                  (goto-char (treesit-node-start parent))
+                  (back-to-indentation)
+                  (point))
+              (point))))
+        0)
+       ((node-is "^|>$") parent-bol 0)
+       ((node-is "^|$") parent-bol 0)
+       ((node-is "^]$") ,'elixir-ts--parent-expression-start 0)
+       ((node-is "^}$") ,'elixir-ts--parent-expression-start 0)
+       ((node-is "^)$") ,'elixir-ts--parent-expression-start 0)
+       ((node-is "^else_block$") grand-parent 0)
+       ((node-is "^catch_block$") grand-parent 0)
+       ((node-is "^rescue_block$") grand-parent 0)
+       ((node-is "^after_block$") grand-parent 0)
+       ((parent-is "^else_block$") parent ,offset)
+       ((parent-is "^catch_block$") parent ,offset)
+       ((parent-is "^rescue_block$") parent ,offset)
+       ((parent-is "^rescue_block$") parent ,offset)
+       ((parent-is "^after_block$") parent ,offset)
+       ((parent-is "^access_call$")
+        ,'elixir-ts--argument-indent-anchor
+        ,'elixir-ts--argument-indent-offset)
+       ((parent-is "^tuple$")
+        ,'elixir-ts--argument-indent-anchor
+        ,'elixir-ts--argument-indent-offset)
+       ((parent-is "^list$")
+        ,'elixir-ts--argument-indent-anchor
+        ,'elixir-ts--argument-indent-offset)
+       ((parent-is "^pair$") parent ,offset)
+       ((parent-is "^map_content$") parent-bol 0)
+       ((parent-is "^map$") ,'elixir-ts--parent-expression-start ,offset)
+       ((node-is "^stab_clause$") parent-bol ,offset)
+       ((query ,elixir-ts--capture-operator-parent) grand-parent 0)
+       ((node-is "^when$") parent 0)
+       ((node-is "^keywords$") parent-bol ,offset)
+       ((parent-is "^body$")
+        (lambda (node parent _)
+          (save-excursion
+            ;; The grammar adds a comment outside of the body, so we have to 
indent
+            ;; to the grand-parent if it is available.
+            (goto-char (treesit-node-start
+                        (or (treesit-node-parent parent) (parent))))
+            (back-to-indentation)
+            (point)))
+        ,offset)
+       ((parent-is "^arguments$")
+        ,'elixir-ts--argument-indent-anchor
+        ,'elixir-ts--argument-indent-offset)
+       ;; Handle incomplete maps when parent is ERROR.
+       ((n-p-gp "^binary_operator$" "ERROR" nil) parent-bol 0)
+       ;; When there is an ERROR, just indent to prev-line.
+       ((parent-is "ERROR") prev-line 0)
+       ((node-is "^binary_operator$")
+        (lambda (node parent &rest _)
+          (let ((top-level
+                 (treesit-parent-while
+                  node
+                  (lambda (node)
+                    (equal (treesit-node-type node)
+                           "binary_operator")))))
+            (if (treesit-node-eq top-level node)
+                (elixir-ts--parent-expression-start node parent)
+              (treesit-node-start top-level))))
+        (lambda (node parent _)
+          (cond
+           ((equal (treesit-node-type parent) "do_block")
+            ,offset)
+           ((equal (treesit-node-type parent) "binary_operator")
+            ,offset)
+           (t 0))))
+       ((parent-is "^binary_operator$")
+        (lambda (node parent bol &rest _)
+          (treesit-node-start
+           (treesit-parent-while
+            parent
+            (lambda (node)
+              (equal (treesit-node-type node) "binary_operator")))))
+        ,offset)
+       ((node-is "^pair$") first-sibling 0)
+       ((query ,elixir-ts--capture-anonymous-function-end) parent-bol 0)
+       ((node-is "^end$") standalone-parent 0)
+       ((parent-is "^do_block$") grand-parent ,offset)
+       ((parent-is "^anonymous_function$")
+        elixir-ts--treesit-anchor-grand-parent-bol ,offset)
+       ((parent-is "^else_block$") parent ,offset)
+       ((parent-is "^rescue_block$") parent ,offset)
+       ((parent-is "^catch_block$") parent ,offset)
+       ((parent-is "^keywords$") parent-bol 0)
+       ((node-is "^call$") parent-bol ,offset)
+       ((node-is "^comment$") parent-bol ,offset)))))
+
+(defvar elixir-ts--font-lock-settings
+  (treesit-font-lock-rules
+   :language 'elixir
+   :feature 'elixir-comment
+   '((comment) @font-lock-comment-face)
+
+   :language 'elixir
+   :feature 'elixir-string
+   :override t
+   '([(string) (charlist)] @font-lock-string-face)
+
+   :language 'elixir
+   :feature 'elixir-string-interpolation
+   :override t
+   '((string
+      [
+       quoted_end: _ @font-lock-string-face
+       quoted_start: _ @font-lock-string-face
+       (quoted_content) @font-lock-string-face
+       (interpolation
+        "#{" @font-lock-regexp-grouping-backslash "}"
+        @font-lock-regexp-grouping-backslash)
+       ])
+     (charlist
+      [
+       quoted_end: _ @font-lock-string-face
+       quoted_start: _ @font-lock-string-face
+       (quoted_content) @font-lock-string-face
+       (interpolation
+        "#{" @font-lock-regexp-grouping-backslash "}"
+        @font-lock-regexp-grouping-backslash)
+       ]))
+
+   :language 'elixir
+   :feature 'elixir-keyword
+   `(,elixir-ts--reserved-keywords-vector
+     @font-lock-keyword-face
+     (binary_operator
+      operator: _ @font-lock-keyword-face
+      (:match ,elixir-ts--reserved-keywords-re @font-lock-keyword-face)))
+
+   :language 'elixir
+   :feature 'elixir-doc
+   :override t
+   `((unary_operator
+      operator: "@" @elixir-ts-font-comment-doc-attribute-face
+      operand: (call
+                target: (identifier) 
@elixir-ts-font-comment-doc-identifier-face
+                ;; Arguments can be optional, so adding another
+                ;; entry without arguments.
+                ;; If we don't handle then we don't apply font
+                ;; and the non doc fortification query will take specify
+                ;; a more specific font which takes precedence.
+                (arguments
+                 [
+                  (string) @font-lock-doc-face
+                  (charlist) @font-lock-doc-face
+                  (sigil) @font-lock-doc-face
+                  (boolean) @font-lock-doc-face
+                  ]))
+      (:match ,elixir-ts--doc-keywords-re
+              @elixir-ts-font-comment-doc-identifier-face))
+     (unary_operator
+      operator: "@" @elixir-ts-font-comment-doc-attribute-face
+      operand: (call
+                target: (identifier) 
@elixir-ts-font-comment-doc-identifier-face)
+      (:match ,elixir-ts--doc-keywords-re
+              @elixir-ts-font-comment-doc-identifier-face)))
+
+   :language 'elixir
+   :feature 'elixir-unary-operator
+   `((unary_operator operator: "@" @font-lock-preprocessor-face
+                     operand: [
+                               (identifier)  @font-lock-preprocessor-face
+                               (call target: (identifier)
+                                     @font-lock-preprocessor-face)
+                               (boolean)  @font-lock-preprocessor-face
+                               (nil)  @font-lock-preprocessor-face
+                               ])
+
+     (unary_operator operator: "&") @font-lock-function-name-face
+     (operator_identifier) @font-lock-operator-face)
+
+   :language 'elixir
+   :feature 'elixir-operator
+   '((binary_operator operator: _ @font-lock-operator-face)
+     (dot operator: _ @font-lock-operator-face)
+     (stab_clause operator: _ @font-lock-operator-face)
+
+     [(boolean) (nil)] @font-lock-constant-face
+     [(integer) (float)] @font-lock-number-face
+     (alias) @font-lock-type-face
+     (call target: (dot left: (atom) @font-lock-type-face))
+     (char) @font-lock-constant-face
+     [(atom) (quoted_atom)] @font-lock-type-face
+     [(keyword) (quoted_keyword)] @font-lock-builtin-face)
+
+   :language 'elixir
+   :feature 'elixir-call
+   `((call
+      target: (identifier) @font-lock-keyword-face
+      (:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face))
+     (call
+      target: (identifier) @font-lock-keyword-face
+      (:match ,elixir-ts--kernel-keywords-re @font-lock-keyword-face))
+     (call
+      target: [(identifier) @font-lock-function-name-face
+               (dot right: (identifier) @font-lock-keyword-face)])
+     (call
+      target: (identifier) @font-lock-keyword-face
+      (arguments
+       [
+        (identifier) @font-lock-keyword-face
+        (binary_operator
+         left: (identifier) @font-lock-keyword-face
+         operator: "when")
+        ])
+      (:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face))
+     (call
+      target: (identifier) @font-lock-keyword-face
+      (arguments
+       (binary_operator
+        operator: "|>"
+        right: (identifier)))
+      (:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face)))
+
+   :language 'elixir
+   :feature 'elixir-constant
+   `((binary_operator operator: "|>" right: (identifier)
+                      @font-lock-function-name-face)
+     ((identifier) @font-lock-keyword-face
+      (:match ,elixir-ts--builtin-keywords-re
+              @font-lock-keyword-face))
+     ((identifier) @font-lock-comment-face
+      (:match "^_" @font-lock-comment-face))
+     (identifier) @font-lock-function-name-face
+     ["%"] @font-lock-keyward-face
+     ["," ";"] @font-lock-keyword-face
+     ["(" ")" "[" "]" "{" "}" "<<" ">>"] @font-lock-keyword-face)
+
+   :language 'elixir
+   :feature 'elixir-sigil
+   :override t
+   `((sigil
+      (sigil_name) @elixir-ts-font-sigil-name-face
+      quoted_start: _ @font-lock-string-face
+      quoted_end: _ @font-lock-string-face
+      (:match "^[sSwWpP]$" @elixir-ts-font-sigil-name-face))
+     @font-lock-string-face
+     (sigil
+      (sigil_name) @elixir-ts-font-sigil-name-face
+      quoted_start: _ @font-lock-regex-face
+      quoted_end: _ @font-lock-regex-face
+      (:match "^[rR]$" @elixir-ts-font-sigil-name-face))
+     @font-lock-regex-face
+     (sigil
+      "~" @font-lock-string-face
+      (sigil_name) @elixir-ts-font-sigil-name-face
+      quoted_start: _ @font-lock-string-face
+      quoted_end: _ @font-lock-string-face
+      (:match "^[HF]$" @elixir-ts-font-sigil-name-face)))
+
+   :language 'elixir
+   :feature 'elixir-string-escape
+   :override t
+   `((escape_sequence) @font-lock-regexp-grouping-backslash))
+  "Tree-sitter font-lock settings.")
+
+(defvar elixir-ts--treesit-range-rules
+  (when (treesit-available-p)
+    (treesit-range-rules
+     :embed 'heex
+     :host 'elixir
+     '((sigil (sigil_name) @name (:match "^[HF]$" @name) (quoted_content) 
@heex)))))
+
+(defun elixir-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)
+   (if (eq (treesit-language-at (point)) 'heex)
+       heex-ts--sexp-regexp
+     elixir-ts--sexp-regexp)
+   (abs arg)))
+
+(defun elixir-ts--treesit-anchor-grand-parent-bol (_n parent &rest _)
+  "Return the beginning of non-space characters for the parent node of PARENT."
+  (save-excursion
+    (goto-char (treesit-node-start (treesit-node-parent parent)))
+    (back-to-indentation)
+    (point)))
+
+(defun elixir-ts--treesit-language-at-point (point)
+  "Return the language at POINT."
+  (let* ((range nil)
+         (language-in-range
+          (cl-loop
+           for parser in (treesit-parser-list)
+           do (setq range
+                    (cl-loop
+                     for range in (treesit-parser-included-ranges parser)
+                     if (and (>= point (car range)) (<= point (cdr range)))
+                     return parser))
+           if range
+           return (treesit-parser-language parser))))
+    (if (null language-in-range)
+        (when-let ((parser (car (treesit-parser-list))))
+          (treesit-parser-language parser))
+      language-in-range)))
+
+(defun elixir-ts--defun-p (node)
+  "Return non-nil when NODE is a defun."
+  (member (treesit-node-text
+           (treesit-node-child-by-field-name node "target"))
+          (append
+           elixir-ts--definition-keywords
+           elixir-ts--test-definition-keywords)))
+
+(defun elixir-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)
+    ("call" (let ((node-child
+                   (treesit-node-child (treesit-node-child node 1) 0)))
+              (pcase (treesit-node-type node-child)
+                ("alias" (treesit-node-text node-child t))
+                ("call" (treesit-node-text
+                         (treesit-node-child-by-field-name node-child 
"target") t))
+                ("binary_operator"
+                 (treesit-node-text
+                  (treesit-node-child-by-field-name
+                   (treesit-node-child-by-field-name node-child "left") 
"target")
+                  t))
+                ("identifier"
+                 (treesit-node-text node-child t))
+                (_ nil))))
+    (_ nil)))
+
+;;;###autoload
+(define-derived-mode elixir-ts-mode prog-mode "Elixir"
+  "Major mode for editing Elixir, powered by tree-sitter."
+  :group 'elixir-ts
+  :syntax-table elixir-ts--syntax-table
+
+  ;; Comments
+  (setq-local comment-start "# ")
+  (setq-local comment-start-skip
+              (rx "#" (* (syntax whitespace))))
+
+  (setq-local comment-end "")
+  (setq-local comment-end-skip
+              (rx (* (syntax whitespace))
+                  (group (or (syntax comment-end) "\n"))))
+
+  ;; Compile
+  (setq-local compile-command "mix")
+
+  (when (treesit-ready-p 'elixir)
+    ;; The HEEx parser has to be created first for elixir to ensure elixir
+    ;; is the first language when looking for treesit ranges.
+    (if (treesit-ready-p 'heex)
+        (treesit-parser-create 'heex))
+
+    (treesit-parser-create 'elixir)
+
+    (setq-local treesit-language-at-point-function
+                'elixir-ts--treesit-language-at-point)
+
+    ;; Font-lock.
+    (setq-local treesit-font-lock-settings elixir-ts--font-lock-settings)
+    (setq-local treesit-font-lock-feature-list
+                '(( elixir-comment elixir-constant elixir-doc )
+                  ( elixir-string elixir-keyword elixir-unary-operator
+                    elixir-call elixir-operator )
+                  ( elixir-sigil elixir-string-escape 
elixir-string-interpolation)))
+
+    ;; Imenu.
+    (setq-local treesit-simple-imenu-settings
+                '((nil "\\`call\\'" elixir-ts--defun-p nil)))
+
+    ;; Indent.
+    (setq-local treesit-simple-indent-rules elixir-ts--indent-rules)
+
+    ;; Navigation
+    (setq-local forward-sexp-function #'elixir-ts--forward-sexp)
+    (setq-local treesit-defun-type-regexp
+                '("call" . elixir-ts--defun-p))
+
+    (setq-local treesit-defun-name-function #'elixir-ts--defun-name)
+
+    ;; Embedded Heex
+    (when (treesit-ready-p 'heex)
+      (setq-local treesit-range-settings elixir-ts--treesit-range-rules)
+
+      (setq-local treesit-simple-indent-rules
+                  (append treesit-simple-indent-rules heex-ts--indent-rules))
+
+      (setq-local treesit-font-lock-settings
+                  (append treesit-font-lock-settings
+                          heex-ts--font-lock-settings))
+
+      (setq-local treesit-simple-indent-rules
+                  (append treesit-simple-indent-rules
+                          heex-ts--indent-rules))
+
+      (setq-local treesit-font-lock-feature-list
+                  '(( elixir-comment elixir-constant elixir-doc
+                      heex-comment heex-keyword heex-doctype )
+                    ( elixir-string elixir-keyword elixir-unary-operator
+                      elixir-call elixir-operator
+                      heex-component heex-tag heex-attribute heex-string)
+                    ( elixir-sigil elixir-string-escape
+                      elixir-string-interpolation ))))
+
+    (treesit-major-mode-setup)))
+
+(if (treesit-ready-p 'elixir)
+    (progn
+      (add-to-list 'auto-mode-alist '("\\.elixir\\'" . elixir-ts-mode))
+      (add-to-list 'auto-mode-alist '("\\.ex\\'" . elixir-ts-mode))
+      (add-to-list 'auto-mode-alist '("\\.exs\\'" . elixir-ts-mode))
+      (add-to-list 'auto-mode-alist '("mix\\.lock" . elixir-ts-mode))))
+
+(provide 'elixir-ts-mode)
+
+;;; elixir-ts-mode.el ends here
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 @@
+;;; 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/lisp/subr.el b/lisp/subr.el
index 6ea4d34ebc3..a8c00045e3e 100644
--- a/lisp/subr.el
+++ b/lisp/subr.el
@@ -846,61 +846,6 @@ argument VECP, this copies vectors as well as conses."
          tree)
       tree)))
 
-(defvar safe-copy-tree--seen nil
-  "A hash table for conses/vectors/records already seen by safe-copy-tree-1.
-Its key is a cons or vector/record seen by the algorithm, and its
-value is the corresponding cons/vector/record in the copy.")
-
-(defun safe-copy-tree--1 (tree &optional vecp)
-  "Make a copy of TREE, taking circular structure into account.
-If TREE is a cons cell, this recursively copies both its car and its cdr.
-Contrast to `copy-sequence', which copies only along the cdrs.  With second
-argument VECP, this copies vectors and records as well as conses."
-  (cond
-   ((gethash tree safe-copy-tree--seen))
-   ((consp tree)
-    (let* ((result (cons (car tree) (cdr tree)))
-          (newcons result)
-          hash)
-      (while (and (not hash) (consp tree))
-       (if (setq hash (gethash tree safe-copy-tree--seen))
-            (setq newcons hash)
-         (puthash tree newcons safe-copy-tree--seen))
-        (setq tree newcons)
-        (unless hash
-         (if (or (consp (car tree))
-                  (and vecp (or (vectorp (car tree)) (recordp (car tree)))))
-             (let ((newcar (safe-copy-tree--1 (car tree) vecp)))
-               (setcar tree newcar)))
-          (setq newcons (if (consp (cdr tree))
-                            (cons (cadr tree) (cddr tree))
-                          (cdr tree)))
-          (setcdr tree newcons)
-          (setq tree (cdr tree))))
-      (nconc result
-             (if (and vecp (or (vectorp tree) (recordp tree)))
-                (safe-copy-tree--1 tree vecp) tree))))
-   ((and vecp (or (vectorp tree) (recordp tree)))
-    (let* ((newvec (copy-sequence tree))
-           (i (length newvec)))
-      (puthash tree newvec safe-copy-tree--seen)
-      (setq tree newvec)
-      (while (>= (setq i (1- i)) 0)
-       (aset tree i (safe-copy-tree--1 (aref tree i) vecp)))
-      tree))
-   (t tree)))
-
-(defun safe-copy-tree (tree &optional vecp)
-  "Make a copy of TREE, taking circular structure into account.
-If TREE is a cons cell, this recursively copies both its car and its cdr.
-Contrast to `copy-sequence', which copies only along the cdrs.  With second
-argument VECP, this copies vectors and records as well as conses."
-  (setq safe-copy-tree--seen (make-hash-table :test #'eq))
-  (unwind-protect
-      (safe-copy-tree--1 tree vecp)
-    (clrhash safe-copy-tree--seen)
-    (setq safe-copy-tree--seen nil)))
-
 
 ;;;; Various list-search functions.
 
diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el 
b/test/lisp/emacs-lisp/bytecomp-tests.el
index 10b009a261c..2cd4dd75742 100644
--- a/test/lisp/emacs-lisp/bytecomp-tests.el
+++ b/test/lisp/emacs-lisp/bytecomp-tests.el
@@ -1850,6 +1850,34 @@ EXPECTED-POINT BINDINGS (MODES \\='\\='(ruby-mode 
js-mode python-mode)) \
     (should (eq (byte-compile-file src-file) 'no-byte-compile))
     (should-not (file-exists-p dest-file))))
 
+(ert-deftest bytecomp--copy-tree ()
+  (should (null (bytecomp--copy-tree nil)))
+  (let ((print-circle t))
+    (let* ((x '(1 2 (3 4)))
+           (y (bytecomp--copy-tree x)))
+      (should (equal (prin1-to-string (list x y))
+                     "((1 2 (3 4)) (1 2 (3 4)))")))
+    (let* ((x '#1=(a #1#))
+           (y (bytecomp--copy-tree x)))
+      (should (equal (prin1-to-string (list x y))
+                     "(#1=(a #1#) #2=(a #2#))")))
+    (let* ((x '#1=(#1# a))
+           (y (bytecomp--copy-tree x)))
+      (should (equal (prin1-to-string (list x y))
+                     "(#1=(#1# a) #2=(#2# a))")))
+    (let* ((x '((a . #1=(b)) #1#))
+           (y (bytecomp--copy-tree x)))
+      (should (equal (prin1-to-string (list x y))
+                     "(((a . #1=(b)) #1#) ((a . #2=(b)) #2#))")))
+    (let* ((x '#1=(a #2=(#1# b . #3=(#2# c . #1#)) (#3# d)))
+           (y (bytecomp--copy-tree x)))
+      (should (equal (prin1-to-string (list x y))
+                     (concat
+                      "("
+                      "#1=(a #2=(#1# b . #3=(#2# c . #1#)) (#3# d))"
+                      " "
+                      "#4=(a #5=(#4# b . #6=(#5# c . #4#)) (#6# d))"
+                      ")"))))))
 
 ;; Local Variables:
 ;; no-byte-compile: t
diff --git a/test/lisp/emacs-lisp/shortdoc-tests.el 
b/test/lisp/emacs-lisp/shortdoc-tests.el
index a65a4a5ddc3..d2dfbc66864 100644
--- a/test/lisp/emacs-lisp/shortdoc-tests.el
+++ b/test/lisp/emacs-lisp/shortdoc-tests.el
@@ -75,6 +75,21 @@
   (should (equal '((regexp . "(string-match-p \"^[fo]+\" \"foobar\")\n    => 
0"))
                  (shortdoc-function-examples 'string-match-p))))
 
+(ert-deftest shortdoc-help-fns-examples-function-test ()
+  "Test that `shortdoc-help-fns-examples-function' correctly prints ELisp 
function examples."
+  (with-temp-buffer
+    (shortdoc-help-fns-examples-function 'string-fill)
+    (should (equal "\n  Examples:\n\n  (string-fill \"Three short words\" 
12)\n    => \"Three short\\nwords\"\n  (string-fill \"Long-word\" 3)\n    => 
\"Long-word\"\n\n"
+                   (buffer-substring-no-properties (point-min) (point-max))))
+    (erase-buffer)
+    (shortdoc-help-fns-examples-function 'assq)
+    (should (equal "\n  Examples:\n\n  (assq 'foo '((foo . bar) (zot . 
baz)))\n    => (foo . bar)\n\n  (assq 'b '((a . 1) (b . 2)))\n    => (b . 
2)\n\n"
+                   (buffer-substring-no-properties (point-min) (point-max))))
+    (erase-buffer)
+    (shortdoc-help-fns-examples-function 'string-trim)
+    (should (equal "\n  Example:\n\n  (string-trim \" foo \")\n    => 
\"foo\"\n\n"
+                   (buffer-substring-no-properties (point-min) (point-max))))))
+
 (provide 'shortdoc-tests)
 
 ;;; shortdoc-tests.el ends here
diff --git a/test/lisp/progmodes/elixir-ts-mode-resources/indent.erts 
b/test/lisp/progmodes/elixir-ts-mode-resources/indent.erts
new file mode 100644
index 00000000000..748455cc3f2
--- /dev/null
+++ b/test/lisp/progmodes/elixir-ts-mode-resources/indent.erts
@@ -0,0 +1,308 @@
+Code:
+  (lambda ()
+    (setq indent-tabs-mode nil)
+    (elixir-ts-mode)
+    (indent-region (point-min) (point-max)))
+
+Point-Char: $
+
+Name: Basic modules
+
+=-=
+  defmodule Foobar do
+def bar() do
+"one"
+      end
+    end
+=-=
+defmodule Foobar do
+  def bar() do
+    "one"
+  end
+end
+=-=-=
+
+Name: Map
+
+=-=
+map = %{
+  "a" => 1,
+  "b" => 2
+}
+=-=-=
+
+Name: Map in function def
+
+=-=
+def foobar() do
+  %{
+    one: "one",
+    two: "two",
+    three: "three",
+    four: "four"
+  }
+end
+=-=-=
+
+Name: Map in tuple
+
+=-=
+def foo() do
+  {:ok,
+   %{
+     state
+     | extra_arguments: extra_arguments,
+       max_children: max_children,
+       max_restarts: max_restarts,
+       max_seconds: max_seconds,
+       strategy: strategy
+   }}
+end
+=-=-=
+
+Name: Nested maps
+
+=-=
+%{
+  foo: "bar",
+  bar: %{
+    foo: "bar"
+  }
+}
+
+def foo() do
+  %{
+    foo: "bar",
+    bar: %{
+      foo: "bar"
+    }
+  }
+end
+=-=-=
+
+Name: Block assignments
+
+=-=
+foo =
+  if true do
+    "yes"
+  else
+    "no"
+  end
+=-=-=
+
+Name: Function rescue
+
+=-=
+def foo do
+  "bar"
+rescue
+  e ->
+    "bar"
+end
+=-=-=
+
+Name: With statement
+=-=
+with one <- one(),
+     two <- two(),
+     {:ok, value} <- get_value(one, two) do
+  {:ok, value}
+else
+  {:error, %{"Message" => message}} ->
+    {:error, message}
+end
+=-=-=
+
+Name: Pipe statements with fn
+
+=-=
+[1, 2]
+|> Enum.map(fn num ->
+  num + 1
+end)
+=-=-=
+
+Name: Pipe statements stab clases
+
+=-=
+[1, 2]
+|> Enum.map(fn
+  x when x < 10 -> x * 2
+  x -> x * 3
+end)
+=-=-=
+
+Name: Pipe statements params
+
+=-=
+[1, 2]
+|> foobar(
+  :one,
+  :two,
+  :three,
+  :four
+)
+=-=-=
+
+Name: Parameter maps
+
+=-=
+def something(%{
+      one: :one,
+      two: :two
+    }) do
+  {:ok, "done"}
+end
+=-=-=
+
+Name: Binary operator in else block
+
+=-=
+defp foobar() do
+  if false do
+    :foo
+  else
+    :bar |> foo
+  end
+end
+=-=-=
+
+Name: Tuple indentation
+
+=-=
+tuple = {
+  :one,
+  :two
+}
+
+{
+  :one,
+  :two
+}
+=-=-=
+
+Name: Spec and method
+
+=-=
+@spec foobar(
+        t,
+        acc,
+        (one, something -> :bar | far),
+        (two -> :bar | far)
+      ) :: any()
+      when chunk: any
+def foobar(enumerable, acc, chunk_fun, after_fun) do
+  {_, {res, acc}} =
+    case after_fun.(acc) do
+      {:one, "one"} ->
+        "one"
+
+      {:two, "two"} ->
+        "two"
+    end
+end
+=-=-=
+
+Name: Spec with multi-line result
+
+=-=
+@type result ::
+        {:done, term}
+        | {:two}
+        | {:one}
+
+@type result ::
+        {
+          :done,
+          term
+        }
+        | {:two}
+        | {:one}
+
+@type boo_bar ::
+        (foo :: pos_integer, bar :: pos_integer -> any())
+
+@spec foo_bar(
+        t,
+        (foo -> any),
+        (() -> any) | (foo, foo -> boolean) | module()
+      ) :: any
+      when foo: any
+def foo(one, fun, other)
+=-=-=
+
+Name: String concatenation in call
+
+=-=
+IO.warn(
+  "one" <>
+    "two" <>
+    "bar"
+)
+
+IO.warn(
+  "foo" <>
+    "bar"
+)
+=-=-=
+
+Name: Incomplete tuple
+
+=-=
+map = {
+:foo
+
+=-=
+map = {
+  :foo
+
+=-=-=
+
+Name: Incomplete map
+
+=-=
+map = %{
+  "a" => "a",
+=-=-=
+
+Name: Incomplete list
+
+=-=
+map = [
+:foo
+
+=-=
+map = [
+  :foo
+
+=-=-=
+
+Name: String concatenation
+
+=-=
+"one" <>
+  "two" <>
+  "three" <>
+  "four"
+=-=-=
+
+Name: Tuple with same line first node
+
+=-=
+{:one,
+ :two}
+
+{:ok,
+ fn one ->
+   one
+   |> String.upcase(one)
+ end}
+=-=-=
+
+Name: Long tuple
+
+=-=
+{"January", "February", "March", "April", "May", "June", "July", "August", 
"September",
+ "October", "November", "December"}
+=-=-=
diff --git a/test/lisp/progmodes/elixir-ts-mode-tests.el 
b/test/lisp/progmodes/elixir-ts-mode-tests.el
new file mode 100644
index 00000000000..8e546ad5cc6
--- /dev/null
+++ b/test/lisp/progmodes/elixir-ts-mode-tests.el
@@ -0,0 +1,31 @@
+;;; c-ts-mode-tests.el --- Tests for Tree-sitter-based C mode         -*- 
lexical-binding: t; -*-
+
+;; Copyright (C) 2023 Free Software Foundation, Inc.
+
+;; 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/>.
+
+;;; Code:
+
+(require 'ert)
+(require 'ert-x)
+(require 'treesit)
+
+(ert-deftest elixir-ts-mode-test-indentation ()
+  (skip-unless (and (treesit-ready-p 'elixir) (treesit-ready-p 'heex)))
+  (ert-test-erts-file (ert-resource-file "indent.erts")))
+
+(provide 'elixir-ts-mode-tests)
+;;; elixir-ts-mode-tests.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 @@
+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
index 00000000000..b59126e136a
--- /dev/null
+++ b/test/lisp/progmodes/heex-ts-mode-tests.el
@@ -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)
diff --git a/test/lisp/subr-tests.el b/test/lisp/subr-tests.el
index 37fe09c1716..050ee22ac18 100644
--- a/test/lisp/subr-tests.el
+++ b/test/lisp/subr-tests.el
@@ -1205,31 +1205,5 @@ final or penultimate step during initialization."))
     (should (equal a-dedup '("a" "b" "a" "b" "c")))
     (should (eq a a-dedup))))
 
-(ert-deftest subr--safe-copy-tree ()
-  (should (null (safe-copy-tree nil)))
-  (let* ((foo '(1 2 (3 4))) (bar (safe-copy-tree foo)))
-    (should (equal bar foo))
-    (should-not (eq bar foo))
-    (should-not (eq (caddr bar) (caddr foo))))
-  (let* ((foo '#1=(a #1#)) (bar (safe-copy-tree foo)))
-    (should (eq (car bar) (car foo)))
-;    (should-not (proper-list-p bar))
-    (should (eq (caadr bar) (caadr foo)))
-    (should (eq (caadr bar) 'a)))
-  (let* ((foo [1 2 3 4]) (bar (safe-copy-tree foo)))
-    (should (eq bar foo)))
-  (let* ((foo [1 (2 3) 4]) (bar (safe-copy-tree foo t)))
-    (should-not (eq bar foo))
-    (should (equal bar foo))
-    (should-not (eq (aref bar 1) (aref foo 1))))
-  (let* ((foo [1 [2 3] 4]) (bar (safe-copy-tree foo t)))
-    (should (equal bar foo))
-    (should-not (eq bar foo))
-    (should-not (eq (aref bar 1) (aref foo 1))))
-  (let* ((foo (record 'foo 1 "two" 3)) (bar (safe-copy-tree foo t)))
-    (should (equal bar foo))
-    (should-not (eq bar foo))
-    (should (eq (aref bar 2) (aref foo 2)))))
-
 (provide 'subr-tests)
 ;;; subr-tests.el ends here



reply via email to

[Prev in Thread] Current Thread [Next in Thread]