[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