diff options
| author | Po Lu | 2023-03-13 07:52:08 +0800 |
|---|---|---|
| committer | Po Lu | 2023-03-13 07:52:08 +0800 |
| commit | a517c24697d080475e2d531c8ce1d433aa44a9c6 (patch) | |
| tree | 057e84aa396d4b2266d5ff007d23265727d633f4 | |
| parent | 08a3749794b8e6c1b3db882ee15e3e91f3700414 (diff) | |
| parent | 75f04848a653e70f12f0e5a62b756c5bba0dd204 (diff) | |
| download | emacs-a517c24697d080475e2d531c8ce1d433aa44a9c6.tar.gz emacs-a517c24697d080475e2d531c8ce1d433aa44a9c6.zip | |
Merge remote-tracking branch 'origin/master' into feature/android
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 | |||
| 708 | their elements). This function cannot cope with circular lists. | 708 | their elements). This function cannot cope with circular lists. |
| 709 | @end defun | 709 | @end defun |
| 710 | 710 | ||
| 711 | @defun safe-copy-tree tree &optional vecp | ||
| 712 | This function returns a copy of the tree @var{tree}. If @var{tree} is | ||
| 713 | a 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 | ||
| 715 | same way. | ||
| 716 | |||
| 717 | Normally, when @var{tree} is anything other than a cons cell, | ||
| 718 | @code{copy-tree} simply returns @var{tree}. However, if @var{vecp} is | ||
| 719 | non-@code{nil}, it copies vectors and records too (and operates | ||
| 720 | recursively on their elements). This function handles circular lists | ||
| 721 | and 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 |
| 725 | This function returns a ``flattened'' copy of @var{tree}, that is, | 712 | This function returns a ``flattened'' copy of @var{tree}, that is, |
| 726 | a list containing all the non-@code{nil} terminal nodes, or leaves, of | 713 | a list containing all the non-@code{nil} terminal nodes, or leaves, of |
| @@ -261,6 +261,15 @@ An optional major mode based on the tree-sitter library for editing | |||
| 261 | HTML files. | 261 | HTML files. |
| 262 | 262 | ||
| 263 | --- | 263 | --- |
| 264 | *** New major mode heex-ts-mode'. | ||
| 265 | A major mode based on the tree-sitter library for editing HEEx files. | ||
| 266 | |||
| 267 | --- | ||
| 268 | *** New major mode elixir-ts-mode'. | ||
| 269 | A major mode based on the tree-sitter library for editing Elixir | ||
| 270 | files. | ||
| 271 | |||
| 272 | --- | ||
| 264 | ** The highly accessible Modus themes collection has six items. | 273 | ** The highly accessible Modus themes collection has six items. |
| 265 | The 'modus-operandi' and 'modus-vivendi' are the main themes that have | 274 | The 'modus-operandi' and 'modus-vivendi' are the main themes that have |
| 266 | been part of Emacs since version 28. The former is light, the latter | 275 | been 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 | |||
| 414 | the warning name 'suspicious'. | 423 | the warning name 'suspicious'. |
| 415 | 424 | ||
| 416 | +++ | 425 | +++ |
| 417 | ** New function 'safe-copy-tree' | ||
| 418 | This function is a version of copy-tree which handles circular lists | ||
| 419 | and circular vectors/records. | ||
| 420 | |||
| 421 | +++ | ||
| 422 | ** New function 'file-user-uid'. | 426 | ** New function 'file-user-uid'. |
| 423 | This function is like 'user-uid', but is aware of file name handlers, | 427 | This function is like 'user-uid', but is aware of file name handlers, |
| 424 | so it will return the remote UID for remote files (or -1 if the | 428 | so 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. | ||
| 528 | Only 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." | |||
| 1621 | You can add this function to the `help-fns-describe-function-functions' | 1621 | You can add this function to the `help-fns-describe-function-functions' |
| 1622 | hook to show examples of using FUNCTION in *Help* buffers produced | 1622 | hook to show examples of using FUNCTION in *Help* buffers produced |
| 1623 | by \\[describe-function]." | 1623 | by \\[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). | ||
| 485 | With 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. | ||
| 529 | Return 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. | ||
| 126 | Return 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). | ||
| 136 | With 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. | ||
| 851 | Its key is a cons or vector/record seen by the algorithm, and its | ||
| 852 | value 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. | ||
| 856 | If TREE is a cons cell, this recursively copies both its car and its cdr. | ||
| 857 | Contrast to `copy-sequence', which copies only along the cdrs. With second | ||
| 858 | argument 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. | ||
| 895 | If TREE is a cons cell, this recursively copies both its car and its cdr. | ||
| 896 | Contrast to `copy-sequence', which copies only along the cdrs. With second | ||
| 897 | argument 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 @@ | |||
| 1 | Code: | ||
| 2 | (lambda () | ||
| 3 | (setq indent-tabs-mode nil) | ||
| 4 | (elixir-ts-mode) | ||
| 5 | (indent-region (point-min) (point-max))) | ||
| 6 | |||
| 7 | Point-Char: $ | ||
| 8 | |||
| 9 | Name: Basic modules | ||
| 10 | |||
| 11 | =-= | ||
| 12 | defmodule Foobar do | ||
| 13 | def bar() do | ||
| 14 | "one" | ||
| 15 | end | ||
| 16 | end | ||
| 17 | =-= | ||
| 18 | defmodule Foobar do | ||
| 19 | def bar() do | ||
| 20 | "one" | ||
| 21 | end | ||
| 22 | end | ||
| 23 | =-=-= | ||
| 24 | |||
| 25 | Name: Map | ||
| 26 | |||
| 27 | =-= | ||
| 28 | map = %{ | ||
| 29 | "a" => 1, | ||
| 30 | "b" => 2 | ||
| 31 | } | ||
| 32 | =-=-= | ||
| 33 | |||
| 34 | Name: Map in function def | ||
| 35 | |||
| 36 | =-= | ||
| 37 | def foobar() do | ||
| 38 | %{ | ||
| 39 | one: "one", | ||
| 40 | two: "two", | ||
| 41 | three: "three", | ||
| 42 | four: "four" | ||
| 43 | } | ||
| 44 | end | ||
| 45 | =-=-= | ||
| 46 | |||
| 47 | Name: Map in tuple | ||
| 48 | |||
| 49 | =-= | ||
| 50 | def 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 | }} | ||
| 60 | end | ||
| 61 | =-=-= | ||
| 62 | |||
| 63 | Name: Nested maps | ||
| 64 | |||
| 65 | =-= | ||
| 66 | %{ | ||
| 67 | foo: "bar", | ||
| 68 | bar: %{ | ||
| 69 | foo: "bar" | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | def foo() do | ||
| 74 | %{ | ||
| 75 | foo: "bar", | ||
| 76 | bar: %{ | ||
| 77 | foo: "bar" | ||
| 78 | } | ||
| 79 | } | ||
| 80 | end | ||
| 81 | =-=-= | ||
| 82 | |||
| 83 | Name: Block assignments | ||
| 84 | |||
| 85 | =-= | ||
| 86 | foo = | ||
| 87 | if true do | ||
| 88 | "yes" | ||
| 89 | else | ||
| 90 | "no" | ||
| 91 | end | ||
| 92 | =-=-= | ||
| 93 | |||
| 94 | Name: Function rescue | ||
| 95 | |||
| 96 | =-= | ||
| 97 | def foo do | ||
| 98 | "bar" | ||
| 99 | rescue | ||
| 100 | e -> | ||
| 101 | "bar" | ||
| 102 | end | ||
| 103 | =-=-= | ||
| 104 | |||
| 105 | Name: With statement | ||
| 106 | =-= | ||
| 107 | with one <- one(), | ||
| 108 | two <- two(), | ||
| 109 | {:ok, value} <- get_value(one, two) do | ||
| 110 | {:ok, value} | ||
| 111 | else | ||
| 112 | {:error, %{"Message" => message}} -> | ||
| 113 | {:error, message} | ||
| 114 | end | ||
| 115 | =-=-= | ||
| 116 | |||
| 117 | Name: Pipe statements with fn | ||
| 118 | |||
| 119 | =-= | ||
| 120 | [1, 2] | ||
| 121 | |> Enum.map(fn num -> | ||
| 122 | num + 1 | ||
| 123 | end) | ||
| 124 | =-=-= | ||
| 125 | |||
| 126 | Name: Pipe statements stab clases | ||
| 127 | |||
| 128 | =-= | ||
| 129 | [1, 2] | ||
| 130 | |> Enum.map(fn | ||
| 131 | x when x < 10 -> x * 2 | ||
| 132 | x -> x * 3 | ||
| 133 | end) | ||
| 134 | =-=-= | ||
| 135 | |||
| 136 | Name: Pipe statements params | ||
| 137 | |||
| 138 | =-= | ||
| 139 | [1, 2] | ||
| 140 | |> foobar( | ||
| 141 | :one, | ||
| 142 | :two, | ||
| 143 | :three, | ||
| 144 | :four | ||
| 145 | ) | ||
| 146 | =-=-= | ||
| 147 | |||
| 148 | Name: Parameter maps | ||
| 149 | |||
| 150 | =-= | ||
| 151 | def something(%{ | ||
| 152 | one: :one, | ||
| 153 | two: :two | ||
| 154 | }) do | ||
| 155 | {:ok, "done"} | ||
| 156 | end | ||
| 157 | =-=-= | ||
| 158 | |||
| 159 | Name: Binary operator in else block | ||
| 160 | |||
| 161 | =-= | ||
| 162 | defp foobar() do | ||
| 163 | if false do | ||
| 164 | :foo | ||
| 165 | else | ||
| 166 | :bar |> foo | ||
| 167 | end | ||
| 168 | end | ||
| 169 | =-=-= | ||
| 170 | |||
| 171 | Name: Tuple indentation | ||
| 172 | |||
| 173 | =-= | ||
| 174 | tuple = { | ||
| 175 | :one, | ||
| 176 | :two | ||
| 177 | } | ||
| 178 | |||
| 179 | { | ||
| 180 | :one, | ||
| 181 | :two | ||
| 182 | } | ||
| 183 | =-=-= | ||
| 184 | |||
| 185 | Name: 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 | ||
| 195 | def 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 | ||
| 204 | end | ||
| 205 | =-=-= | ||
| 206 | |||
| 207 | Name: 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 | ||
| 232 | def foo(one, fun, other) | ||
| 233 | =-=-= | ||
| 234 | |||
| 235 | Name: String concatenation in call | ||
| 236 | |||
| 237 | =-= | ||
| 238 | IO.warn( | ||
| 239 | "one" <> | ||
| 240 | "two" <> | ||
| 241 | "bar" | ||
| 242 | ) | ||
| 243 | |||
| 244 | IO.warn( | ||
| 245 | "foo" <> | ||
| 246 | "bar" | ||
| 247 | ) | ||
| 248 | =-=-= | ||
| 249 | |||
| 250 | Name: Incomplete tuple | ||
| 251 | |||
| 252 | =-= | ||
| 253 | map = { | ||
| 254 | :foo | ||
| 255 | |||
| 256 | =-= | ||
| 257 | map = { | ||
| 258 | :foo | ||
| 259 | |||
| 260 | =-=-= | ||
| 261 | |||
| 262 | Name: Incomplete map | ||
| 263 | |||
| 264 | =-= | ||
| 265 | map = %{ | ||
| 266 | "a" => "a", | ||
| 267 | =-=-= | ||
| 268 | |||
| 269 | Name: Incomplete list | ||
| 270 | |||
| 271 | =-= | ||
| 272 | map = [ | ||
| 273 | :foo | ||
| 274 | |||
| 275 | =-= | ||
| 276 | map = [ | ||
| 277 | :foo | ||
| 278 | |||
| 279 | =-=-= | ||
| 280 | |||
| 281 | Name: String concatenation | ||
| 282 | |||
| 283 | =-= | ||
| 284 | "one" <> | ||
| 285 | "two" <> | ||
| 286 | "three" <> | ||
| 287 | "four" | ||
| 288 | =-=-= | ||
| 289 | |||
| 290 | Name: 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 | |||
| 303 | Name: 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 @@ | |||
| 1 | Code: | ||
| 2 | (lambda () | ||
| 3 | (setq indent-tabs-mode nil) | ||
| 4 | (heex-ts-mode) | ||
| 5 | (indent-region (point-min) (point-max))) | ||
| 6 | |||
| 7 | Point-Char: $ | ||
| 8 | |||
| 9 | Name: Tag | ||
| 10 | |||
| 11 | =-= | ||
| 12 | <div> | ||
| 13 | div | ||
| 14 | </div> | ||
| 15 | =-= | ||
| 16 | <div> | ||
| 17 | div | ||
| 18 | </div> | ||
| 19 | =-=-= | ||
| 20 | |||
| 21 | Name: Component | ||
| 22 | |||
| 23 | =-= | ||
| 24 | <Foo> | ||
| 25 | foobar | ||
| 26 | </Foo> | ||
| 27 | =-= | ||
| 28 | <Foo> | ||
| 29 | foobar | ||
| 30 | </Foo> | ||
| 31 | =-=-= | ||
| 32 | |||
| 33 | Name: 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 |