Merge remote-tracking branch 'origin/master' into feature/android
This commit is contained in:
commit
a517c24697
25 changed files with 1481 additions and 232 deletions
|
@ -8,8 +8,10 @@ languages=(
|
|||
'css'
|
||||
'c-sharp'
|
||||
'dockerfile'
|
||||
'elixir'
|
||||
'go'
|
||||
'go-mod'
|
||||
'heex'
|
||||
'html'
|
||||
'javascript'
|
||||
'json'
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
14
etc/NEWS
14
etc/NEWS
|
@ -260,6 +260,15 @@ following to you init file:
|
|||
An optional major mode based on the tree-sitter library for editing
|
||||
HTML files.
|
||||
|
||||
---
|
||||
*** New major mode heex-ts-mode'.
|
||||
A major mode based on the tree-sitter library for editing HEEx files.
|
||||
|
||||
---
|
||||
*** New major mode elixir-ts-mode'.
|
||||
A major mode based on the tree-sitter library for editing Elixir
|
||||
files.
|
||||
|
||||
---
|
||||
** The highly accessible Modus themes collection has six items.
|
||||
The 'modus-operandi' and 'modus-vivendi' are the main themes that have
|
||||
|
@ -413,11 +422,6 @@ was to catch all errors, add an explicit handler for 'error', or use
|
|||
This warning can be suppressed using 'with-suppressed-warnings' with
|
||||
the warning name 'suspicious'.
|
||||
|
||||
+++
|
||||
** New function 'safe-copy-tree'
|
||||
This function is a version of copy-tree which handles circular lists
|
||||
and circular vectors/records.
|
||||
|
||||
+++
|
||||
** New function 'file-user-uid'.
|
||||
This function is like 'user-uid', but is aware of file name handlers,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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."
|
||||
|
|
|
@ -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")))
|
||||
|
|
634
lisp/progmodes/elixir-ts-mode.el
Normal file
634
lisp/progmodes/elixir-ts-mode.el
Normal 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
|
185
lisp/progmodes/heex-ts-mode.el
Normal file
185
lisp/progmodes/heex-ts-mode.el
Normal 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
|
55
lisp/subr.el
55
lisp/subr.el
|
@ -846,61 +846,6 @@ argument VECP, this copies vectors as well as conses."
|
|||
tree)
|
||||
tree)))
|
||||
|
||||
(defvar safe-copy-tree--seen nil
|
||||
"A hash table for conses/vectors/records already seen by safe-copy-tree-1.
|
||||
Its key is a cons or vector/record seen by the algorithm, and its
|
||||
value is the corresponding cons/vector/record in the copy.")
|
||||
|
||||
(defun safe-copy-tree--1 (tree &optional vecp)
|
||||
"Make a copy of TREE, taking circular structure into account.
|
||||
If TREE is a cons cell, this recursively copies both its car and its cdr.
|
||||
Contrast to `copy-sequence', which copies only along the cdrs. With second
|
||||
argument VECP, this copies vectors and records as well as conses."
|
||||
(cond
|
||||
((gethash tree safe-copy-tree--seen))
|
||||
((consp tree)
|
||||
(let* ((result (cons (car tree) (cdr tree)))
|
||||
(newcons result)
|
||||
hash)
|
||||
(while (and (not hash) (consp tree))
|
||||
(if (setq hash (gethash tree safe-copy-tree--seen))
|
||||
(setq newcons hash)
|
||||
(puthash tree newcons safe-copy-tree--seen))
|
||||
(setq tree newcons)
|
||||
(unless hash
|
||||
(if (or (consp (car tree))
|
||||
(and vecp (or (vectorp (car tree)) (recordp (car tree)))))
|
||||
(let ((newcar (safe-copy-tree--1 (car tree) vecp)))
|
||||
(setcar tree newcar)))
|
||||
(setq newcons (if (consp (cdr tree))
|
||||
(cons (cadr tree) (cddr tree))
|
||||
(cdr tree)))
|
||||
(setcdr tree newcons)
|
||||
(setq tree (cdr tree))))
|
||||
(nconc result
|
||||
(if (and vecp (or (vectorp tree) (recordp tree)))
|
||||
(safe-copy-tree--1 tree vecp) tree))))
|
||||
((and vecp (or (vectorp tree) (recordp tree)))
|
||||
(let* ((newvec (copy-sequence tree))
|
||||
(i (length newvec)))
|
||||
(puthash tree newvec safe-copy-tree--seen)
|
||||
(setq tree newvec)
|
||||
(while (>= (setq i (1- i)) 0)
|
||||
(aset tree i (safe-copy-tree--1 (aref tree i) vecp)))
|
||||
tree))
|
||||
(t tree)))
|
||||
|
||||
(defun safe-copy-tree (tree &optional vecp)
|
||||
"Make a copy of TREE, taking circular structure into account.
|
||||
If TREE is a cons cell, this recursively copies both its car and its cdr.
|
||||
Contrast to `copy-sequence', which copies only along the cdrs. With second
|
||||
argument VECP, this copies vectors and records as well as conses."
|
||||
(setq safe-copy-tree--seen (make-hash-table :test #'eq))
|
||||
(unwind-protect
|
||||
(safe-copy-tree--1 tree vecp)
|
||||
(clrhash safe-copy-tree--seen)
|
||||
(setq safe-copy-tree--seen nil)))
|
||||
|
||||
|
||||
;;;; Various list-search functions.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
308
test/lisp/progmodes/elixir-ts-mode-resources/indent.erts
Normal file
308
test/lisp/progmodes/elixir-ts-mode-resources/indent.erts
Normal 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"}
|
||||
=-=-=
|
31
test/lisp/progmodes/elixir-ts-mode-tests.el
Normal file
31
test/lisp/progmodes/elixir-ts-mode-tests.el
Normal 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
|
47
test/lisp/progmodes/heex-ts-mode-resources/indent.erts
Normal file
47
test/lisp/progmodes/heex-ts-mode-resources/indent.erts
Normal 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>
|
||||
=-=-=
|
9
test/lisp/progmodes/heex-ts-mode-tests.el
Normal file
9
test/lisp/progmodes/heex-ts-mode-tests.el
Normal 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)
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue