Merge remote-tracking branch 'origin/master' into feature/android

This commit is contained in:
Po Lu 2023-03-13 07:52:08 +08:00
commit a517c24697
25 changed files with 1481 additions and 232 deletions

View file

@ -8,8 +8,10 @@ languages=(
'css'
'c-sharp'
'dockerfile'
'elixir'
'go'
'go-mod'
'heex'
'html'
'javascript'
'json'

View file

@ -31,11 +31,17 @@ case "${lang}" in
"cmake")
org="uyha"
;;
"elixir")
org="elixir-lang"
;;
"go-mod")
# The parser is called "gomod".
lang="gomod"
org="camdencheek"
;;
"heex")
org="phoenixframework"
;;
"typescript")
sourcedir="tree-sitter-typescript/typescript/src"
grammardir="tree-sitter-typescript/typescript"

View file

@ -708,19 +708,6 @@ non-@code{nil}, it copies vectors too (and operates recursively on
their elements). This function cannot cope with circular lists.
@end defun
@defun safe-copy-tree tree &optional vecp
This function returns a copy of the tree @var{tree}. If @var{tree} is
a cons cell, this make a new cons cell with the same @sc{car} and
@sc{cdr}, then recursively copies the @sc{car} and @sc{cdr} in the
same way.
Normally, when @var{tree} is anything other than a cons cell,
@code{copy-tree} simply returns @var{tree}. However, if @var{vecp} is
non-@code{nil}, it copies vectors and records too (and operates
recursively on their elements). This function handles circular lists
and vectors, and is thus slower than @code{copy-tree} for typical cases.
@end defun
@defun flatten-tree tree
This function returns a ``flattened'' copy of @var{tree}, that is,
a list containing all the non-@code{nil} terminal nodes, or leaves, of

View file

@ -260,6 +260,15 @@ following to you init file:
An optional major mode based on the tree-sitter library for editing
HTML files.
---
*** New major mode heex-ts-mode'.
A major mode based on the tree-sitter library for editing HEEx files.
---
*** New major mode elixir-ts-mode'.
A major mode based on the tree-sitter library for editing Elixir
files.
---
** The highly accessible Modus themes collection has six items.
The 'modus-operandi' and 'modus-vivendi' are the main themes that have
@ -413,11 +422,6 @@ was to catch all errors, add an explicit handler for 'error', or use
This warning can be suppressed using 'with-suppressed-warnings' with
the warning name 'suspicious'.
+++
** New function 'safe-copy-tree'
This function is a version of copy-tree which handles circular lists
and circular vectors/records.
+++
** New function 'file-user-uid'.
This function is like 'user-uid', but is aware of file name handlers,

View file

@ -495,6 +495,42 @@ Return the compile-time value of FORM."
(cdr form)))
(funcall non-toplevel-case form)))
(defvar bytecomp--copy-tree-seen)
(defun bytecomp--copy-tree-1 (tree)
;; TREE must be a cons.
(or (gethash tree bytecomp--copy-tree-seen)
(let* ((next (cdr tree))
(result (cons nil next))
(copy result))
(while (progn
(puthash tree copy bytecomp--copy-tree-seen)
(let ((a (car tree)))
(setcar copy (if (consp a)
(bytecomp--copy-tree-1 a)
a)))
(and (consp next)
(let ((tail (gethash next bytecomp--copy-tree-seen)))
(if tail
(progn (setcdr copy tail)
nil)
(setq tree next)
(setq next (cdr next))
(let ((prev copy))
(setq copy (cons nil next))
(setcdr prev copy)
t))))))
result)))
(defun bytecomp--copy-tree (tree)
"Make a copy of TREE, preserving any circular structure therein.
Only conses are traversed and duplicated, not arrays or any other structure."
(if (consp tree)
(let ((bytecomp--copy-tree-seen (make-hash-table :test #'eq)))
(bytecomp--copy-tree-1 tree))
tree))
(defconst byte-compile-initial-macro-environment
`(
;; (byte-compiler-options . (lambda (&rest forms)
@ -534,7 +570,7 @@ Return the compile-time value of FORM."
form
macroexpand-all-environment)))
(eval (byte-run-strip-symbol-positions
(safe-copy-tree expanded))
(bytecomp--copy-tree expanded))
lexical-binding)
expanded)))))
(with-suppressed-warnings

View file

@ -1621,13 +1621,38 @@ doesn't has any shortdoc information."
You can add this function to the `help-fns-describe-function-functions'
hook to show examples of using FUNCTION in *Help* buffers produced
by \\[describe-function]."
(let ((examples (shortdoc-function-examples function))
(times 0))
(let* ((examples (shortdoc-function-examples function))
(num-examples (length examples))
(times 0))
(dolist (example examples)
(when (zerop times)
(if (eq (length examples) 1)
(insert "\n Example:\n\n")
(insert "\n Examples:\n\n")))
(if (> num-examples 1)
(insert "\n Examples:\n\n")
;; Some functions have more than one example per group.
;; Count the number of arrows to know if we need to
;; pluralize "Example".
(let* ((text (cdr example))
(count 0)
(pos 0)
(end (length text))
(double-arrow (if (char-displayable-p ?⇒)
""
" =>"))
(double-arrow-example (if (char-displayable-p ?⇒)
" e.g. ⇒"
" e.g. =>"))
(single-arrow (if (char-displayable-p ?→)
""
" ->")))
(while (and (< pos end)
(or (string-match double-arrow text pos)
(string-match double-arrow-example text pos)
(string-match single-arrow text pos)))
(setq count (1+ count)
pos (match-end 0)))
(if (> count 1)
(insert "\n Examples:\n\n")
(insert "\n Example:\n\n")))))
(setq times (1+ times))
(insert " ")
(insert (cdr example))

View file

@ -432,31 +432,32 @@ Emacs dired can't find files."
(defun tramp-adb-handle-file-name-all-completions (filename directory)
"Like `file-name-all-completions' for Tramp files."
(all-completions
filename
(with-parsed-tramp-file-name (expand-file-name directory) nil
(with-tramp-file-property v localname "file-name-all-completions"
(tramp-adb-send-command
v (format "%s -a %s | cat"
(tramp-adb-get-ls-command v)
(tramp-shell-quote-argument localname)))
(mapcar
(lambda (f)
(if (file-directory-p (expand-file-name f directory))
(file-name-as-directory f)
f))
(with-current-buffer (tramp-get-buffer v)
(delete-dups
(append
;; On some file systems like "sdcard", "." and ".." are
;; not included. We fix this by `delete-dups'.
'("." "..")
(delq
nil
(mapcar
(lambda (l)
(and (not (string-match-p (rx bol (* blank) eol) l)) l))
(split-string (buffer-string) "\n")))))))))))
(ignore-error file-missing
(all-completions
filename
(with-parsed-tramp-file-name (expand-file-name directory) nil
(with-tramp-file-property v localname "file-name-all-completions"
(tramp-adb-send-command
v (format "%s -a %s | cat"
(tramp-adb-get-ls-command v)
(tramp-shell-quote-argument localname)))
(mapcar
(lambda (f)
(if (file-directory-p (expand-file-name f directory))
(file-name-as-directory f)
f))
(with-current-buffer (tramp-get-buffer v)
(delete-dups
(append
;; On some file systems like "sdcard", "." and ".." are
;; not included. We fix this by `delete-dups'.
'("." "..")
(delq
nil
(mapcar
(lambda (l)
(and (not (string-match-p (rx bol (* blank) eol) l)) l))
(split-string (buffer-string) "\n"))))))))))))
(defun tramp-adb-handle-file-local-copy (filename)
"Like `file-local-copy' for Tramp files."

View file

@ -650,7 +650,9 @@ offered."
(defun tramp-archive-handle-file-name-all-completions (filename directory)
"Like `file-name-all-completions' for file archives."
(file-name-all-completions filename (tramp-archive-gvfs-file-name directory)))
(ignore-error file-missing
(file-name-all-completions
filename (tramp-archive-gvfs-file-name directory))))
(defun tramp-archive-handle-file-readable-p (filename)
"Like `file-readable-p' for file archives."

View file

@ -730,18 +730,19 @@ absolute file names."
(defun tramp-crypt-handle-file-name-all-completions (filename directory)
"Like `file-name-all-completions' for Tramp files."
(all-completions
filename
(let* (completion-regexp-list
tramp-crypt-enabled
(directory (file-name-as-directory directory))
(enc-dir (tramp-crypt-encrypt-file-name directory)))
(mapcar
(lambda (x)
(substring
(tramp-crypt-decrypt-file-name (concat enc-dir x))
(length directory)))
(file-name-all-completions "" enc-dir)))))
(ignore-error file-missing
(all-completions
filename
(let* (completion-regexp-list
tramp-crypt-enabled
(directory (file-name-as-directory directory))
(enc-dir (tramp-crypt-encrypt-file-name directory)))
(mapcar
(lambda (x)
(substring
(tramp-crypt-decrypt-file-name (concat enc-dir x))
(length directory)))
(file-name-all-completions "" enc-dir))))))
(defun tramp-crypt-handle-file-readable-p (filename)
"Like `file-readable-p' for Tramp files."

View file

@ -98,20 +98,21 @@
(defun tramp-fuse-handle-file-name-all-completions (filename directory)
"Like `file-name-all-completions' for Tramp files."
(tramp-fuse-remove-hidden-files
(all-completions
filename
(delete-dups
(append
(file-name-all-completions
filename (tramp-fuse-local-file-name directory))
;; Some storage systems do not return "." and "..".
(let (result)
(dolist (item '(".." ".") result)
(when (string-prefix-p filename item)
(catch 'match
(dolist (elt completion-regexp-list)
(unless (string-match-p elt item) (throw 'match nil)))
(setq result (cons (concat item "/") result)))))))))))
(ignore-error file-missing
(all-completions
filename
(delete-dups
(append
(file-name-all-completions
filename (tramp-fuse-local-file-name directory))
;; Some storage systems do not return "." and "..".
(let (result)
(dolist (item '(".." ".") result)
(when (string-prefix-p filename item)
(catch 'match
(dolist (elt completion-regexp-list)
(unless (string-match-p elt item) (throw 'match nil)))
(setq result (cons (concat item "/") result))))))))))))
;; This function isn't used.
(defun tramp-fuse-handle-insert-directory

View file

@ -1418,16 +1418,19 @@ If FILE-SYSTEM is non-nil, return file system attributes."
(defun tramp-gvfs-handle-file-name-all-completions (filename directory)
"Like `file-name-all-completions' for Tramp files."
(unless (tramp-compat-string-search "/" filename)
(all-completions
filename
(with-parsed-tramp-file-name (expand-file-name directory) nil
(with-tramp-file-property v localname "file-name-all-completions"
(let ((result '("./" "../")))
;; Get a list of directories and files.
(dolist (item (tramp-gvfs-get-directory-attributes directory) result)
(if (string-equal (cdr (assoc "type" item)) "directory")
(push (file-name-as-directory (car item)) result)
(push (car item) result)))))))))
(ignore-error file-missing
(all-completions
filename
(with-parsed-tramp-file-name (expand-file-name directory) nil
(with-tramp-file-property v localname "file-name-all-completions"
(let ((result '("./" "../")))
;; Get a list of directories and files.
(dolist (item
(tramp-gvfs-get-directory-attributes directory)
result)
(if (string-equal (cdr (assoc "type" item)) "directory")
(push (file-name-as-directory (car item)) result)
(push (car item) result))))))))))
(defun tramp-gvfs-handle-file-notify-add-watch (file-name flags _callback)
"Like `file-notify-add-watch' for Tramp files."

View file

@ -1767,41 +1767,43 @@ ID-FORMAT valid values are `string' and `integer'."
(with-parsed-tramp-file-name (expand-file-name directory) nil
(when (and (not (tramp-compat-string-search "/" filename))
(tramp-connectable-p v))
(all-completions
filename
(with-tramp-file-property v localname "file-name-all-completions"
(let (result)
;; Get a list of directories and files, including reliably
;; tagging the directories with a trailing "/". Because I
;; rock. --daniel@danann.net
(when (tramp-send-command-and-check
v
(if (tramp-get-remote-perl v)
(progn
(tramp-maybe-send-script
v tramp-perl-file-name-all-completions
"tramp_perl_file_name_all_completions")
(format "tramp_perl_file_name_all_completions %s"
(tramp-shell-quote-argument localname)))
(unless (tramp-compat-string-search "/" filename)
(ignore-error file-missing
(all-completions
filename
(with-tramp-file-property v localname "file-name-all-completions"
(let (result)
;; Get a list of directories and files, including
;; reliably tagging the directories with a trailing "/".
;; Because I rock. --daniel@danann.net
(when (tramp-send-command-and-check
v
(if (tramp-get-remote-perl v)
(progn
(tramp-maybe-send-script
v tramp-perl-file-name-all-completions
"tramp_perl_file_name_all_completions")
(format "tramp_perl_file_name_all_completions %s"
(tramp-shell-quote-argument localname)))
(format (concat
"cd %s 2>&1 && %s -a 2>%s"
" | while IFS= read f; do"
" if %s -d \"$f\" 2>%s;"
" then \\echo \"$f/\"; else \\echo \"$f\"; fi;"
" done")
(tramp-shell-quote-argument localname)
(tramp-get-ls-command v)
(tramp-get-remote-null-device v)
(tramp-get-test-command v)
(tramp-get-remote-null-device v))))
(format (concat
"cd %s 2>&1 && %s -a 2>%s"
" | while IFS= read f; do"
" if %s -d \"$f\" 2>%s;"
" then \\echo \"$f/\"; else \\echo \"$f\"; fi;"
" done")
(tramp-shell-quote-argument localname)
(tramp-get-ls-command v)
(tramp-get-remote-null-device v)
(tramp-get-test-command v)
(tramp-get-remote-null-device v))))
;; Now grab the output.
(with-current-buffer (tramp-get-buffer v)
(goto-char (point-max))
(while (zerop (forward-line -1))
(push (buffer-substring (point) (line-end-position)) result)))
result)))))))
;; Now grab the output.
(with-current-buffer (tramp-get-buffer v)
(goto-char (point-max))
(while (zerop (forward-line -1))
(push (buffer-substring (point) (line-end-position)) result)))
result)))))))))
;; cp, mv and ln

View file

@ -976,18 +976,20 @@ PRESERVE-UID-GID and PRESERVE-EXTENDED-ATTRIBUTES are completely ignored."
;; files.
(defun tramp-smb-handle-file-name-all-completions (filename directory)
"Like `file-name-all-completions' for Tramp files."
(all-completions
filename
(with-parsed-tramp-file-name (expand-file-name directory) nil
(with-tramp-file-property v localname "file-name-all-completions"
(delete-dups
(mapcar
(lambda (x)
(list
(if (tramp-compat-string-search "d" (nth 1 x))
(file-name-as-directory (nth 0 x))
(nth 0 x))))
(tramp-smb-get-file-entries directory)))))))
(ignore-error file-missing
(all-completions
filename
(when (file-directory-p directory)
(with-parsed-tramp-file-name (expand-file-name directory) nil
(with-tramp-file-property v localname "file-name-all-completions"
(delete-dups
(mapcar
(lambda (x)
(list
(if (tramp-compat-string-search "d" (nth 1 x))
(file-name-as-directory (nth 0 x))
(nth 0 x))))
(tramp-smb-get-file-entries directory)))))))))
(defun tramp-smb-handle-file-system-info (filename)
"Like `file-system-info' for Tramp files."

View file

@ -460,26 +460,27 @@ the result will be a local, non-Tramp, file name."
(defun tramp-sudoedit-handle-file-name-all-completions (filename directory)
"Like `file-name-all-completions' for Tramp files."
(all-completions
filename
(with-parsed-tramp-file-name (expand-file-name directory) nil
(with-tramp-file-property v localname "file-name-all-completions"
(tramp-sudoedit-send-command
v "ls" "-a1" "--quoting-style=literal" "--show-control-chars"
(if (tramp-string-empty-or-nil-p localname)
"" (file-name-unquote localname)))
(mapcar
(lambda (f)
(if (ignore-errors (file-directory-p (expand-file-name f directory)))
(file-name-as-directory f)
f))
(delq
nil
(ignore-error file-missing
(all-completions
filename
(with-parsed-tramp-file-name (expand-file-name directory) nil
(with-tramp-file-property v localname "file-name-all-completions"
(tramp-sudoedit-send-command
v "ls" "-a1" "--quoting-style=literal" "--show-control-chars"
(if (tramp-string-empty-or-nil-p localname)
"" (file-name-unquote localname)))
(mapcar
(lambda (l) (and (not (string-match-p (rx bol (* blank) eol) l)) l))
(split-string
(tramp-get-buffer-string (tramp-get-connection-buffer v))
"\n" 'omit))))))))
(lambda (f)
(if (ignore-errors (file-directory-p (expand-file-name f directory)))
(file-name-as-directory f)
f))
(delq
nil
(mapcar
(lambda (l) (and (not (string-match-p (rx bol (* blank) eol) l)) l))
(split-string
(tramp-get-buffer-string (tramp-get-connection-buffer v))
"\n" 'omit)))))))))
(defun tramp-sudoedit-handle-file-readable-p (filename)
"Like `file-readable-p' for Tramp files."

View file

@ -221,7 +221,7 @@ chosen (interactively or automatically)."
((java-mode java-ts-mode) . ("jdtls"))
(dart-mode . ("dart" "language-server"
"--client-id" "emacs.eglot-dart"))
(elixir-mode . ("language_server.sh"))
((elixir-ts-mode elixir-mode) . ("language_server.sh"))
(ada-mode . ("ada_language_server"))
(scala-mode . ,(eglot-alternatives
'("metals" "metals-emacs")))

View file

@ -0,0 +1,634 @@
;;; elixir-ts-mode.el --- Major mode for Elixir with tree-sitter support -*- lexical-binding: t; -*-
;; Copyright (C) 2022-2023 Free Software Foundation, Inc.
;; Author: Wilhelm H Kirschbaum <wkirschbaum@gmail.com>
;; Created: November 2022
;; Keywords: elixir languages tree-sitter
;; This file is part of GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; This package provides `elixir-ts-mode' which is a major mode for editing
;; Elixir files and embedded HEEx templates that uses Tree Sitter to parse
;; the language.
;;
;; This package is compatible with and was tested against the tree-sitter grammar
;; for Elixir found at https://github.com/elixir-lang/tree-sitter-elixir.
;;
;; Features
;;
;; * Indent
;;
;; `elixir-ts-mode' tries to replicate the indentation provided by
;; mix format, but will come with some minor differences.
;;
;; * IMenu
;; * Navigation
;; * Which-fun
;;; Code:
(require 'treesit)
(require 'heex-ts-mode)
(eval-when-compile (require 'rx))
(declare-function treesit-parser-create "treesit.c")
(declare-function treesit-node-child "treesit.c")
(declare-function treesit-node-type "treesit.c")
(declare-function treesit-node-child-by-field-name "treesit.c")
(declare-function treesit-parser-language "treesit.c")
(declare-function treesit-parser-included-ranges "treesit.c")
(declare-function treesit-parser-list "treesit.c")
(declare-function treesit-node-parent "treesit.c")
(declare-function treesit-node-start "treesit.c")
(declare-function treesit-query-compile "treesit.c")
(declare-function treesit-node-eq "treesit.c")
(declare-function treesit-node-prev-sibling "treesit.c")
(defgroup elixir-ts nil
"Major mode for editing Elixir code."
:prefix "elixir-ts-"
:group 'languages)
(defcustom elixir-ts-indent-offset 2
"Indentation of Elixir statements."
:version "30.1"
:type 'integer
:safe 'integerp
:group 'elixir-ts)
(defface elixir-ts-font-comment-doc-identifier-face
'((t (:inherit font-lock-doc-face)))
"Face used for @comment.doc tags in Elixir files.")
(defface elixir-ts-font-comment-doc-attribute-face
'((t (:inherit font-lock-doc-face)))
"Face used for @comment.doc.__attribute__ tags in Elixir files.")
(defface elixir-ts-font-sigil-name-face
'((t (:inherit font-lock-string-face)))
"Face used for @__name__ tags in Elixir files.")
(defconst elixir-ts--sexp-regexp
(rx bol
(or "call" "stab_clause" "binary_operator" "list" "tuple" "map" "pair"
"sigil" "string" "atom" "pair" "alias" "arguments" "atom" "identifier"
"boolean" "quoted_content")
eol))
(defconst elixir-ts--test-definition-keywords
'("describe" "test"))
(defconst elixir-ts--definition-keywords
'("def" "defdelegate" "defexception" "defguard" "defguardp"
"defimpl" "defmacro" "defmacrop" "defmodule" "defn" "defnp"
"defoverridable" "defp" "defprotocol" "defstruct"))
(defconst elixir-ts--definition-keywords-re
(concat "^" (regexp-opt elixir-ts--definition-keywords) "$"))
(defconst elixir-ts--kernel-keywords
'("alias" "case" "cond" "else" "for" "if" "import" "quote"
"raise" "receive" "require" "reraise" "super" "throw" "try"
"unless" "unquote" "unquote_splicing" "use" "with"))
(defconst elixir-ts--kernel-keywords-re
(concat "^" (regexp-opt elixir-ts--kernel-keywords) "$"))
(defconst elixir-ts--builtin-keywords
'("__MODULE__" "__DIR__" "__ENV__" "__CALLER__" "__STACKTRACE__"))
(defconst elixir-ts--builtin-keywords-re
(concat "^" (regexp-opt elixir-ts--builtin-keywords) "$"))
(defconst elixir-ts--doc-keywords
'("moduledoc" "typedoc" "doc"))
(defconst elixir-ts--doc-keywords-re
(concat "^" (regexp-opt elixir-ts--doc-keywords) "$"))
(defconst elixir-ts--reserved-keywords
'("when" "and" "or" "not" "in"
"not in" "fn" "do" "end" "catch" "rescue" "after" "else"))
(defconst elixir-ts--reserved-keywords-re
(concat "^" (regexp-opt elixir-ts--reserved-keywords) "$"))
(defconst elixir-ts--reserved-keywords-vector
(apply #'vector elixir-ts--reserved-keywords))
(defvar elixir-ts--capture-anonymous-function-end
(when (treesit-available-p)
(treesit-query-compile 'elixir '((anonymous_function "end" @end)))))
(defvar elixir-ts--capture-operator-parent
(when (treesit-available-p)
(treesit-query-compile 'elixir '((binary_operator operator: _ @val)))))
(defvar elixir-ts--syntax-table
(let ((table (make-syntax-table)))
(modify-syntax-entry ?| "." table)
(modify-syntax-entry ?- "." table)
(modify-syntax-entry ?+ "." table)
(modify-syntax-entry ?* "." table)
(modify-syntax-entry ?/ "." table)
(modify-syntax-entry ?< "." table)
(modify-syntax-entry ?> "." table)
(modify-syntax-entry ?_ "_" table)
(modify-syntax-entry ?? "w" table)
(modify-syntax-entry ?~ "w" table)
(modify-syntax-entry ?! "_" table)
(modify-syntax-entry ?' "\"" table)
(modify-syntax-entry ?\" "\"" table)
(modify-syntax-entry ?# "<" table)
(modify-syntax-entry ?\n ">" table)
(modify-syntax-entry ?\( "()" table)
(modify-syntax-entry ?\) ")(" table)
(modify-syntax-entry ?\{ "(}" table)
(modify-syntax-entry ?\} "){" table)
(modify-syntax-entry ?\[ "(]" table)
(modify-syntax-entry ?\] ")[" table)
(modify-syntax-entry ?: "'" table)
(modify-syntax-entry ?@ "'" table)
table)
"Syntax table for `elixir-ts-mode'.")
(defun elixir-ts--argument-indent-offset (node _parent &rest _)
"Return the argument offset position for NODE."
(if (treesit-node-prev-sibling node t) 0 elixir-ts-indent-offset))
(defun elixir-ts--argument-indent-anchor (node parent &rest _)
"Return the argument anchor position for NODE and PARENT."
(let ((first-sibling (treesit-node-child parent 0 t)))
(if (and first-sibling (not (treesit-node-eq first-sibling node)))
(treesit-node-start first-sibling)
(elixir-ts--parent-expression-start node parent))))
(defun elixir-ts--parent-expression-start (_node parent &rest _)
"Return the indentation expression start for NODE and PARENT."
;; If the parent is the first expression on the line return the
;; parent start of node position, otherwise use the parent call
;; start if available.
(if (eq (treesit-node-start parent)
(save-excursion
(goto-char (treesit-node-start parent))
(back-to-indentation)
(point)))
(treesit-node-start parent)
(let ((expr-parent
(treesit-parent-until
parent
(lambda (n)
(member (treesit-node-type n)
'("call" "binary_operator" "keywords" "list"))))))
(save-excursion
(goto-char (treesit-node-start expr-parent))
(back-to-indentation)
(if (looking-at "|>")
(point)
(treesit-node-start expr-parent))))))
(defvar elixir-ts--indent-rules
(let ((offset elixir-ts-indent-offset))
`((elixir
((parent-is "^source$") column-0 0)
((parent-is "^string$") parent-bol 0)
((parent-is "^quoted_content$")
(lambda (_n parent bol &rest _)
(save-excursion
(back-to-indentation)
(if (bolp)
(progn
(goto-char (treesit-node-start parent))
(back-to-indentation)
(point))
(point))))
0)
((node-is "^|>$") parent-bol 0)
((node-is "^|$") parent-bol 0)
((node-is "^]$") ,'elixir-ts--parent-expression-start 0)
((node-is "^}$") ,'elixir-ts--parent-expression-start 0)
((node-is "^)$") ,'elixir-ts--parent-expression-start 0)
((node-is "^else_block$") grand-parent 0)
((node-is "^catch_block$") grand-parent 0)
((node-is "^rescue_block$") grand-parent 0)
((node-is "^after_block$") grand-parent 0)
((parent-is "^else_block$") parent ,offset)
((parent-is "^catch_block$") parent ,offset)
((parent-is "^rescue_block$") parent ,offset)
((parent-is "^rescue_block$") parent ,offset)
((parent-is "^after_block$") parent ,offset)
((parent-is "^access_call$")
,'elixir-ts--argument-indent-anchor
,'elixir-ts--argument-indent-offset)
((parent-is "^tuple$")
,'elixir-ts--argument-indent-anchor
,'elixir-ts--argument-indent-offset)
((parent-is "^list$")
,'elixir-ts--argument-indent-anchor
,'elixir-ts--argument-indent-offset)
((parent-is "^pair$") parent ,offset)
((parent-is "^map_content$") parent-bol 0)
((parent-is "^map$") ,'elixir-ts--parent-expression-start ,offset)
((node-is "^stab_clause$") parent-bol ,offset)
((query ,elixir-ts--capture-operator-parent) grand-parent 0)
((node-is "^when$") parent 0)
((node-is "^keywords$") parent-bol ,offset)
((parent-is "^body$")
(lambda (node parent _)
(save-excursion
;; The grammar adds a comment outside of the body, so we have to indent
;; to the grand-parent if it is available.
(goto-char (treesit-node-start
(or (treesit-node-parent parent) (parent))))
(back-to-indentation)
(point)))
,offset)
((parent-is "^arguments$")
,'elixir-ts--argument-indent-anchor
,'elixir-ts--argument-indent-offset)
;; Handle incomplete maps when parent is ERROR.
((n-p-gp "^binary_operator$" "ERROR" nil) parent-bol 0)
;; When there is an ERROR, just indent to prev-line.
((parent-is "ERROR") prev-line 0)
((node-is "^binary_operator$")
(lambda (node parent &rest _)
(let ((top-level
(treesit-parent-while
node
(lambda (node)
(equal (treesit-node-type node)
"binary_operator")))))
(if (treesit-node-eq top-level node)
(elixir-ts--parent-expression-start node parent)
(treesit-node-start top-level))))
(lambda (node parent _)
(cond
((equal (treesit-node-type parent) "do_block")
,offset)
((equal (treesit-node-type parent) "binary_operator")
,offset)
(t 0))))
((parent-is "^binary_operator$")
(lambda (node parent bol &rest _)
(treesit-node-start
(treesit-parent-while
parent
(lambda (node)
(equal (treesit-node-type node) "binary_operator")))))
,offset)
((node-is "^pair$") first-sibling 0)
((query ,elixir-ts--capture-anonymous-function-end) parent-bol 0)
((node-is "^end$") standalone-parent 0)
((parent-is "^do_block$") grand-parent ,offset)
((parent-is "^anonymous_function$")
elixir-ts--treesit-anchor-grand-parent-bol ,offset)
((parent-is "^else_block$") parent ,offset)
((parent-is "^rescue_block$") parent ,offset)
((parent-is "^catch_block$") parent ,offset)
((parent-is "^keywords$") parent-bol 0)
((node-is "^call$") parent-bol ,offset)
((node-is "^comment$") parent-bol ,offset)))))
(defvar elixir-ts--font-lock-settings
(treesit-font-lock-rules
:language 'elixir
:feature 'elixir-comment
'((comment) @font-lock-comment-face)
:language 'elixir
:feature 'elixir-string
:override t
'([(string) (charlist)] @font-lock-string-face)
:language 'elixir
:feature 'elixir-string-interpolation
:override t
'((string
[
quoted_end: _ @font-lock-string-face
quoted_start: _ @font-lock-string-face
(quoted_content) @font-lock-string-face
(interpolation
"#{" @font-lock-regexp-grouping-backslash "}"
@font-lock-regexp-grouping-backslash)
])
(charlist
[
quoted_end: _ @font-lock-string-face
quoted_start: _ @font-lock-string-face
(quoted_content) @font-lock-string-face
(interpolation
"#{" @font-lock-regexp-grouping-backslash "}"
@font-lock-regexp-grouping-backslash)
]))
:language 'elixir
:feature 'elixir-keyword
`(,elixir-ts--reserved-keywords-vector
@font-lock-keyword-face
(binary_operator
operator: _ @font-lock-keyword-face
(:match ,elixir-ts--reserved-keywords-re @font-lock-keyword-face)))
:language 'elixir
:feature 'elixir-doc
:override t
`((unary_operator
operator: "@" @elixir-ts-font-comment-doc-attribute-face
operand: (call
target: (identifier) @elixir-ts-font-comment-doc-identifier-face
;; Arguments can be optional, so adding another
;; entry without arguments.
;; If we don't handle then we don't apply font
;; and the non doc fortification query will take specify
;; a more specific font which takes precedence.
(arguments
[
(string) @font-lock-doc-face
(charlist) @font-lock-doc-face
(sigil) @font-lock-doc-face
(boolean) @font-lock-doc-face
]))
(:match ,elixir-ts--doc-keywords-re
@elixir-ts-font-comment-doc-identifier-face))
(unary_operator
operator: "@" @elixir-ts-font-comment-doc-attribute-face
operand: (call
target: (identifier) @elixir-ts-font-comment-doc-identifier-face)
(:match ,elixir-ts--doc-keywords-re
@elixir-ts-font-comment-doc-identifier-face)))
:language 'elixir
:feature 'elixir-unary-operator
`((unary_operator operator: "@" @font-lock-preprocessor-face
operand: [
(identifier) @font-lock-preprocessor-face
(call target: (identifier)
@font-lock-preprocessor-face)
(boolean) @font-lock-preprocessor-face
(nil) @font-lock-preprocessor-face
])
(unary_operator operator: "&") @font-lock-function-name-face
(operator_identifier) @font-lock-operator-face)
:language 'elixir
:feature 'elixir-operator
'((binary_operator operator: _ @font-lock-operator-face)
(dot operator: _ @font-lock-operator-face)
(stab_clause operator: _ @font-lock-operator-face)
[(boolean) (nil)] @font-lock-constant-face
[(integer) (float)] @font-lock-number-face
(alias) @font-lock-type-face
(call target: (dot left: (atom) @font-lock-type-face))
(char) @font-lock-constant-face
[(atom) (quoted_atom)] @font-lock-type-face
[(keyword) (quoted_keyword)] @font-lock-builtin-face)
:language 'elixir
:feature 'elixir-call
`((call
target: (identifier) @font-lock-keyword-face
(:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face))
(call
target: (identifier) @font-lock-keyword-face
(:match ,elixir-ts--kernel-keywords-re @font-lock-keyword-face))
(call
target: [(identifier) @font-lock-function-name-face
(dot right: (identifier) @font-lock-keyword-face)])
(call
target: (identifier) @font-lock-keyword-face
(arguments
[
(identifier) @font-lock-keyword-face
(binary_operator
left: (identifier) @font-lock-keyword-face
operator: "when")
])
(:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face))
(call
target: (identifier) @font-lock-keyword-face
(arguments
(binary_operator
operator: "|>"
right: (identifier)))
(:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face)))
:language 'elixir
:feature 'elixir-constant
`((binary_operator operator: "|>" right: (identifier)
@font-lock-function-name-face)
((identifier) @font-lock-keyword-face
(:match ,elixir-ts--builtin-keywords-re
@font-lock-keyword-face))
((identifier) @font-lock-comment-face
(:match "^_" @font-lock-comment-face))
(identifier) @font-lock-function-name-face
["%"] @font-lock-keyward-face
["," ";"] @font-lock-keyword-face
["(" ")" "[" "]" "{" "}" "<<" ">>"] @font-lock-keyword-face)
:language 'elixir
:feature 'elixir-sigil
:override t
`((sigil
(sigil_name) @elixir-ts-font-sigil-name-face
quoted_start: _ @font-lock-string-face
quoted_end: _ @font-lock-string-face
(:match "^[sSwWpP]$" @elixir-ts-font-sigil-name-face))
@font-lock-string-face
(sigil
(sigil_name) @elixir-ts-font-sigil-name-face
quoted_start: _ @font-lock-regex-face
quoted_end: _ @font-lock-regex-face
(:match "^[rR]$" @elixir-ts-font-sigil-name-face))
@font-lock-regex-face
(sigil
"~" @font-lock-string-face
(sigil_name) @elixir-ts-font-sigil-name-face
quoted_start: _ @font-lock-string-face
quoted_end: _ @font-lock-string-face
(:match "^[HF]$" @elixir-ts-font-sigil-name-face)))
:language 'elixir
:feature 'elixir-string-escape
:override t
`((escape_sequence) @font-lock-regexp-grouping-backslash))
"Tree-sitter font-lock settings.")
(defvar elixir-ts--treesit-range-rules
(when (treesit-available-p)
(treesit-range-rules
:embed 'heex
:host 'elixir
'((sigil (sigil_name) @name (:match "^[HF]$" @name) (quoted_content) @heex)))))
(defun elixir-ts--forward-sexp (&optional arg)
"Move forward across one balanced expression (sexp).
With ARG, do it many times. Negative ARG means move backward."
(or arg (setq arg 1))
(funcall
(if (> arg 0) #'treesit-end-of-thing #'treesit-beginning-of-thing)
(if (eq (treesit-language-at (point)) 'heex)
heex-ts--sexp-regexp
elixir-ts--sexp-regexp)
(abs arg)))
(defun elixir-ts--treesit-anchor-grand-parent-bol (_n parent &rest _)
"Return the beginning of non-space characters for the parent node of PARENT."
(save-excursion
(goto-char (treesit-node-start (treesit-node-parent parent)))
(back-to-indentation)
(point)))
(defun elixir-ts--treesit-language-at-point (point)
"Return the language at POINT."
(let* ((range nil)
(language-in-range
(cl-loop
for parser in (treesit-parser-list)
do (setq range
(cl-loop
for range in (treesit-parser-included-ranges parser)
if (and (>= point (car range)) (<= point (cdr range)))
return parser))
if range
return (treesit-parser-language parser))))
(if (null language-in-range)
(when-let ((parser (car (treesit-parser-list))))
(treesit-parser-language parser))
language-in-range)))
(defun elixir-ts--defun-p (node)
"Return non-nil when NODE is a defun."
(member (treesit-node-text
(treesit-node-child-by-field-name node "target"))
(append
elixir-ts--definition-keywords
elixir-ts--test-definition-keywords)))
(defun elixir-ts--defun-name (node)
"Return the name of the defun NODE.
Return nil if NODE is not a defun node or doesn't have a name."
(pcase (treesit-node-type node)
("call" (let ((node-child
(treesit-node-child (treesit-node-child node 1) 0)))
(pcase (treesit-node-type node-child)
("alias" (treesit-node-text node-child t))
("call" (treesit-node-text
(treesit-node-child-by-field-name node-child "target") t))
("binary_operator"
(treesit-node-text
(treesit-node-child-by-field-name
(treesit-node-child-by-field-name node-child "left") "target")
t))
("identifier"
(treesit-node-text node-child t))
(_ nil))))
(_ nil)))
;;;###autoload
(define-derived-mode elixir-ts-mode prog-mode "Elixir"
"Major mode for editing Elixir, powered by tree-sitter."
:group 'elixir-ts
:syntax-table elixir-ts--syntax-table
;; Comments
(setq-local comment-start "# ")
(setq-local comment-start-skip
(rx "#" (* (syntax whitespace))))
(setq-local comment-end "")
(setq-local comment-end-skip
(rx (* (syntax whitespace))
(group (or (syntax comment-end) "\n"))))
;; Compile
(setq-local compile-command "mix")
(when (treesit-ready-p 'elixir)
;; The HEEx parser has to be created first for elixir to ensure elixir
;; is the first language when looking for treesit ranges.
(if (treesit-ready-p 'heex)
(treesit-parser-create 'heex))
(treesit-parser-create 'elixir)
(setq-local treesit-language-at-point-function
'elixir-ts--treesit-language-at-point)
;; Font-lock.
(setq-local treesit-font-lock-settings elixir-ts--font-lock-settings)
(setq-local treesit-font-lock-feature-list
'(( elixir-comment elixir-constant elixir-doc )
( elixir-string elixir-keyword elixir-unary-operator
elixir-call elixir-operator )
( elixir-sigil elixir-string-escape elixir-string-interpolation)))
;; Imenu.
(setq-local treesit-simple-imenu-settings
'((nil "\\`call\\'" elixir-ts--defun-p nil)))
;; Indent.
(setq-local treesit-simple-indent-rules elixir-ts--indent-rules)
;; Navigation
(setq-local forward-sexp-function #'elixir-ts--forward-sexp)
(setq-local treesit-defun-type-regexp
'("call" . elixir-ts--defun-p))
(setq-local treesit-defun-name-function #'elixir-ts--defun-name)
;; Embedded Heex
(when (treesit-ready-p 'heex)
(setq-local treesit-range-settings elixir-ts--treesit-range-rules)
(setq-local treesit-simple-indent-rules
(append treesit-simple-indent-rules heex-ts--indent-rules))
(setq-local treesit-font-lock-settings
(append treesit-font-lock-settings
heex-ts--font-lock-settings))
(setq-local treesit-simple-indent-rules
(append treesit-simple-indent-rules
heex-ts--indent-rules))
(setq-local treesit-font-lock-feature-list
'(( elixir-comment elixir-constant elixir-doc
heex-comment heex-keyword heex-doctype )
( elixir-string elixir-keyword elixir-unary-operator
elixir-call elixir-operator
heex-component heex-tag heex-attribute heex-string)
( elixir-sigil elixir-string-escape
elixir-string-interpolation ))))
(treesit-major-mode-setup)))
(if (treesit-ready-p 'elixir)
(progn
(add-to-list 'auto-mode-alist '("\\.elixir\\'" . elixir-ts-mode))
(add-to-list 'auto-mode-alist '("\\.ex\\'" . elixir-ts-mode))
(add-to-list 'auto-mode-alist '("\\.exs\\'" . elixir-ts-mode))
(add-to-list 'auto-mode-alist '("mix\\.lock" . elixir-ts-mode))))
(provide 'elixir-ts-mode)
;;; elixir-ts-mode.el ends here

View file

@ -0,0 +1,185 @@
;;; heex-ts-mode.el --- Major mode for Heex with tree-sitter support -*- lexical-binding: t; -*-
;; Copyright (C) 2022-2023 Free Software Foundation, Inc.
;; Author: Wilhelm H Kirschbaum <wkirschbaum@gmail.com>
;; Created: November 2022
;; Keywords: elixir languages tree-sitter
;; This file is part of GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;;
;; This package provides `heex-ts-mode' which is a major mode for editing
;; HEEx files that uses Tree Sitter to parse the language.
;;
;; This package is compatible with and was tested against the tree-sitter grammar
;; for HEEx found at https://github.com/phoenixframework/tree-sitter-heex.
;;; Code:
(require 'treesit)
(eval-when-compile (require 'rx))
(declare-function treesit-parser-create "treesit.c")
(declare-function treesit-node-child "treesit.c")
(declare-function treesit-node-type "treesit.c")
(declare-function treesit-node-start "treesit.c")
(defgroup heex-ts nil
"Major mode for editing HEEx code."
:prefix "heex-ts-"
:group 'langauges)
(defcustom heex-ts-indent-offset 2
"Indentation of HEEx statements."
:version "30.1"
:type 'integer
:safe 'integerp
:group 'heex-ts)
(defconst heex-ts--sexp-regexp
(rx bol
(or "directive" "tag" "component" "slot"
"attribute" "attribute_value" "quoted_attribute_value")
eol))
;; There seems to be no parent directive block for tree-sitter-heex,
;; so we ignore them for now until we learn how to query them.
;; https://github.com/phoenixframework/tree-sitter-heex/issues/28
(defvar heex-ts--indent-rules
(let ((offset heex-ts-indent-offset))
`((heex
((parent-is "fragment")
(lambda (node parent &rest _)
;; If HEEx is embedded indent to parent
;; otherwise indent to the bol.
(if (eq (treesit-language-at (point-min)) 'heex)
(point-min)
(save-excursion
(goto-char (treesit-node-start parent))
(back-to-indentation)
(point))
)) 0)
((node-is "end_tag") parent-bol 0)
((node-is "end_component") parent-bol 0)
((node-is "end_slot") parent-bol 0)
((node-is "/>") parent-bol 0)
((node-is ">") parent-bol 0)
((parent-is "comment") prev-adaptive-prefix 0)
((parent-is "component") parent-bol ,offset)
((parent-is "tag") parent-bol ,offset)
((parent-is "start_tag") parent-bol ,offset)
((parent-is "component") parent-bol ,offset)
((parent-is "start_component") parent-bol ,offset)
((parent-is "slot") parent-bol ,offset)
((parent-is "start_slot") parent-bol ,offset)
((parent-is "self_closing_tag") parent-bol ,offset)
(no-node parent-bol ,offset)))))
(defvar heex-ts--font-lock-settings
(when (treesit-available-p)
(treesit-font-lock-rules
:language 'heex
:feature 'heex-comment
'((comment) @font-lock-comment-face)
:language 'heex
:feature 'heex-doctype
'((doctype) @font-lock-doc-face)
:language 'heex
:feature 'heex-tag
`([(tag_name) (slot_name)] @font-lock-function-name-face)
:language 'heex
:feature 'heex-attribute
`((attribute_name) @font-lock-variable-name-face)
:language 'heex
:feature 'heex-keyword
`((special_attribute_name) @font-lock-keyword-face)
:language 'heex
:feature 'heex-string
`([(attribute_value) (quoted_attribute_value)] @font-lock-constant-face)
:language 'heex
:feature 'heex-component
`([
(component_name) @font-lock-function-name-face
(module) @font-lock-keyword-face
(function) @font-lock-keyword-face
"." @font-lock-keyword-face
])))
"Tree-sitter font-lock settings.")
(defun heex-ts--defun-name (node)
"Return the name of the defun NODE.
Return nil if NODE is not a defun node or doesn't have a name."
(pcase (treesit-node-type node)
((or "component" "slot" "tag")
(string-trim
(treesit-node-text
(treesit-node-child (treesit-node-child node 0) 1) nil)))
(_ nil)))
(defun heex-ts--forward-sexp (&optional arg)
"Move forward across one balanced expression (sexp).
With ARG, do it many times. Negative ARG means move backward."
(or arg (setq arg 1))
(funcall
(if (> arg 0) #'treesit-end-of-thing #'treesit-beginning-of-thing)
heex-ts--sexp-regexp
(abs arg)))
;;;###autoload
(define-derived-mode heex-ts-mode html-mode "HEEx"
"Major mode for editing HEEx, powered by tree-sitter."
:group 'heex-ts
(when (treesit-ready-p 'heex)
(treesit-parser-create 'heex)
;; Comments
(setq-local treesit-text-type-regexp
(regexp-opt '("comment" "text")))
(setq-local forward-sexp-function #'heex-ts--forward-sexp)
;; Navigation.
(setq-local treesit-defun-type-regexp
(rx bol (or "component" "tag" "slot") eol))
(setq-local treesit-defun-name-function #'heex-ts--defun-name)
;; Imenu
(setq-local treesit-simple-imenu-settings
'(("Component" "\\`component\\'" nil nil)
("Slot" "\\`slot\\'" nil nil)
("Tag" "\\`tag\\'" nil nil)))
(setq-local treesit-font-lock-settings heex-ts--font-lock-settings)
(setq-local treesit-simple-indent-rules heex-ts--indent-rules)
(setq-local treesit-font-lock-feature-list
'(( heex-comment heex-keyword heex-doctype )
( heex-component heex-tag heex-attribute heex-string )
() ()))
(treesit-major-mode-setup)))
(if (treesit-ready-p 'heex)
;; Both .heex and the deprecated .leex files should work
;; with the tree-sitter-heex grammar.
(add-to-list 'auto-mode-alist '("\\.[hl]?eex\\'" . heex-ts-mode)))
(provide 'heex-ts-mode)
;;; heex-ts-mode.el ends here

View file

@ -846,61 +846,6 @@ argument VECP, this copies vectors as well as conses."
tree)
tree)))
(defvar safe-copy-tree--seen nil
"A hash table for conses/vectors/records already seen by safe-copy-tree-1.
Its key is a cons or vector/record seen by the algorithm, and its
value is the corresponding cons/vector/record in the copy.")
(defun safe-copy-tree--1 (tree &optional vecp)
"Make a copy of TREE, taking circular structure into account.
If TREE is a cons cell, this recursively copies both its car and its cdr.
Contrast to `copy-sequence', which copies only along the cdrs. With second
argument VECP, this copies vectors and records as well as conses."
(cond
((gethash tree safe-copy-tree--seen))
((consp tree)
(let* ((result (cons (car tree) (cdr tree)))
(newcons result)
hash)
(while (and (not hash) (consp tree))
(if (setq hash (gethash tree safe-copy-tree--seen))
(setq newcons hash)
(puthash tree newcons safe-copy-tree--seen))
(setq tree newcons)
(unless hash
(if (or (consp (car tree))
(and vecp (or (vectorp (car tree)) (recordp (car tree)))))
(let ((newcar (safe-copy-tree--1 (car tree) vecp)))
(setcar tree newcar)))
(setq newcons (if (consp (cdr tree))
(cons (cadr tree) (cddr tree))
(cdr tree)))
(setcdr tree newcons)
(setq tree (cdr tree))))
(nconc result
(if (and vecp (or (vectorp tree) (recordp tree)))
(safe-copy-tree--1 tree vecp) tree))))
((and vecp (or (vectorp tree) (recordp tree)))
(let* ((newvec (copy-sequence tree))
(i (length newvec)))
(puthash tree newvec safe-copy-tree--seen)
(setq tree newvec)
(while (>= (setq i (1- i)) 0)
(aset tree i (safe-copy-tree--1 (aref tree i) vecp)))
tree))
(t tree)))
(defun safe-copy-tree (tree &optional vecp)
"Make a copy of TREE, taking circular structure into account.
If TREE is a cons cell, this recursively copies both its car and its cdr.
Contrast to `copy-sequence', which copies only along the cdrs. With second
argument VECP, this copies vectors and records as well as conses."
(setq safe-copy-tree--seen (make-hash-table :test #'eq))
(unwind-protect
(safe-copy-tree--1 tree vecp)
(clrhash safe-copy-tree--seen)
(setq safe-copy-tree--seen nil)))
;;;; Various list-search functions.

View file

@ -1850,6 +1850,34 @@ EXPECTED-POINT BINDINGS (MODES \\='\\='(ruby-mode js-mode python-mode)) \
(should (eq (byte-compile-file src-file) 'no-byte-compile))
(should-not (file-exists-p dest-file))))
(ert-deftest bytecomp--copy-tree ()
(should (null (bytecomp--copy-tree nil)))
(let ((print-circle t))
(let* ((x '(1 2 (3 4)))
(y (bytecomp--copy-tree x)))
(should (equal (prin1-to-string (list x y))
"((1 2 (3 4)) (1 2 (3 4)))")))
(let* ((x '#1=(a #1#))
(y (bytecomp--copy-tree x)))
(should (equal (prin1-to-string (list x y))
"(#1=(a #1#) #2=(a #2#))")))
(let* ((x '#1=(#1# a))
(y (bytecomp--copy-tree x)))
(should (equal (prin1-to-string (list x y))
"(#1=(#1# a) #2=(#2# a))")))
(let* ((x '((a . #1=(b)) #1#))
(y (bytecomp--copy-tree x)))
(should (equal (prin1-to-string (list x y))
"(((a . #1=(b)) #1#) ((a . #2=(b)) #2#))")))
(let* ((x '#1=(a #2=(#1# b . #3=(#2# c . #1#)) (#3# d)))
(y (bytecomp--copy-tree x)))
(should (equal (prin1-to-string (list x y))
(concat
"("
"#1=(a #2=(#1# b . #3=(#2# c . #1#)) (#3# d))"
" "
"#4=(a #5=(#4# b . #6=(#5# c . #4#)) (#6# d))"
")"))))))
;; Local Variables:
;; no-byte-compile: t

View file

@ -75,6 +75,21 @@
(should (equal '((regexp . "(string-match-p \"^[fo]+\" \"foobar\")\n => 0"))
(shortdoc-function-examples 'string-match-p))))
(ert-deftest shortdoc-help-fns-examples-function-test ()
"Test that `shortdoc-help-fns-examples-function' correctly prints ELisp function examples."
(with-temp-buffer
(shortdoc-help-fns-examples-function 'string-fill)
(should (equal "\n Examples:\n\n (string-fill \"Three short words\" 12)\n => \"Three short\\nwords\"\n (string-fill \"Long-word\" 3)\n => \"Long-word\"\n\n"
(buffer-substring-no-properties (point-min) (point-max))))
(erase-buffer)
(shortdoc-help-fns-examples-function 'assq)
(should (equal "\n Examples:\n\n (assq 'foo '((foo . bar) (zot . baz)))\n => (foo . bar)\n\n (assq 'b '((a . 1) (b . 2)))\n => (b . 2)\n\n"
(buffer-substring-no-properties (point-min) (point-max))))
(erase-buffer)
(shortdoc-help-fns-examples-function 'string-trim)
(should (equal "\n Example:\n\n (string-trim \" foo \")\n => \"foo\"\n\n"
(buffer-substring-no-properties (point-min) (point-max))))))
(provide 'shortdoc-tests)
;;; shortdoc-tests.el ends here

View file

@ -0,0 +1,308 @@
Code:
(lambda ()
(setq indent-tabs-mode nil)
(elixir-ts-mode)
(indent-region (point-min) (point-max)))
Point-Char: $
Name: Basic modules
=-=
defmodule Foobar do
def bar() do
"one"
end
end
=-=
defmodule Foobar do
def bar() do
"one"
end
end
=-=-=
Name: Map
=-=
map = %{
"a" => 1,
"b" => 2
}
=-=-=
Name: Map in function def
=-=
def foobar() do
%{
one: "one",
two: "two",
three: "three",
four: "four"
}
end
=-=-=
Name: Map in tuple
=-=
def foo() do
{:ok,
%{
state
| extra_arguments: extra_arguments,
max_children: max_children,
max_restarts: max_restarts,
max_seconds: max_seconds,
strategy: strategy
}}
end
=-=-=
Name: Nested maps
=-=
%{
foo: "bar",
bar: %{
foo: "bar"
}
}
def foo() do
%{
foo: "bar",
bar: %{
foo: "bar"
}
}
end
=-=-=
Name: Block assignments
=-=
foo =
if true do
"yes"
else
"no"
end
=-=-=
Name: Function rescue
=-=
def foo do
"bar"
rescue
e ->
"bar"
end
=-=-=
Name: With statement
=-=
with one <- one(),
two <- two(),
{:ok, value} <- get_value(one, two) do
{:ok, value}
else
{:error, %{"Message" => message}} ->
{:error, message}
end
=-=-=
Name: Pipe statements with fn
=-=
[1, 2]
|> Enum.map(fn num ->
num + 1
end)
=-=-=
Name: Pipe statements stab clases
=-=
[1, 2]
|> Enum.map(fn
x when x < 10 -> x * 2
x -> x * 3
end)
=-=-=
Name: Pipe statements params
=-=
[1, 2]
|> foobar(
:one,
:two,
:three,
:four
)
=-=-=
Name: Parameter maps
=-=
def something(%{
one: :one,
two: :two
}) do
{:ok, "done"}
end
=-=-=
Name: Binary operator in else block
=-=
defp foobar() do
if false do
:foo
else
:bar |> foo
end
end
=-=-=
Name: Tuple indentation
=-=
tuple = {
:one,
:two
}
{
:one,
:two
}
=-=-=
Name: Spec and method
=-=
@spec foobar(
t,
acc,
(one, something -> :bar | far),
(two -> :bar | far)
) :: any()
when chunk: any
def foobar(enumerable, acc, chunk_fun, after_fun) do
{_, {res, acc}} =
case after_fun.(acc) do
{:one, "one"} ->
"one"
{:two, "two"} ->
"two"
end
end
=-=-=
Name: Spec with multi-line result
=-=
@type result ::
{:done, term}
| {:two}
| {:one}
@type result ::
{
:done,
term
}
| {:two}
| {:one}
@type boo_bar ::
(foo :: pos_integer, bar :: pos_integer -> any())
@spec foo_bar(
t,
(foo -> any),
(() -> any) | (foo, foo -> boolean) | module()
) :: any
when foo: any
def foo(one, fun, other)
=-=-=
Name: String concatenation in call
=-=
IO.warn(
"one" <>
"two" <>
"bar"
)
IO.warn(
"foo" <>
"bar"
)
=-=-=
Name: Incomplete tuple
=-=
map = {
:foo
=-=
map = {
:foo
=-=-=
Name: Incomplete map
=-=
map = %{
"a" => "a",
=-=-=
Name: Incomplete list
=-=
map = [
:foo
=-=
map = [
:foo
=-=-=
Name: String concatenation
=-=
"one" <>
"two" <>
"three" <>
"four"
=-=-=
Name: Tuple with same line first node
=-=
{:one,
:two}
{:ok,
fn one ->
one
|> String.upcase(one)
end}
=-=-=
Name: Long tuple
=-=
{"January", "February", "March", "April", "May", "June", "July", "August", "September",
"October", "November", "December"}
=-=-=

View file

@ -0,0 +1,31 @@
;;; c-ts-mode-tests.el --- Tests for Tree-sitter-based C mode -*- lexical-binding: t; -*-
;; Copyright (C) 2023 Free Software Foundation, Inc.
;; This file is part of GNU Emacs.
;; GNU Emacs is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; GNU Emacs is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
;;; Code:
(require 'ert)
(require 'ert-x)
(require 'treesit)
(ert-deftest elixir-ts-mode-test-indentation ()
(skip-unless (and (treesit-ready-p 'elixir) (treesit-ready-p 'heex)))
(ert-test-erts-file (ert-resource-file "indent.erts")))
(provide 'elixir-ts-mode-tests)
;;; elixir-ts-mode-tests.el ends here

View file

@ -0,0 +1,47 @@
Code:
(lambda ()
(setq indent-tabs-mode nil)
(heex-ts-mode)
(indent-region (point-min) (point-max)))
Point-Char: $
Name: Tag
=-=
<div>
div
</div>
=-=
<div>
div
</div>
=-=-=
Name: Component
=-=
<Foo>
foobar
</Foo>
=-=
<Foo>
foobar
</Foo>
=-=-=
Name: Slots
=-=
<Foo>
<:bar>
foobar
</:bar>
</Foo>
=-=
<Foo>
<:bar>
foobar
</:bar>
</Foo>
=-=-=

View file

@ -0,0 +1,9 @@
(require 'ert)
(require 'ert-x)
(require 'treesit)
(ert-deftest heex-ts-mode-test-indentation ()
(skip-unless (treesit-ready-p 'heex))
(ert-test-erts-file (ert-resource-file "indent.erts")))
(provide 'heex-ts-mode-tests)

View file

@ -1205,31 +1205,5 @@ final or penultimate step during initialization."))
(should (equal a-dedup '("a" "b" "a" "b" "c")))
(should (eq a a-dedup))))
(ert-deftest subr--safe-copy-tree ()
(should (null (safe-copy-tree nil)))
(let* ((foo '(1 2 (3 4))) (bar (safe-copy-tree foo)))
(should (equal bar foo))
(should-not (eq bar foo))
(should-not (eq (caddr bar) (caddr foo))))
(let* ((foo '#1=(a #1#)) (bar (safe-copy-tree foo)))
(should (eq (car bar) (car foo)))
; (should-not (proper-list-p bar))
(should (eq (caadr bar) (caadr foo)))
(should (eq (caadr bar) 'a)))
(let* ((foo [1 2 3 4]) (bar (safe-copy-tree foo)))
(should (eq bar foo)))
(let* ((foo [1 (2 3) 4]) (bar (safe-copy-tree foo t)))
(should-not (eq bar foo))
(should (equal bar foo))
(should-not (eq (aref bar 1) (aref foo 1))))
(let* ((foo [1 [2 3] 4]) (bar (safe-copy-tree foo t)))
(should (equal bar foo))
(should-not (eq bar foo))
(should-not (eq (aref bar 1) (aref foo 1))))
(let* ((foo (record 'foo 1 "two" 3)) (bar (safe-copy-tree foo t)))
(should (equal bar foo))
(should-not (eq bar foo))
(should (eq (aref bar 2) (aref foo 2)))))
(provide 'subr-tests)
;;; subr-tests.el ends here