aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPo Lu2023-03-13 07:52:08 +0800
committerPo Lu2023-03-13 07:52:08 +0800
commita517c24697d080475e2d531c8ce1d433aa44a9c6 (patch)
tree057e84aa396d4b2266d5ff007d23265727d633f4
parent08a3749794b8e6c1b3db882ee15e3e91f3700414 (diff)
parent75f04848a653e70f12f0e5a62b756c5bba0dd204 (diff)
downloademacs-a517c24697d080475e2d531c8ce1d433aa44a9c6.tar.gz
emacs-a517c24697d080475e2d531c8ce1d433aa44a9c6.zip
Merge remote-tracking branch 'origin/master' into feature/android
-rwxr-xr-xadmin/notes/tree-sitter/build-module/batch.sh2
-rwxr-xr-xadmin/notes/tree-sitter/build-module/build.sh6
-rw-r--r--doc/lispref/lists.texi13
-rw-r--r--etc/NEWS14
-rw-r--r--lisp/emacs-lisp/bytecomp.el38
-rw-r--r--lisp/emacs-lisp/shortdoc.el35
-rw-r--r--lisp/net/tramp-adb.el51
-rw-r--r--lisp/net/tramp-archive.el4
-rw-r--r--lisp/net/tramp-crypt.el25
-rw-r--r--lisp/net/tramp-fuse.el29
-rw-r--r--lisp/net/tramp-gvfs.el23
-rw-r--r--lisp/net/tramp-sh.el72
-rw-r--r--lisp/net/tramp-smb.el26
-rw-r--r--lisp/net/tramp-sudoedit.el39
-rw-r--r--lisp/progmodes/eglot.el2
-rw-r--r--lisp/progmodes/elixir-ts-mode.el634
-rw-r--r--lisp/progmodes/heex-ts-mode.el185
-rw-r--r--lisp/subr.el55
-rw-r--r--test/lisp/emacs-lisp/bytecomp-tests.el28
-rw-r--r--test/lisp/emacs-lisp/shortdoc-tests.el15
-rw-r--r--test/lisp/progmodes/elixir-ts-mode-resources/indent.erts308
-rw-r--r--test/lisp/progmodes/elixir-ts-mode-tests.el31
-rw-r--r--test/lisp/progmodes/heex-ts-mode-resources/indent.erts47
-rw-r--r--test/lisp/progmodes/heex-ts-mode-tests.el9
-rw-r--r--test/lisp/subr-tests.el26
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=(
8 'css' 8 'css'
9 'c-sharp' 9 'c-sharp'
10 'dockerfile' 10 'dockerfile'
11 'elixir'
11 'go' 12 'go'
12 'go-mod' 13 'go-mod'
14 'heex'
13 'html' 15 'html'
14 'javascript' 16 'javascript'
15 'json' 17 '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
31 "cmake") 31 "cmake")
32 org="uyha" 32 org="uyha"
33 ;; 33 ;;
34 "elixir")
35 org="elixir-lang"
36 ;;
34 "go-mod") 37 "go-mod")
35 # The parser is called "gomod". 38 # The parser is called "gomod".
36 lang="gomod" 39 lang="gomod"
37 org="camdencheek" 40 org="camdencheek"
38 ;; 41 ;;
42 "heex")
43 org="phoenixframework"
44 ;;
39 "typescript") 45 "typescript")
40 sourcedir="tree-sitter-typescript/typescript/src" 46 sourcedir="tree-sitter-typescript/typescript/src"
41 grammardir="tree-sitter-typescript/typescript" 47 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
708their elements). This function cannot cope with circular lists. 708their elements). This function cannot cope with circular lists.
709@end defun 709@end defun
710 710
711@defun safe-copy-tree tree &optional vecp
712This function returns a copy of the tree @var{tree}. If @var{tree} is
713a cons cell, this make a new cons cell with the same @sc{car} and
714@sc{cdr}, then recursively copies the @sc{car} and @sc{cdr} in the
715same way.
716
717Normally, when @var{tree} is anything other than a cons cell,
718@code{copy-tree} simply returns @var{tree}. However, if @var{vecp} is
719non-@code{nil}, it copies vectors and records too (and operates
720recursively on their elements). This function handles circular lists
721and vectors, and is thus slower than @code{copy-tree} for typical cases.
722@end defun
723
724@defun flatten-tree tree 711@defun flatten-tree tree
725This function returns a ``flattened'' copy of @var{tree}, that is, 712This function returns a ``flattened'' copy of @var{tree}, that is,
726a list containing all the non-@code{nil} terminal nodes, or leaves, of 713a 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
@@ -261,6 +261,15 @@ An optional major mode based on the tree-sitter library for editing
261HTML files. 261HTML files.
262 262
263--- 263---
264*** New major mode heex-ts-mode'.
265A major mode based on the tree-sitter library for editing HEEx files.
266
267---
268*** New major mode elixir-ts-mode'.
269A major mode based on the tree-sitter library for editing Elixir
270files.
271
272---
264** The highly accessible Modus themes collection has six items. 273** The highly accessible Modus themes collection has six items.
265The 'modus-operandi' and 'modus-vivendi' are the main themes that have 274The 'modus-operandi' and 'modus-vivendi' are the main themes that have
266been part of Emacs since version 28. The former is light, the latter 275been part of Emacs since version 28. The former is light, the latter
@@ -414,11 +423,6 @@ This warning can be suppressed using 'with-suppressed-warnings' with
414the warning name 'suspicious'. 423the warning name 'suspicious'.
415 424
416+++ 425+++
417** New function 'safe-copy-tree'
418This function is a version of copy-tree which handles circular lists
419and circular vectors/records.
420
421+++
422** New function 'file-user-uid'. 426** New function 'file-user-uid'.
423This function is like 'user-uid', but is aware of file name handlers, 427This function is like 'user-uid', but is aware of file name handlers,
424so it will return the remote UID for remote files (or -1 if the 428so it will return the remote UID for remote files (or -1 if the
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."
495 (cdr form))) 495 (cdr form)))
496 (funcall non-toplevel-case form))) 496 (funcall non-toplevel-case form)))
497 497
498
499(defvar bytecomp--copy-tree-seen)
500
501(defun bytecomp--copy-tree-1 (tree)
502 ;; TREE must be a cons.
503 (or (gethash tree bytecomp--copy-tree-seen)
504 (let* ((next (cdr tree))
505 (result (cons nil next))
506 (copy result))
507 (while (progn
508 (puthash tree copy bytecomp--copy-tree-seen)
509 (let ((a (car tree)))
510 (setcar copy (if (consp a)
511 (bytecomp--copy-tree-1 a)
512 a)))
513 (and (consp next)
514 (let ((tail (gethash next bytecomp--copy-tree-seen)))
515 (if tail
516 (progn (setcdr copy tail)
517 nil)
518 (setq tree next)
519 (setq next (cdr next))
520 (let ((prev copy))
521 (setq copy (cons nil next))
522 (setcdr prev copy)
523 t))))))
524 result)))
525
526(defun bytecomp--copy-tree (tree)
527 "Make a copy of TREE, preserving any circular structure therein.
528Only conses are traversed and duplicated, not arrays or any other structure."
529 (if (consp tree)
530 (let ((bytecomp--copy-tree-seen (make-hash-table :test #'eq)))
531 (bytecomp--copy-tree-1 tree))
532 tree))
533
498(defconst byte-compile-initial-macro-environment 534(defconst byte-compile-initial-macro-environment
499 `( 535 `(
500 ;; (byte-compiler-options . (lambda (&rest forms) 536 ;; (byte-compiler-options . (lambda (&rest forms)
@@ -534,7 +570,7 @@ Return the compile-time value of FORM."
534 form 570 form
535 macroexpand-all-environment))) 571 macroexpand-all-environment)))
536 (eval (byte-run-strip-symbol-positions 572 (eval (byte-run-strip-symbol-positions
537 (safe-copy-tree expanded)) 573 (bytecomp--copy-tree expanded))
538 lexical-binding) 574 lexical-binding)
539 expanded))))) 575 expanded)))))
540 (with-suppressed-warnings 576 (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."
1621You can add this function to the `help-fns-describe-function-functions' 1621You can add this function to the `help-fns-describe-function-functions'
1622hook to show examples of using FUNCTION in *Help* buffers produced 1622hook to show examples of using FUNCTION in *Help* buffers produced
1623by \\[describe-function]." 1623by \\[describe-function]."
1624 (let ((examples (shortdoc-function-examples function)) 1624 (let* ((examples (shortdoc-function-examples function))
1625 (times 0)) 1625 (num-examples (length examples))
1626 (times 0))
1626 (dolist (example examples) 1627 (dolist (example examples)
1627 (when (zerop times) 1628 (when (zerop times)
1628 (if (eq (length examples) 1) 1629 (if (> num-examples 1)
1629 (insert "\n Example:\n\n") 1630 (insert "\n Examples:\n\n")
1630 (insert "\n Examples:\n\n"))) 1631 ;; Some functions have more than one example per group.
1632 ;; Count the number of arrows to know if we need to
1633 ;; pluralize "Example".
1634 (let* ((text (cdr example))
1635 (count 0)
1636 (pos 0)
1637 (end (length text))
1638 (double-arrow (if (char-displayable-p ?⇒)
1639 " ⇒"
1640 " =>"))
1641 (double-arrow-example (if (char-displayable-p ?⇒)
1642 " e.g. ⇒"
1643 " e.g. =>"))
1644 (single-arrow (if (char-displayable-p ?→)
1645 " →"
1646 " ->")))
1647 (while (and (< pos end)
1648 (or (string-match double-arrow text pos)
1649 (string-match double-arrow-example text pos)
1650 (string-match single-arrow text pos)))
1651 (setq count (1+ count)
1652 pos (match-end 0)))
1653 (if (> count 1)
1654 (insert "\n Examples:\n\n")
1655 (insert "\n Example:\n\n")))))
1631 (setq times (1+ times)) 1656 (setq times (1+ times))
1632 (insert " ") 1657 (insert " ")
1633 (insert (cdr example)) 1658 (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."
432 432
433(defun tramp-adb-handle-file-name-all-completions (filename directory) 433(defun tramp-adb-handle-file-name-all-completions (filename directory)
434 "Like `file-name-all-completions' for Tramp files." 434 "Like `file-name-all-completions' for Tramp files."
435 (all-completions 435 (ignore-error file-missing
436 filename 436 (all-completions
437 (with-parsed-tramp-file-name (expand-file-name directory) nil 437 filename
438 (with-tramp-file-property v localname "file-name-all-completions" 438 (with-parsed-tramp-file-name (expand-file-name directory) nil
439 (tramp-adb-send-command 439 (with-tramp-file-property v localname "file-name-all-completions"
440 v (format "%s -a %s | cat" 440 (tramp-adb-send-command
441 (tramp-adb-get-ls-command v) 441 v (format "%s -a %s | cat"
442 (tramp-shell-quote-argument localname))) 442 (tramp-adb-get-ls-command v)
443 (mapcar 443 (tramp-shell-quote-argument localname)))
444 (lambda (f) 444 (mapcar
445 (if (file-directory-p (expand-file-name f directory)) 445 (lambda (f)
446 (file-name-as-directory f) 446 (if (file-directory-p (expand-file-name f directory))
447 f)) 447 (file-name-as-directory f)
448 (with-current-buffer (tramp-get-buffer v) 448 f))
449 (delete-dups 449 (with-current-buffer (tramp-get-buffer v)
450 (append 450 (delete-dups
451 ;; On some file systems like "sdcard", "." and ".." are 451 (append
452 ;; not included. We fix this by `delete-dups'. 452 ;; On some file systems like "sdcard", "." and ".." are
453 '("." "..") 453 ;; not included. We fix this by `delete-dups'.
454 (delq 454 '("." "..")
455 nil 455 (delq
456 (mapcar 456 nil
457 (lambda (l) 457 (mapcar
458 (and (not (string-match-p (rx bol (* blank) eol) l)) l)) 458 (lambda (l)
459 (split-string (buffer-string) "\n"))))))))))) 459 (and (not (string-match-p (rx bol (* blank) eol) l)) l))
460 (split-string (buffer-string) "\n"))))))))))))
460 461
461(defun tramp-adb-handle-file-local-copy (filename) 462(defun tramp-adb-handle-file-local-copy (filename)
462 "Like `file-local-copy' for Tramp files." 463 "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."
650 650
651(defun tramp-archive-handle-file-name-all-completions (filename directory) 651(defun tramp-archive-handle-file-name-all-completions (filename directory)
652 "Like `file-name-all-completions' for file archives." 652 "Like `file-name-all-completions' for file archives."
653 (file-name-all-completions filename (tramp-archive-gvfs-file-name directory))) 653 (ignore-error file-missing
654 (file-name-all-completions
655 filename (tramp-archive-gvfs-file-name directory))))
654 656
655(defun tramp-archive-handle-file-readable-p (filename) 657(defun tramp-archive-handle-file-readable-p (filename)
656 "Like `file-readable-p' for file archives." 658 "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."
730 730
731(defun tramp-crypt-handle-file-name-all-completions (filename directory) 731(defun tramp-crypt-handle-file-name-all-completions (filename directory)
732 "Like `file-name-all-completions' for Tramp files." 732 "Like `file-name-all-completions' for Tramp files."
733 (all-completions 733 (ignore-error file-missing
734 filename 734 (all-completions
735 (let* (completion-regexp-list 735 filename
736 tramp-crypt-enabled 736 (let* (completion-regexp-list
737 (directory (file-name-as-directory directory)) 737 tramp-crypt-enabled
738 (enc-dir (tramp-crypt-encrypt-file-name directory))) 738 (directory (file-name-as-directory directory))
739 (mapcar 739 (enc-dir (tramp-crypt-encrypt-file-name directory)))
740 (lambda (x) 740 (mapcar
741 (substring 741 (lambda (x)
742 (tramp-crypt-decrypt-file-name (concat enc-dir x)) 742 (substring
743 (length directory))) 743 (tramp-crypt-decrypt-file-name (concat enc-dir x))
744 (file-name-all-completions "" enc-dir))))) 744 (length directory)))
745 (file-name-all-completions "" enc-dir))))))
745 746
746(defun tramp-crypt-handle-file-readable-p (filename) 747(defun tramp-crypt-handle-file-readable-p (filename)
747 "Like `file-readable-p' for Tramp files." 748 "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 @@
98(defun tramp-fuse-handle-file-name-all-completions (filename directory) 98(defun tramp-fuse-handle-file-name-all-completions (filename directory)
99 "Like `file-name-all-completions' for Tramp files." 99 "Like `file-name-all-completions' for Tramp files."
100 (tramp-fuse-remove-hidden-files 100 (tramp-fuse-remove-hidden-files
101 (all-completions 101 (ignore-error file-missing
102 filename 102 (all-completions
103 (delete-dups 103 filename
104 (append 104 (delete-dups
105 (file-name-all-completions 105 (append
106 filename (tramp-fuse-local-file-name directory)) 106 (file-name-all-completions
107 ;; Some storage systems do not return "." and "..". 107 filename (tramp-fuse-local-file-name directory))
108 (let (result) 108 ;; Some storage systems do not return "." and "..".
109 (dolist (item '(".." ".") result) 109 (let (result)
110 (when (string-prefix-p filename item) 110 (dolist (item '(".." ".") result)
111 (catch 'match 111 (when (string-prefix-p filename item)
112 (dolist (elt completion-regexp-list) 112 (catch 'match
113 (unless (string-match-p elt item) (throw 'match nil))) 113 (dolist (elt completion-regexp-list)
114 (setq result (cons (concat item "/") result))))))))))) 114 (unless (string-match-p elt item) (throw 'match nil)))
115 (setq result (cons (concat item "/") result))))))))))))
115 116
116;; This function isn't used. 117;; This function isn't used.
117(defun tramp-fuse-handle-insert-directory 118(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."
1418(defun tramp-gvfs-handle-file-name-all-completions (filename directory) 1418(defun tramp-gvfs-handle-file-name-all-completions (filename directory)
1419 "Like `file-name-all-completions' for Tramp files." 1419 "Like `file-name-all-completions' for Tramp files."
1420 (unless (tramp-compat-string-search "/" filename) 1420 (unless (tramp-compat-string-search "/" filename)
1421 (all-completions 1421 (ignore-error file-missing
1422 filename 1422 (all-completions
1423 (with-parsed-tramp-file-name (expand-file-name directory) nil 1423 filename
1424 (with-tramp-file-property v localname "file-name-all-completions" 1424 (with-parsed-tramp-file-name (expand-file-name directory) nil
1425 (let ((result '("./" "../"))) 1425 (with-tramp-file-property v localname "file-name-all-completions"
1426 ;; Get a list of directories and files. 1426 (let ((result '("./" "../")))
1427 (dolist (item (tramp-gvfs-get-directory-attributes directory) result) 1427 ;; Get a list of directories and files.
1428 (if (string-equal (cdr (assoc "type" item)) "directory") 1428 (dolist (item
1429 (push (file-name-as-directory (car item)) result) 1429 (tramp-gvfs-get-directory-attributes directory)
1430 (push (car item) result))))))))) 1430 result)
1431 (if (string-equal (cdr (assoc "type" item)) "directory")
1432 (push (file-name-as-directory (car item)) result)
1433 (push (car item) result))))))))))
1431 1434
1432(defun tramp-gvfs-handle-file-notify-add-watch (file-name flags _callback) 1435(defun tramp-gvfs-handle-file-notify-add-watch (file-name flags _callback)
1433 "Like `file-notify-add-watch' for Tramp files." 1436 "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'."
1767 (with-parsed-tramp-file-name (expand-file-name directory) nil 1767 (with-parsed-tramp-file-name (expand-file-name directory) nil
1768 (when (and (not (tramp-compat-string-search "/" filename)) 1768 (when (and (not (tramp-compat-string-search "/" filename))
1769 (tramp-connectable-p v)) 1769 (tramp-connectable-p v))
1770 (all-completions 1770 (unless (tramp-compat-string-search "/" filename)
1771 filename 1771 (ignore-error file-missing
1772 (with-tramp-file-property v localname "file-name-all-completions" 1772 (all-completions
1773 (let (result) 1773 filename
1774 ;; Get a list of directories and files, including reliably 1774 (with-tramp-file-property v localname "file-name-all-completions"
1775 ;; tagging the directories with a trailing "/". Because I 1775 (let (result)
1776 ;; rock. --daniel@danann.net 1776 ;; Get a list of directories and files, including
1777 (when (tramp-send-command-and-check 1777 ;; reliably tagging the directories with a trailing "/".
1778 v 1778 ;; Because I rock. --daniel@danann.net
1779 (if (tramp-get-remote-perl v) 1779 (when (tramp-send-command-and-check
1780 (progn 1780 v
1781 (tramp-maybe-send-script 1781 (if (tramp-get-remote-perl v)
1782 v tramp-perl-file-name-all-completions 1782 (progn
1783 "tramp_perl_file_name_all_completions") 1783 (tramp-maybe-send-script
1784 (format "tramp_perl_file_name_all_completions %s" 1784 v tramp-perl-file-name-all-completions
1785 (tramp-shell-quote-argument localname))) 1785 "tramp_perl_file_name_all_completions")
1786 1786 (format "tramp_perl_file_name_all_completions %s"
1787 (format (concat 1787 (tramp-shell-quote-argument localname)))
1788 "cd %s 2>&1 && %s -a 2>%s" 1788
1789 " | while IFS= read f; do" 1789 (format (concat
1790 " if %s -d \"$f\" 2>%s;" 1790 "cd %s 2>&1 && %s -a 2>%s"
1791 " then \\echo \"$f/\"; else \\echo \"$f\"; fi;" 1791 " | while IFS= read f; do"
1792 " done") 1792 " if %s -d \"$f\" 2>%s;"
1793 (tramp-shell-quote-argument localname) 1793 " then \\echo \"$f/\"; else \\echo \"$f\"; fi;"
1794 (tramp-get-ls-command v) 1794 " done")
1795 (tramp-get-remote-null-device v) 1795 (tramp-shell-quote-argument localname)
1796 (tramp-get-test-command v) 1796 (tramp-get-ls-command v)
1797 (tramp-get-remote-null-device v)))) 1797 (tramp-get-remote-null-device v)
1798 1798 (tramp-get-test-command v)
1799 ;; Now grab the output. 1799 (tramp-get-remote-null-device v))))
1800 (with-current-buffer (tramp-get-buffer v) 1800
1801 (goto-char (point-max)) 1801 ;; Now grab the output.
1802 (while (zerop (forward-line -1)) 1802 (with-current-buffer (tramp-get-buffer v)
1803 (push (buffer-substring (point) (line-end-position)) result))) 1803 (goto-char (point-max))
1804 result))))))) 1804 (while (zerop (forward-line -1))
1805 (push (buffer-substring (point) (line-end-position)) result)))
1806 result)))))))))
1805 1807
1806;; cp, mv and ln 1808;; cp, mv and ln
1807 1809
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."
976;; files. 976;; files.
977(defun tramp-smb-handle-file-name-all-completions (filename directory) 977(defun tramp-smb-handle-file-name-all-completions (filename directory)
978 "Like `file-name-all-completions' for Tramp files." 978 "Like `file-name-all-completions' for Tramp files."
979 (all-completions 979 (ignore-error file-missing
980 filename 980 (all-completions
981 (with-parsed-tramp-file-name (expand-file-name directory) nil 981 filename
982 (with-tramp-file-property v localname "file-name-all-completions" 982 (when (file-directory-p directory)
983 (delete-dups 983 (with-parsed-tramp-file-name (expand-file-name directory) nil
984 (mapcar 984 (with-tramp-file-property v localname "file-name-all-completions"
985 (lambda (x) 985 (delete-dups
986 (list 986 (mapcar
987 (if (tramp-compat-string-search "d" (nth 1 x)) 987 (lambda (x)
988 (file-name-as-directory (nth 0 x)) 988 (list
989 (nth 0 x)))) 989 (if (tramp-compat-string-search "d" (nth 1 x))
990 (tramp-smb-get-file-entries directory))))))) 990 (file-name-as-directory (nth 0 x))
991 (nth 0 x))))
992 (tramp-smb-get-file-entries directory)))))))))
991 993
992(defun tramp-smb-handle-file-system-info (filename) 994(defun tramp-smb-handle-file-system-info (filename)
993 "Like `file-system-info' for Tramp files." 995 "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."
460 460
461(defun tramp-sudoedit-handle-file-name-all-completions (filename directory) 461(defun tramp-sudoedit-handle-file-name-all-completions (filename directory)
462 "Like `file-name-all-completions' for Tramp files." 462 "Like `file-name-all-completions' for Tramp files."
463 (all-completions 463 (ignore-error file-missing
464 filename 464 (all-completions
465 (with-parsed-tramp-file-name (expand-file-name directory) nil 465 filename
466 (with-tramp-file-property v localname "file-name-all-completions" 466 (with-parsed-tramp-file-name (expand-file-name directory) nil
467 (tramp-sudoedit-send-command 467 (with-tramp-file-property v localname "file-name-all-completions"
468 v "ls" "-a1" "--quoting-style=literal" "--show-control-chars" 468 (tramp-sudoedit-send-command
469 (if (tramp-string-empty-or-nil-p localname) 469 v "ls" "-a1" "--quoting-style=literal" "--show-control-chars"
470 "" (file-name-unquote localname))) 470 (if (tramp-string-empty-or-nil-p localname)
471 (mapcar 471 "" (file-name-unquote localname)))
472 (lambda (f)
473 (if (ignore-errors (file-directory-p (expand-file-name f directory)))
474 (file-name-as-directory f)
475 f))
476 (delq
477 nil
478 (mapcar 472 (mapcar
479 (lambda (l) (and (not (string-match-p (rx bol (* blank) eol) l)) l)) 473 (lambda (f)
480 (split-string 474 (if (ignore-errors (file-directory-p (expand-file-name f directory)))
481 (tramp-get-buffer-string (tramp-get-connection-buffer v)) 475 (file-name-as-directory f)
482 "\n" 'omit)))))))) 476 f))
477 (delq
478 nil
479 (mapcar
480 (lambda (l) (and (not (string-match-p (rx bol (* blank) eol) l)) l))
481 (split-string
482 (tramp-get-buffer-string (tramp-get-connection-buffer v))
483 "\n" 'omit)))))))))
483 484
484(defun tramp-sudoedit-handle-file-readable-p (filename) 485(defun tramp-sudoedit-handle-file-readable-p (filename)
485 "Like `file-readable-p' for Tramp files." 486 "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)."
221 ((java-mode java-ts-mode) . ("jdtls")) 221 ((java-mode java-ts-mode) . ("jdtls"))
222 (dart-mode . ("dart" "language-server" 222 (dart-mode . ("dart" "language-server"
223 "--client-id" "emacs.eglot-dart")) 223 "--client-id" "emacs.eglot-dart"))
224 (elixir-mode . ("language_server.sh")) 224 ((elixir-ts-mode elixir-mode) . ("language_server.sh"))
225 (ada-mode . ("ada_language_server")) 225 (ada-mode . ("ada_language_server"))
226 (scala-mode . ,(eglot-alternatives 226 (scala-mode . ,(eglot-alternatives
227 '("metals" "metals-emacs"))) 227 '("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 @@
1;;; elixir-ts-mode.el --- Major mode for Elixir with tree-sitter support -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2022-2023 Free Software Foundation, Inc.
4
5;; Author: Wilhelm H Kirschbaum <wkirschbaum@gmail.com>
6;; Created: November 2022
7;; Keywords: elixir languages tree-sitter
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software: you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
23
24;;; Commentary:
25;;
26;; This package provides `elixir-ts-mode' which is a major mode for editing
27;; Elixir files and embedded HEEx templates that uses Tree Sitter to parse
28;; the language.
29;;
30;; This package is compatible with and was tested against the tree-sitter grammar
31;; for Elixir found at https://github.com/elixir-lang/tree-sitter-elixir.
32;;
33;; Features
34;;
35;; * Indent
36;;
37;; `elixir-ts-mode' tries to replicate the indentation provided by
38;; mix format, but will come with some minor differences.
39;;
40;; * IMenu
41;; * Navigation
42;; * Which-fun
43
44;;; Code:
45
46(require 'treesit)
47(require 'heex-ts-mode)
48(eval-when-compile (require 'rx))
49
50(declare-function treesit-parser-create "treesit.c")
51(declare-function treesit-node-child "treesit.c")
52(declare-function treesit-node-type "treesit.c")
53(declare-function treesit-node-child-by-field-name "treesit.c")
54(declare-function treesit-parser-language "treesit.c")
55(declare-function treesit-parser-included-ranges "treesit.c")
56(declare-function treesit-parser-list "treesit.c")
57(declare-function treesit-node-parent "treesit.c")
58(declare-function treesit-node-start "treesit.c")
59(declare-function treesit-query-compile "treesit.c")
60(declare-function treesit-node-eq "treesit.c")
61(declare-function treesit-node-prev-sibling "treesit.c")
62
63(defgroup elixir-ts nil
64 "Major mode for editing Elixir code."
65 :prefix "elixir-ts-"
66 :group 'languages)
67
68(defcustom elixir-ts-indent-offset 2
69 "Indentation of Elixir statements."
70 :version "30.1"
71 :type 'integer
72 :safe 'integerp
73 :group 'elixir-ts)
74
75(defface elixir-ts-font-comment-doc-identifier-face
76 '((t (:inherit font-lock-doc-face)))
77 "Face used for @comment.doc tags in Elixir files.")
78
79(defface elixir-ts-font-comment-doc-attribute-face
80 '((t (:inherit font-lock-doc-face)))
81 "Face used for @comment.doc.__attribute__ tags in Elixir files.")
82
83(defface elixir-ts-font-sigil-name-face
84 '((t (:inherit font-lock-string-face)))
85 "Face used for @__name__ tags in Elixir files.")
86
87(defconst elixir-ts--sexp-regexp
88 (rx bol
89 (or "call" "stab_clause" "binary_operator" "list" "tuple" "map" "pair"
90 "sigil" "string" "atom" "pair" "alias" "arguments" "atom" "identifier"
91 "boolean" "quoted_content")
92 eol))
93
94(defconst elixir-ts--test-definition-keywords
95 '("describe" "test"))
96
97(defconst elixir-ts--definition-keywords
98 '("def" "defdelegate" "defexception" "defguard" "defguardp"
99 "defimpl" "defmacro" "defmacrop" "defmodule" "defn" "defnp"
100 "defoverridable" "defp" "defprotocol" "defstruct"))
101
102(defconst elixir-ts--definition-keywords-re
103 (concat "^" (regexp-opt elixir-ts--definition-keywords) "$"))
104
105(defconst elixir-ts--kernel-keywords
106 '("alias" "case" "cond" "else" "for" "if" "import" "quote"
107 "raise" "receive" "require" "reraise" "super" "throw" "try"
108 "unless" "unquote" "unquote_splicing" "use" "with"))
109
110(defconst elixir-ts--kernel-keywords-re
111 (concat "^" (regexp-opt elixir-ts--kernel-keywords) "$"))
112
113(defconst elixir-ts--builtin-keywords
114 '("__MODULE__" "__DIR__" "__ENV__" "__CALLER__" "__STACKTRACE__"))
115
116(defconst elixir-ts--builtin-keywords-re
117 (concat "^" (regexp-opt elixir-ts--builtin-keywords) "$"))
118
119(defconst elixir-ts--doc-keywords
120 '("moduledoc" "typedoc" "doc"))
121
122(defconst elixir-ts--doc-keywords-re
123 (concat "^" (regexp-opt elixir-ts--doc-keywords) "$"))
124
125(defconst elixir-ts--reserved-keywords
126 '("when" "and" "or" "not" "in"
127 "not in" "fn" "do" "end" "catch" "rescue" "after" "else"))
128
129(defconst elixir-ts--reserved-keywords-re
130 (concat "^" (regexp-opt elixir-ts--reserved-keywords) "$"))
131
132(defconst elixir-ts--reserved-keywords-vector
133 (apply #'vector elixir-ts--reserved-keywords))
134
135(defvar elixir-ts--capture-anonymous-function-end
136 (when (treesit-available-p)
137 (treesit-query-compile 'elixir '((anonymous_function "end" @end)))))
138
139(defvar elixir-ts--capture-operator-parent
140 (when (treesit-available-p)
141 (treesit-query-compile 'elixir '((binary_operator operator: _ @val)))))
142
143(defvar elixir-ts--syntax-table
144 (let ((table (make-syntax-table)))
145 (modify-syntax-entry ?| "." table)
146 (modify-syntax-entry ?- "." table)
147 (modify-syntax-entry ?+ "." table)
148 (modify-syntax-entry ?* "." table)
149 (modify-syntax-entry ?/ "." table)
150 (modify-syntax-entry ?< "." table)
151 (modify-syntax-entry ?> "." table)
152 (modify-syntax-entry ?_ "_" table)
153 (modify-syntax-entry ?? "w" table)
154 (modify-syntax-entry ?~ "w" table)
155 (modify-syntax-entry ?! "_" table)
156 (modify-syntax-entry ?' "\"" table)
157 (modify-syntax-entry ?\" "\"" table)
158 (modify-syntax-entry ?# "<" table)
159 (modify-syntax-entry ?\n ">" table)
160 (modify-syntax-entry ?\( "()" table)
161 (modify-syntax-entry ?\) ")(" table)
162 (modify-syntax-entry ?\{ "(}" table)
163 (modify-syntax-entry ?\} "){" table)
164 (modify-syntax-entry ?\[ "(]" table)
165 (modify-syntax-entry ?\] ")[" table)
166 (modify-syntax-entry ?: "'" table)
167 (modify-syntax-entry ?@ "'" table)
168 table)
169 "Syntax table for `elixir-ts-mode'.")
170
171(defun elixir-ts--argument-indent-offset (node _parent &rest _)
172 "Return the argument offset position for NODE."
173 (if (treesit-node-prev-sibling node t) 0 elixir-ts-indent-offset))
174
175(defun elixir-ts--argument-indent-anchor (node parent &rest _)
176 "Return the argument anchor position for NODE and PARENT."
177 (let ((first-sibling (treesit-node-child parent 0 t)))
178 (if (and first-sibling (not (treesit-node-eq first-sibling node)))
179 (treesit-node-start first-sibling)
180 (elixir-ts--parent-expression-start node parent))))
181
182(defun elixir-ts--parent-expression-start (_node parent &rest _)
183 "Return the indentation expression start for NODE and PARENT."
184 ;; If the parent is the first expression on the line return the
185 ;; parent start of node position, otherwise use the parent call
186 ;; start if available.
187 (if (eq (treesit-node-start parent)
188 (save-excursion
189 (goto-char (treesit-node-start parent))
190 (back-to-indentation)
191 (point)))
192 (treesit-node-start parent)
193 (let ((expr-parent
194 (treesit-parent-until
195 parent
196 (lambda (n)
197 (member (treesit-node-type n)
198 '("call" "binary_operator" "keywords" "list"))))))
199 (save-excursion
200 (goto-char (treesit-node-start expr-parent))
201 (back-to-indentation)
202 (if (looking-at "|>")
203 (point)
204 (treesit-node-start expr-parent))))))
205
206(defvar elixir-ts--indent-rules
207 (let ((offset elixir-ts-indent-offset))
208 `((elixir
209 ((parent-is "^source$") column-0 0)
210 ((parent-is "^string$") parent-bol 0)
211 ((parent-is "^quoted_content$")
212 (lambda (_n parent bol &rest _)
213 (save-excursion
214 (back-to-indentation)
215 (if (bolp)
216 (progn
217 (goto-char (treesit-node-start parent))
218 (back-to-indentation)
219 (point))
220 (point))))
221 0)
222 ((node-is "^|>$") parent-bol 0)
223 ((node-is "^|$") parent-bol 0)
224 ((node-is "^]$") ,'elixir-ts--parent-expression-start 0)
225 ((node-is "^}$") ,'elixir-ts--parent-expression-start 0)
226 ((node-is "^)$") ,'elixir-ts--parent-expression-start 0)
227 ((node-is "^else_block$") grand-parent 0)
228 ((node-is "^catch_block$") grand-parent 0)
229 ((node-is "^rescue_block$") grand-parent 0)
230 ((node-is "^after_block$") grand-parent 0)
231 ((parent-is "^else_block$") parent ,offset)
232 ((parent-is "^catch_block$") parent ,offset)
233 ((parent-is "^rescue_block$") parent ,offset)
234 ((parent-is "^rescue_block$") parent ,offset)
235 ((parent-is "^after_block$") parent ,offset)
236 ((parent-is "^access_call$")
237 ,'elixir-ts--argument-indent-anchor
238 ,'elixir-ts--argument-indent-offset)
239 ((parent-is "^tuple$")
240 ,'elixir-ts--argument-indent-anchor
241 ,'elixir-ts--argument-indent-offset)
242 ((parent-is "^list$")
243 ,'elixir-ts--argument-indent-anchor
244 ,'elixir-ts--argument-indent-offset)
245 ((parent-is "^pair$") parent ,offset)
246 ((parent-is "^map_content$") parent-bol 0)
247 ((parent-is "^map$") ,'elixir-ts--parent-expression-start ,offset)
248 ((node-is "^stab_clause$") parent-bol ,offset)
249 ((query ,elixir-ts--capture-operator-parent) grand-parent 0)
250 ((node-is "^when$") parent 0)
251 ((node-is "^keywords$") parent-bol ,offset)
252 ((parent-is "^body$")
253 (lambda (node parent _)
254 (save-excursion
255 ;; The grammar adds a comment outside of the body, so we have to indent
256 ;; to the grand-parent if it is available.
257 (goto-char (treesit-node-start
258 (or (treesit-node-parent parent) (parent))))
259 (back-to-indentation)
260 (point)))
261 ,offset)
262 ((parent-is "^arguments$")
263 ,'elixir-ts--argument-indent-anchor
264 ,'elixir-ts--argument-indent-offset)
265 ;; Handle incomplete maps when parent is ERROR.
266 ((n-p-gp "^binary_operator$" "ERROR" nil) parent-bol 0)
267 ;; When there is an ERROR, just indent to prev-line.
268 ((parent-is "ERROR") prev-line 0)
269 ((node-is "^binary_operator$")
270 (lambda (node parent &rest _)
271 (let ((top-level
272 (treesit-parent-while
273 node
274 (lambda (node)
275 (equal (treesit-node-type node)
276 "binary_operator")))))
277 (if (treesit-node-eq top-level node)
278 (elixir-ts--parent-expression-start node parent)
279 (treesit-node-start top-level))))
280 (lambda (node parent _)
281 (cond
282 ((equal (treesit-node-type parent) "do_block")
283 ,offset)
284 ((equal (treesit-node-type parent) "binary_operator")
285 ,offset)
286 (t 0))))
287 ((parent-is "^binary_operator$")
288 (lambda (node parent bol &rest _)
289 (treesit-node-start
290 (treesit-parent-while
291 parent
292 (lambda (node)
293 (equal (treesit-node-type node) "binary_operator")))))
294 ,offset)
295 ((node-is "^pair$") first-sibling 0)
296 ((query ,elixir-ts--capture-anonymous-function-end) parent-bol 0)
297 ((node-is "^end$") standalone-parent 0)
298 ((parent-is "^do_block$") grand-parent ,offset)
299 ((parent-is "^anonymous_function$")
300 elixir-ts--treesit-anchor-grand-parent-bol ,offset)
301 ((parent-is "^else_block$") parent ,offset)
302 ((parent-is "^rescue_block$") parent ,offset)
303 ((parent-is "^catch_block$") parent ,offset)
304 ((parent-is "^keywords$") parent-bol 0)
305 ((node-is "^call$") parent-bol ,offset)
306 ((node-is "^comment$") parent-bol ,offset)))))
307
308(defvar elixir-ts--font-lock-settings
309 (treesit-font-lock-rules
310 :language 'elixir
311 :feature 'elixir-comment
312 '((comment) @font-lock-comment-face)
313
314 :language 'elixir
315 :feature 'elixir-string
316 :override t
317 '([(string) (charlist)] @font-lock-string-face)
318
319 :language 'elixir
320 :feature 'elixir-string-interpolation
321 :override t
322 '((string
323 [
324 quoted_end: _ @font-lock-string-face
325 quoted_start: _ @font-lock-string-face
326 (quoted_content) @font-lock-string-face
327 (interpolation
328 "#{" @font-lock-regexp-grouping-backslash "}"
329 @font-lock-regexp-grouping-backslash)
330 ])
331 (charlist
332 [
333 quoted_end: _ @font-lock-string-face
334 quoted_start: _ @font-lock-string-face
335 (quoted_content) @font-lock-string-face
336 (interpolation
337 "#{" @font-lock-regexp-grouping-backslash "}"
338 @font-lock-regexp-grouping-backslash)
339 ]))
340
341 :language 'elixir
342 :feature 'elixir-keyword
343 `(,elixir-ts--reserved-keywords-vector
344 @font-lock-keyword-face
345 (binary_operator
346 operator: _ @font-lock-keyword-face
347 (:match ,elixir-ts--reserved-keywords-re @font-lock-keyword-face)))
348
349 :language 'elixir
350 :feature 'elixir-doc
351 :override t
352 `((unary_operator
353 operator: "@" @elixir-ts-font-comment-doc-attribute-face
354 operand: (call
355 target: (identifier) @elixir-ts-font-comment-doc-identifier-face
356 ;; Arguments can be optional, so adding another
357 ;; entry without arguments.
358 ;; If we don't handle then we don't apply font
359 ;; and the non doc fortification query will take specify
360 ;; a more specific font which takes precedence.
361 (arguments
362 [
363 (string) @font-lock-doc-face
364 (charlist) @font-lock-doc-face
365 (sigil) @font-lock-doc-face
366 (boolean) @font-lock-doc-face
367 ]))
368 (:match ,elixir-ts--doc-keywords-re
369 @elixir-ts-font-comment-doc-identifier-face))
370 (unary_operator
371 operator: "@" @elixir-ts-font-comment-doc-attribute-face
372 operand: (call
373 target: (identifier) @elixir-ts-font-comment-doc-identifier-face)
374 (:match ,elixir-ts--doc-keywords-re
375 @elixir-ts-font-comment-doc-identifier-face)))
376
377 :language 'elixir
378 :feature 'elixir-unary-operator
379 `((unary_operator operator: "@" @font-lock-preprocessor-face
380 operand: [
381 (identifier) @font-lock-preprocessor-face
382 (call target: (identifier)
383 @font-lock-preprocessor-face)
384 (boolean) @font-lock-preprocessor-face
385 (nil) @font-lock-preprocessor-face
386 ])
387
388 (unary_operator operator: "&") @font-lock-function-name-face
389 (operator_identifier) @font-lock-operator-face)
390
391 :language 'elixir
392 :feature 'elixir-operator
393 '((binary_operator operator: _ @font-lock-operator-face)
394 (dot operator: _ @font-lock-operator-face)
395 (stab_clause operator: _ @font-lock-operator-face)
396
397 [(boolean) (nil)] @font-lock-constant-face
398 [(integer) (float)] @font-lock-number-face
399 (alias) @font-lock-type-face
400 (call target: (dot left: (atom) @font-lock-type-face))
401 (char) @font-lock-constant-face
402 [(atom) (quoted_atom)] @font-lock-type-face
403 [(keyword) (quoted_keyword)] @font-lock-builtin-face)
404
405 :language 'elixir
406 :feature 'elixir-call
407 `((call
408 target: (identifier) @font-lock-keyword-face
409 (:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face))
410 (call
411 target: (identifier) @font-lock-keyword-face
412 (:match ,elixir-ts--kernel-keywords-re @font-lock-keyword-face))
413 (call
414 target: [(identifier) @font-lock-function-name-face
415 (dot right: (identifier) @font-lock-keyword-face)])
416 (call
417 target: (identifier) @font-lock-keyword-face
418 (arguments
419 [
420 (identifier) @font-lock-keyword-face
421 (binary_operator
422 left: (identifier) @font-lock-keyword-face
423 operator: "when")
424 ])
425 (:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face))
426 (call
427 target: (identifier) @font-lock-keyword-face
428 (arguments
429 (binary_operator
430 operator: "|>"
431 right: (identifier)))
432 (:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face)))
433
434 :language 'elixir
435 :feature 'elixir-constant
436 `((binary_operator operator: "|>" right: (identifier)
437 @font-lock-function-name-face)
438 ((identifier) @font-lock-keyword-face
439 (:match ,elixir-ts--builtin-keywords-re
440 @font-lock-keyword-face))
441 ((identifier) @font-lock-comment-face
442 (:match "^_" @font-lock-comment-face))
443 (identifier) @font-lock-function-name-face
444 ["%"] @font-lock-keyward-face
445 ["," ";"] @font-lock-keyword-face
446 ["(" ")" "[" "]" "{" "}" "<<" ">>"] @font-lock-keyword-face)
447
448 :language 'elixir
449 :feature 'elixir-sigil
450 :override t
451 `((sigil
452 (sigil_name) @elixir-ts-font-sigil-name-face
453 quoted_start: _ @font-lock-string-face
454 quoted_end: _ @font-lock-string-face
455 (:match "^[sSwWpP]$" @elixir-ts-font-sigil-name-face))
456 @font-lock-string-face
457 (sigil
458 (sigil_name) @elixir-ts-font-sigil-name-face
459 quoted_start: _ @font-lock-regex-face
460 quoted_end: _ @font-lock-regex-face
461 (:match "^[rR]$" @elixir-ts-font-sigil-name-face))
462 @font-lock-regex-face
463 (sigil
464 "~" @font-lock-string-face
465 (sigil_name) @elixir-ts-font-sigil-name-face
466 quoted_start: _ @font-lock-string-face
467 quoted_end: _ @font-lock-string-face
468 (:match "^[HF]$" @elixir-ts-font-sigil-name-face)))
469
470 :language 'elixir
471 :feature 'elixir-string-escape
472 :override t
473 `((escape_sequence) @font-lock-regexp-grouping-backslash))
474 "Tree-sitter font-lock settings.")
475
476(defvar elixir-ts--treesit-range-rules
477 (when (treesit-available-p)
478 (treesit-range-rules
479 :embed 'heex
480 :host 'elixir
481 '((sigil (sigil_name) @name (:match "^[HF]$" @name) (quoted_content) @heex)))))
482
483(defun elixir-ts--forward-sexp (&optional arg)
484 "Move forward across one balanced expression (sexp).
485With ARG, do it many times. Negative ARG means move backward."
486 (or arg (setq arg 1))
487 (funcall
488 (if (> arg 0) #'treesit-end-of-thing #'treesit-beginning-of-thing)
489 (if (eq (treesit-language-at (point)) 'heex)
490 heex-ts--sexp-regexp
491 elixir-ts--sexp-regexp)
492 (abs arg)))
493
494(defun elixir-ts--treesit-anchor-grand-parent-bol (_n parent &rest _)
495 "Return the beginning of non-space characters for the parent node of PARENT."
496 (save-excursion
497 (goto-char (treesit-node-start (treesit-node-parent parent)))
498 (back-to-indentation)
499 (point)))
500
501(defun elixir-ts--treesit-language-at-point (point)
502 "Return the language at POINT."
503 (let* ((range nil)
504 (language-in-range
505 (cl-loop
506 for parser in (treesit-parser-list)
507 do (setq range
508 (cl-loop
509 for range in (treesit-parser-included-ranges parser)
510 if (and (>= point (car range)) (<= point (cdr range)))
511 return parser))
512 if range
513 return (treesit-parser-language parser))))
514 (if (null language-in-range)
515 (when-let ((parser (car (treesit-parser-list))))
516 (treesit-parser-language parser))
517 language-in-range)))
518
519(defun elixir-ts--defun-p (node)
520 "Return non-nil when NODE is a defun."
521 (member (treesit-node-text
522 (treesit-node-child-by-field-name node "target"))
523 (append
524 elixir-ts--definition-keywords
525 elixir-ts--test-definition-keywords)))
526
527(defun elixir-ts--defun-name (node)
528 "Return the name of the defun NODE.
529Return nil if NODE is not a defun node or doesn't have a name."
530 (pcase (treesit-node-type node)
531 ("call" (let ((node-child
532 (treesit-node-child (treesit-node-child node 1) 0)))
533 (pcase (treesit-node-type node-child)
534 ("alias" (treesit-node-text node-child t))
535 ("call" (treesit-node-text
536 (treesit-node-child-by-field-name node-child "target") t))
537 ("binary_operator"
538 (treesit-node-text
539 (treesit-node-child-by-field-name
540 (treesit-node-child-by-field-name node-child "left") "target")
541 t))
542 ("identifier"
543 (treesit-node-text node-child t))
544 (_ nil))))
545 (_ nil)))
546
547;;;###autoload
548(define-derived-mode elixir-ts-mode prog-mode "Elixir"
549 "Major mode for editing Elixir, powered by tree-sitter."
550 :group 'elixir-ts
551 :syntax-table elixir-ts--syntax-table
552
553 ;; Comments
554 (setq-local comment-start "# ")
555 (setq-local comment-start-skip
556 (rx "#" (* (syntax whitespace))))
557
558 (setq-local comment-end "")
559 (setq-local comment-end-skip
560 (rx (* (syntax whitespace))
561 (group (or (syntax comment-end) "\n"))))
562
563 ;; Compile
564 (setq-local compile-command "mix")
565
566 (when (treesit-ready-p 'elixir)
567 ;; The HEEx parser has to be created first for elixir to ensure elixir
568 ;; is the first language when looking for treesit ranges.
569 (if (treesit-ready-p 'heex)
570 (treesit-parser-create 'heex))
571
572 (treesit-parser-create 'elixir)
573
574 (setq-local treesit-language-at-point-function
575 'elixir-ts--treesit-language-at-point)
576
577 ;; Font-lock.
578 (setq-local treesit-font-lock-settings elixir-ts--font-lock-settings)
579 (setq-local treesit-font-lock-feature-list
580 '(( elixir-comment elixir-constant elixir-doc )
581 ( elixir-string elixir-keyword elixir-unary-operator
582 elixir-call elixir-operator )
583 ( elixir-sigil elixir-string-escape elixir-string-interpolation)))
584
585 ;; Imenu.
586 (setq-local treesit-simple-imenu-settings
587 '((nil "\\`call\\'" elixir-ts--defun-p nil)))
588
589 ;; Indent.
590 (setq-local treesit-simple-indent-rules elixir-ts--indent-rules)
591
592 ;; Navigation
593 (setq-local forward-sexp-function #'elixir-ts--forward-sexp)
594 (setq-local treesit-defun-type-regexp
595 '("call" . elixir-ts--defun-p))
596
597 (setq-local treesit-defun-name-function #'elixir-ts--defun-name)
598
599 ;; Embedded Heex
600 (when (treesit-ready-p 'heex)
601 (setq-local treesit-range-settings elixir-ts--treesit-range-rules)
602
603 (setq-local treesit-simple-indent-rules
604 (append treesit-simple-indent-rules heex-ts--indent-rules))
605
606 (setq-local treesit-font-lock-settings
607 (append treesit-font-lock-settings
608 heex-ts--font-lock-settings))
609
610 (setq-local treesit-simple-indent-rules
611 (append treesit-simple-indent-rules
612 heex-ts--indent-rules))
613
614 (setq-local treesit-font-lock-feature-list
615 '(( elixir-comment elixir-constant elixir-doc
616 heex-comment heex-keyword heex-doctype )
617 ( elixir-string elixir-keyword elixir-unary-operator
618 elixir-call elixir-operator
619 heex-component heex-tag heex-attribute heex-string)
620 ( elixir-sigil elixir-string-escape
621 elixir-string-interpolation ))))
622
623 (treesit-major-mode-setup)))
624
625(if (treesit-ready-p 'elixir)
626 (progn
627 (add-to-list 'auto-mode-alist '("\\.elixir\\'" . elixir-ts-mode))
628 (add-to-list 'auto-mode-alist '("\\.ex\\'" . elixir-ts-mode))
629 (add-to-list 'auto-mode-alist '("\\.exs\\'" . elixir-ts-mode))
630 (add-to-list 'auto-mode-alist '("mix\\.lock" . elixir-ts-mode))))
631
632(provide 'elixir-ts-mode)
633
634;;; 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 @@
1;;; heex-ts-mode.el --- Major mode for Heex with tree-sitter support -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2022-2023 Free Software Foundation, Inc.
4
5;; Author: Wilhelm H Kirschbaum <wkirschbaum@gmail.com>
6;; Created: November 2022
7;; Keywords: elixir languages tree-sitter
8
9;; This file is part of GNU Emacs.
10
11;; GNU Emacs is free software: you can redistribute it and/or modify
12;; it under the terms of the GNU General Public License as published by
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
22;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
23
24;;; Commentary:
25;;
26;; This package provides `heex-ts-mode' which is a major mode for editing
27;; HEEx files that uses Tree Sitter to parse the language.
28;;
29;; This package is compatible with and was tested against the tree-sitter grammar
30;; for HEEx found at https://github.com/phoenixframework/tree-sitter-heex.
31
32;;; Code:
33
34(require 'treesit)
35(eval-when-compile (require 'rx))
36
37(declare-function treesit-parser-create "treesit.c")
38(declare-function treesit-node-child "treesit.c")
39(declare-function treesit-node-type "treesit.c")
40(declare-function treesit-node-start "treesit.c")
41
42(defgroup heex-ts nil
43 "Major mode for editing HEEx code."
44 :prefix "heex-ts-"
45 :group 'langauges)
46
47(defcustom heex-ts-indent-offset 2
48 "Indentation of HEEx statements."
49 :version "30.1"
50 :type 'integer
51 :safe 'integerp
52 :group 'heex-ts)
53
54(defconst heex-ts--sexp-regexp
55 (rx bol
56 (or "directive" "tag" "component" "slot"
57 "attribute" "attribute_value" "quoted_attribute_value")
58 eol))
59
60;; There seems to be no parent directive block for tree-sitter-heex,
61;; so we ignore them for now until we learn how to query them.
62;; https://github.com/phoenixframework/tree-sitter-heex/issues/28
63(defvar heex-ts--indent-rules
64 (let ((offset heex-ts-indent-offset))
65 `((heex
66 ((parent-is "fragment")
67 (lambda (node parent &rest _)
68 ;; If HEEx is embedded indent to parent
69 ;; otherwise indent to the bol.
70 (if (eq (treesit-language-at (point-min)) 'heex)
71 (point-min)
72 (save-excursion
73 (goto-char (treesit-node-start parent))
74 (back-to-indentation)
75 (point))
76 )) 0)
77 ((node-is "end_tag") parent-bol 0)
78 ((node-is "end_component") parent-bol 0)
79 ((node-is "end_slot") parent-bol 0)
80 ((node-is "/>") parent-bol 0)
81 ((node-is ">") parent-bol 0)
82 ((parent-is "comment") prev-adaptive-prefix 0)
83 ((parent-is "component") parent-bol ,offset)
84 ((parent-is "tag") parent-bol ,offset)
85 ((parent-is "start_tag") parent-bol ,offset)
86 ((parent-is "component") parent-bol ,offset)
87 ((parent-is "start_component") parent-bol ,offset)
88 ((parent-is "slot") parent-bol ,offset)
89 ((parent-is "start_slot") parent-bol ,offset)
90 ((parent-is "self_closing_tag") parent-bol ,offset)
91 (no-node parent-bol ,offset)))))
92
93(defvar heex-ts--font-lock-settings
94 (when (treesit-available-p)
95 (treesit-font-lock-rules
96 :language 'heex
97 :feature 'heex-comment
98 '((comment) @font-lock-comment-face)
99 :language 'heex
100 :feature 'heex-doctype
101 '((doctype) @font-lock-doc-face)
102 :language 'heex
103 :feature 'heex-tag
104 `([(tag_name) (slot_name)] @font-lock-function-name-face)
105 :language 'heex
106 :feature 'heex-attribute
107 `((attribute_name) @font-lock-variable-name-face)
108 :language 'heex
109 :feature 'heex-keyword
110 `((special_attribute_name) @font-lock-keyword-face)
111 :language 'heex
112 :feature 'heex-string
113 `([(attribute_value) (quoted_attribute_value)] @font-lock-constant-face)
114 :language 'heex
115 :feature 'heex-component
116 `([
117 (component_name) @font-lock-function-name-face
118 (module) @font-lock-keyword-face
119 (function) @font-lock-keyword-face
120 "." @font-lock-keyword-face
121 ])))
122 "Tree-sitter font-lock settings.")
123
124(defun heex-ts--defun-name (node)
125 "Return the name of the defun NODE.
126Return nil if NODE is not a defun node or doesn't have a name."
127 (pcase (treesit-node-type node)
128 ((or "component" "slot" "tag")
129 (string-trim
130 (treesit-node-text
131 (treesit-node-child (treesit-node-child node 0) 1) nil)))
132 (_ nil)))
133
134(defun heex-ts--forward-sexp (&optional arg)
135 "Move forward across one balanced expression (sexp).
136With ARG, do it many times. Negative ARG means move backward."
137 (or arg (setq arg 1))
138 (funcall
139 (if (> arg 0) #'treesit-end-of-thing #'treesit-beginning-of-thing)
140 heex-ts--sexp-regexp
141 (abs arg)))
142
143;;;###autoload
144(define-derived-mode heex-ts-mode html-mode "HEEx"
145 "Major mode for editing HEEx, powered by tree-sitter."
146 :group 'heex-ts
147
148 (when (treesit-ready-p 'heex)
149 (treesit-parser-create 'heex)
150
151 ;; Comments
152 (setq-local treesit-text-type-regexp
153 (regexp-opt '("comment" "text")))
154
155 (setq-local forward-sexp-function #'heex-ts--forward-sexp)
156
157 ;; Navigation.
158 (setq-local treesit-defun-type-regexp
159 (rx bol (or "component" "tag" "slot") eol))
160 (setq-local treesit-defun-name-function #'heex-ts--defun-name)
161
162 ;; Imenu
163 (setq-local treesit-simple-imenu-settings
164 '(("Component" "\\`component\\'" nil nil)
165 ("Slot" "\\`slot\\'" nil nil)
166 ("Tag" "\\`tag\\'" nil nil)))
167
168 (setq-local treesit-font-lock-settings heex-ts--font-lock-settings)
169
170 (setq-local treesit-simple-indent-rules heex-ts--indent-rules)
171
172 (setq-local treesit-font-lock-feature-list
173 '(( heex-comment heex-keyword heex-doctype )
174 ( heex-component heex-tag heex-attribute heex-string )
175 () ()))
176
177 (treesit-major-mode-setup)))
178
179(if (treesit-ready-p 'heex)
180 ;; Both .heex and the deprecated .leex files should work
181 ;; with the tree-sitter-heex grammar.
182 (add-to-list 'auto-mode-alist '("\\.[hl]?eex\\'" . heex-ts-mode)))
183
184(provide 'heex-ts-mode)
185;;; heex-ts-mode.el ends here
diff --git a/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."
846 tree) 846 tree)
847 tree))) 847 tree)))
848 848
849(defvar safe-copy-tree--seen nil
850 "A hash table for conses/vectors/records already seen by safe-copy-tree-1.
851Its key is a cons or vector/record seen by the algorithm, and its
852value is the corresponding cons/vector/record in the copy.")
853
854(defun safe-copy-tree--1 (tree &optional vecp)
855 "Make a copy of TREE, taking circular structure into account.
856If TREE is a cons cell, this recursively copies both its car and its cdr.
857Contrast to `copy-sequence', which copies only along the cdrs. With second
858argument VECP, this copies vectors and records as well as conses."
859 (cond
860 ((gethash tree safe-copy-tree--seen))
861 ((consp tree)
862 (let* ((result (cons (car tree) (cdr tree)))
863 (newcons result)
864 hash)
865 (while (and (not hash) (consp tree))
866 (if (setq hash (gethash tree safe-copy-tree--seen))
867 (setq newcons hash)
868 (puthash tree newcons safe-copy-tree--seen))
869 (setq tree newcons)
870 (unless hash
871 (if (or (consp (car tree))
872 (and vecp (or (vectorp (car tree)) (recordp (car tree)))))
873 (let ((newcar (safe-copy-tree--1 (car tree) vecp)))
874 (setcar tree newcar)))
875 (setq newcons (if (consp (cdr tree))
876 (cons (cadr tree) (cddr tree))
877 (cdr tree)))
878 (setcdr tree newcons)
879 (setq tree (cdr tree))))
880 (nconc result
881 (if (and vecp (or (vectorp tree) (recordp tree)))
882 (safe-copy-tree--1 tree vecp) tree))))
883 ((and vecp (or (vectorp tree) (recordp tree)))
884 (let* ((newvec (copy-sequence tree))
885 (i (length newvec)))
886 (puthash tree newvec safe-copy-tree--seen)
887 (setq tree newvec)
888 (while (>= (setq i (1- i)) 0)
889 (aset tree i (safe-copy-tree--1 (aref tree i) vecp)))
890 tree))
891 (t tree)))
892
893(defun safe-copy-tree (tree &optional vecp)
894 "Make a copy of TREE, taking circular structure into account.
895If TREE is a cons cell, this recursively copies both its car and its cdr.
896Contrast to `copy-sequence', which copies only along the cdrs. With second
897argument VECP, this copies vectors and records as well as conses."
898 (setq safe-copy-tree--seen (make-hash-table :test #'eq))
899 (unwind-protect
900 (safe-copy-tree--1 tree vecp)
901 (clrhash safe-copy-tree--seen)
902 (setq safe-copy-tree--seen nil)))
903
904 849
905;;;; Various list-search functions. 850;;;; Various list-search functions.
906 851
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)) \
1850 (should (eq (byte-compile-file src-file) 'no-byte-compile)) 1850 (should (eq (byte-compile-file src-file) 'no-byte-compile))
1851 (should-not (file-exists-p dest-file)))) 1851 (should-not (file-exists-p dest-file))))
1852 1852
1853(ert-deftest bytecomp--copy-tree ()
1854 (should (null (bytecomp--copy-tree nil)))
1855 (let ((print-circle t))
1856 (let* ((x '(1 2 (3 4)))
1857 (y (bytecomp--copy-tree x)))
1858 (should (equal (prin1-to-string (list x y))
1859 "((1 2 (3 4)) (1 2 (3 4)))")))
1860 (let* ((x '#1=(a #1#))
1861 (y (bytecomp--copy-tree x)))
1862 (should (equal (prin1-to-string (list x y))
1863 "(#1=(a #1#) #2=(a #2#))")))
1864 (let* ((x '#1=(#1# a))
1865 (y (bytecomp--copy-tree x)))
1866 (should (equal (prin1-to-string (list x y))
1867 "(#1=(#1# a) #2=(#2# a))")))
1868 (let* ((x '((a . #1=(b)) #1#))
1869 (y (bytecomp--copy-tree x)))
1870 (should (equal (prin1-to-string (list x y))
1871 "(((a . #1=(b)) #1#) ((a . #2=(b)) #2#))")))
1872 (let* ((x '#1=(a #2=(#1# b . #3=(#2# c . #1#)) (#3# d)))
1873 (y (bytecomp--copy-tree x)))
1874 (should (equal (prin1-to-string (list x y))
1875 (concat
1876 "("
1877 "#1=(a #2=(#1# b . #3=(#2# c . #1#)) (#3# d))"
1878 " "
1879 "#4=(a #5=(#4# b . #6=(#5# c . #4#)) (#6# d))"
1880 ")"))))))
1853 1881
1854;; Local Variables: 1882;; Local Variables:
1855;; no-byte-compile: t 1883;; 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 @@
75 (should (equal '((regexp . "(string-match-p \"^[fo]+\" \"foobar\")\n => 0")) 75 (should (equal '((regexp . "(string-match-p \"^[fo]+\" \"foobar\")\n => 0"))
76 (shortdoc-function-examples 'string-match-p)))) 76 (shortdoc-function-examples 'string-match-p))))
77 77
78(ert-deftest shortdoc-help-fns-examples-function-test ()
79 "Test that `shortdoc-help-fns-examples-function' correctly prints ELisp function examples."
80 (with-temp-buffer
81 (shortdoc-help-fns-examples-function 'string-fill)
82 (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"
83 (buffer-substring-no-properties (point-min) (point-max))))
84 (erase-buffer)
85 (shortdoc-help-fns-examples-function 'assq)
86 (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"
87 (buffer-substring-no-properties (point-min) (point-max))))
88 (erase-buffer)
89 (shortdoc-help-fns-examples-function 'string-trim)
90 (should (equal "\n Example:\n\n (string-trim \" foo \")\n => \"foo\"\n\n"
91 (buffer-substring-no-properties (point-min) (point-max))))))
92
78(provide 'shortdoc-tests) 93(provide 'shortdoc-tests)
79 94
80;;; shortdoc-tests.el ends here 95;;; 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 @@
1Code:
2 (lambda ()
3 (setq indent-tabs-mode nil)
4 (elixir-ts-mode)
5 (indent-region (point-min) (point-max)))
6
7Point-Char: $
8
9Name: Basic modules
10
11=-=
12 defmodule Foobar do
13def bar() do
14"one"
15 end
16 end
17=-=
18defmodule Foobar do
19 def bar() do
20 "one"
21 end
22end
23=-=-=
24
25Name: Map
26
27=-=
28map = %{
29 "a" => 1,
30 "b" => 2
31}
32=-=-=
33
34Name: Map in function def
35
36=-=
37def foobar() do
38 %{
39 one: "one",
40 two: "two",
41 three: "three",
42 four: "four"
43 }
44end
45=-=-=
46
47Name: Map in tuple
48
49=-=
50def foo() do
51 {:ok,
52 %{
53 state
54 | extra_arguments: extra_arguments,
55 max_children: max_children,
56 max_restarts: max_restarts,
57 max_seconds: max_seconds,
58 strategy: strategy
59 }}
60end
61=-=-=
62
63Name: Nested maps
64
65=-=
66%{
67 foo: "bar",
68 bar: %{
69 foo: "bar"
70 }
71}
72
73def foo() do
74 %{
75 foo: "bar",
76 bar: %{
77 foo: "bar"
78 }
79 }
80end
81=-=-=
82
83Name: Block assignments
84
85=-=
86foo =
87 if true do
88 "yes"
89 else
90 "no"
91 end
92=-=-=
93
94Name: Function rescue
95
96=-=
97def foo do
98 "bar"
99rescue
100 e ->
101 "bar"
102end
103=-=-=
104
105Name: With statement
106=-=
107with one <- one(),
108 two <- two(),
109 {:ok, value} <- get_value(one, two) do
110 {:ok, value}
111else
112 {:error, %{"Message" => message}} ->
113 {:error, message}
114end
115=-=-=
116
117Name: Pipe statements with fn
118
119=-=
120[1, 2]
121|> Enum.map(fn num ->
122 num + 1
123end)
124=-=-=
125
126Name: Pipe statements stab clases
127
128=-=
129[1, 2]
130|> Enum.map(fn
131 x when x < 10 -> x * 2
132 x -> x * 3
133end)
134=-=-=
135
136Name: Pipe statements params
137
138=-=
139[1, 2]
140|> foobar(
141 :one,
142 :two,
143 :three,
144 :four
145)
146=-=-=
147
148Name: Parameter maps
149
150=-=
151def something(%{
152 one: :one,
153 two: :two
154 }) do
155 {:ok, "done"}
156end
157=-=-=
158
159Name: Binary operator in else block
160
161=-=
162defp foobar() do
163 if false do
164 :foo
165 else
166 :bar |> foo
167 end
168end
169=-=-=
170
171Name: Tuple indentation
172
173=-=
174tuple = {
175 :one,
176 :two
177}
178
179{
180 :one,
181 :two
182}
183=-=-=
184
185Name: Spec and method
186
187=-=
188@spec foobar(
189 t,
190 acc,
191 (one, something -> :bar | far),
192 (two -> :bar | far)
193 ) :: any()
194 when chunk: any
195def foobar(enumerable, acc, chunk_fun, after_fun) do
196 {_, {res, acc}} =
197 case after_fun.(acc) do
198 {:one, "one"} ->
199 "one"
200
201 {:two, "two"} ->
202 "two"
203 end
204end
205=-=-=
206
207Name: Spec with multi-line result
208
209=-=
210@type result ::
211 {:done, term}
212 | {:two}
213 | {:one}
214
215@type result ::
216 {
217 :done,
218 term
219 }
220 | {:two}
221 | {:one}
222
223@type boo_bar ::
224 (foo :: pos_integer, bar :: pos_integer -> any())
225
226@spec foo_bar(
227 t,
228 (foo -> any),
229 (() -> any) | (foo, foo -> boolean) | module()
230 ) :: any
231 when foo: any
232def foo(one, fun, other)
233=-=-=
234
235Name: String concatenation in call
236
237=-=
238IO.warn(
239 "one" <>
240 "two" <>
241 "bar"
242)
243
244IO.warn(
245 "foo" <>
246 "bar"
247)
248=-=-=
249
250Name: Incomplete tuple
251
252=-=
253map = {
254:foo
255
256=-=
257map = {
258 :foo
259
260=-=-=
261
262Name: Incomplete map
263
264=-=
265map = %{
266 "a" => "a",
267=-=-=
268
269Name: Incomplete list
270
271=-=
272map = [
273:foo
274
275=-=
276map = [
277 :foo
278
279=-=-=
280
281Name: String concatenation
282
283=-=
284"one" <>
285 "two" <>
286 "three" <>
287 "four"
288=-=-=
289
290Name: Tuple with same line first node
291
292=-=
293{:one,
294 :two}
295
296{:ok,
297 fn one ->
298 one
299 |> String.upcase(one)
300 end}
301=-=-=
302
303Name: Long tuple
304
305=-=
306{"January", "February", "March", "April", "May", "June", "July", "August", "September",
307 "October", "November", "December"}
308=-=-=
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 @@
1;;; c-ts-mode-tests.el --- Tests for Tree-sitter-based C mode -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2023 Free Software Foundation, Inc.
4
5;; This file is part of GNU Emacs.
6
7;; GNU Emacs is free software: you can redistribute it and/or modify
8;; it under the terms of the GNU General Public License as published by
9;; the Free Software Foundation, either version 3 of the License, or
10;; (at your option) any later version.
11
12;; GNU Emacs is distributed in the hope that it will be useful,
13;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15;; GNU General Public License for more details.
16
17;; You should have received a copy of the GNU General Public License
18;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
19
20;;; Code:
21
22(require 'ert)
23(require 'ert-x)
24(require 'treesit)
25
26(ert-deftest elixir-ts-mode-test-indentation ()
27 (skip-unless (and (treesit-ready-p 'elixir) (treesit-ready-p 'heex)))
28 (ert-test-erts-file (ert-resource-file "indent.erts")))
29
30(provide 'elixir-ts-mode-tests)
31;;; 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 @@
1Code:
2 (lambda ()
3 (setq indent-tabs-mode nil)
4 (heex-ts-mode)
5 (indent-region (point-min) (point-max)))
6
7Point-Char: $
8
9Name: Tag
10
11=-=
12 <div>
13 div
14 </div>
15=-=
16<div>
17 div
18</div>
19=-=-=
20
21Name: Component
22
23=-=
24 <Foo>
25 foobar
26 </Foo>
27=-=
28<Foo>
29 foobar
30</Foo>
31=-=-=
32
33Name: Slots
34
35=-=
36 <Foo>
37 <:bar>
38 foobar
39 </:bar>
40 </Foo>
41=-=
42<Foo>
43 <:bar>
44 foobar
45 </:bar>
46</Foo>
47=-=-=
diff --git a/test/lisp/progmodes/heex-ts-mode-tests.el b/test/lisp/progmodes/heex-ts-mode-tests.el
new file mode 100644
index 00000000000..b59126e136a
--- /dev/null
+++ b/test/lisp/progmodes/heex-ts-mode-tests.el
@@ -0,0 +1,9 @@
1(require 'ert)
2(require 'ert-x)
3(require 'treesit)
4
5(ert-deftest heex-ts-mode-test-indentation ()
6 (skip-unless (treesit-ready-p 'heex))
7 (ert-test-erts-file (ert-resource-file "indent.erts")))
8
9(provide 'heex-ts-mode-tests)
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."))
1205 (should (equal a-dedup '("a" "b" "a" "b" "c"))) 1205 (should (equal a-dedup '("a" "b" "a" "b" "c")))
1206 (should (eq a a-dedup)))) 1206 (should (eq a a-dedup))))
1207 1207
1208(ert-deftest subr--safe-copy-tree ()
1209 (should (null (safe-copy-tree nil)))
1210 (let* ((foo '(1 2 (3 4))) (bar (safe-copy-tree foo)))
1211 (should (equal bar foo))
1212 (should-not (eq bar foo))
1213 (should-not (eq (caddr bar) (caddr foo))))
1214 (let* ((foo '#1=(a #1#)) (bar (safe-copy-tree foo)))
1215 (should (eq (car bar) (car foo)))
1216; (should-not (proper-list-p bar))
1217 (should (eq (caadr bar) (caadr foo)))
1218 (should (eq (caadr bar) 'a)))
1219 (let* ((foo [1 2 3 4]) (bar (safe-copy-tree foo)))
1220 (should (eq bar foo)))
1221 (let* ((foo [1 (2 3) 4]) (bar (safe-copy-tree foo t)))
1222 (should-not (eq bar foo))
1223 (should (equal bar foo))
1224 (should-not (eq (aref bar 1) (aref foo 1))))
1225 (let* ((foo [1 [2 3] 4]) (bar (safe-copy-tree foo t)))
1226 (should (equal bar foo))
1227 (should-not (eq bar foo))
1228 (should-not (eq (aref bar 1) (aref foo 1))))
1229 (let* ((foo (record 'foo 1 "two" 3)) (bar (safe-copy-tree foo t)))
1230 (should (equal bar foo))
1231 (should-not (eq bar foo))
1232 (should (eq (aref bar 2) (aref foo 2)))))
1233
1234(provide 'subr-tests) 1208(provide 'subr-tests)
1235;;; subr-tests.el ends here 1209;;; subr-tests.el ends here