diff options
| author | Wilhelm H Kirschbaum | 2023-03-12 17:10:43 +0200 |
|---|---|---|
| committer | Eli Zaretskii | 2023-03-12 17:41:44 +0200 |
| commit | d965d030879d9ca4ef5098cb4e2e7c56128b904b (patch) | |
| tree | 8ab985a3946eac5e52417f7bb32b012db3db0af5 | |
| parent | 802e64922bcee40c8362b9627aa33a0de0c068d7 (diff) | |
| download | emacs-d965d030879d9ca4ef5098cb4e2e7c56128b904b.tar.gz emacs-d965d030879d9ca4ef5098cb4e2e7c56128b904b.zip | |
Add elixir-ts-mode (Bug#61996)
* etc/NEWS: Mention the new mode.
* lisp/progmodes/elixir-ts-mode.el: New file.
* test/lisp/progmodes/elixir-ts-mode-tests.el: New file.
* test/lisp/progmodes/elixir-ts-mode-resources/indent.erts: New file.
* admin/notes/tree-sitter/build-module/batch.sh:
* admin/notes/tree-sitter/build-module/build.sh: Add Elixir support.
* lisp/progmodes/eglot.el (eglot-server-programs): Add elixir-ts-mode.
| -rwxr-xr-x | admin/notes/tree-sitter/build-module/batch.sh | 1 | ||||
| -rwxr-xr-x | admin/notes/tree-sitter/build-module/build.sh | 3 | ||||
| -rw-r--r-- | etc/NEWS | 4 | ||||
| -rw-r--r-- | lisp/progmodes/eglot.el | 2 | ||||
| -rw-r--r-- | lisp/progmodes/elixir-ts-mode.el | 634 | ||||
| -rw-r--r-- | test/lisp/progmodes/elixir-ts-mode-resources/indent.erts | 308 | ||||
| -rw-r--r-- | test/lisp/progmodes/elixir-ts-mode-tests.el | 31 |
7 files changed, 982 insertions, 1 deletions
diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh index 8b0072782e8..1d4076564dc 100755 --- a/admin/notes/tree-sitter/build-module/batch.sh +++ b/admin/notes/tree-sitter/build-module/batch.sh | |||
| @@ -8,6 +8,7 @@ languages=( | |||
| 8 | 'css' | 8 | 'css' |
| 9 | 'c-sharp' | 9 | 'c-sharp' |
| 10 | 'dockerfile' | 10 | 'dockerfile' |
| 11 | 'elixir' | ||
| 11 | 'go' | 12 | 'go' |
| 12 | 'go-mod' | 13 | 'go-mod' |
| 13 | 'heex' | 14 | 'heex' |
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh index 78ecfb5bc82..0832875168b 100755 --- a/admin/notes/tree-sitter/build-module/build.sh +++ b/admin/notes/tree-sitter/build-module/build.sh | |||
| @@ -31,6 +31,9 @@ case "${lang}" in | |||
| 31 | "cmake") | 31 | "cmake") |
| 32 | org="uyha" | 32 | org="uyha" |
| 33 | ;; | 33 | ;; |
| 34 | "elixir") | ||
| 35 | org="elixir-lang" | ||
| 36 | ;; | ||
| 34 | "go-mod") | 37 | "go-mod") |
| 35 | # The parser is called "gomod". | 38 | # The parser is called "gomod". |
| 36 | lang="gomod" | 39 | lang="gomod" |
| @@ -251,6 +251,10 @@ HTML files. | |||
| 251 | *** New major mode heex-ts-mode'. | 251 | *** New major mode heex-ts-mode'. |
| 252 | A major mode based on the tree-sitter library for editing HEEx files. | 252 | A major mode based on the tree-sitter library for editing HEEx files. |
| 253 | 253 | ||
| 254 | *** New major mode elixir-ts-mode'. | ||
| 255 | A major mode based on the tree-sitter library for editing Elixir | ||
| 256 | files. | ||
| 257 | |||
| 254 | --- | 258 | --- |
| 255 | ** The highly accessible Modus themes collection has six items. | 259 | ** The highly accessible Modus themes collection has six items. |
| 256 | The 'modus-operandi' and 'modus-vivendi' are the main themes that have | 260 | The 'modus-operandi' and 'modus-vivendi' are the main themes that have |
diff --git a/lisp/progmodes/eglot.el b/lisp/progmodes/eglot.el index 2f8d2002cd3..7b2341f3f49 100644 --- a/lisp/progmodes/eglot.el +++ b/lisp/progmodes/eglot.el | |||
| @@ -221,7 +221,7 @@ chosen (interactively or automatically)." | |||
| 221 | ((java-mode java-ts-mode) . ("jdtls")) | 221 | ((java-mode java-ts-mode) . ("jdtls")) |
| 222 | (dart-mode . ("dart" "language-server" | 222 | (dart-mode . ("dart" "language-server" |
| 223 | "--client-id" "emacs.eglot-dart")) | 223 | "--client-id" "emacs.eglot-dart")) |
| 224 | (elixir-mode . ("language_server.sh")) | 224 | ((elixir-ts-mode elixir-mode) . ("language_server.sh")) |
| 225 | (ada-mode . ("ada_language_server")) | 225 | (ada-mode . ("ada_language_server")) |
| 226 | (scala-mode . ,(eglot-alternatives | 226 | (scala-mode . ,(eglot-alternatives |
| 227 | '("metals" "metals-emacs"))) | 227 | '("metals" "metals-emacs"))) |
diff --git a/lisp/progmodes/elixir-ts-mode.el b/lisp/progmodes/elixir-ts-mode.el new file mode 100644 index 00000000000..8adf647b081 --- /dev/null +++ b/lisp/progmodes/elixir-ts-mode.el | |||
| @@ -0,0 +1,634 @@ | |||
| 1 | ;;; elixir-ts-mode.el --- Major mode for Elixir with tree-sitter support -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2022-2023 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; Author: Wilhelm H Kirschbaum <wkirschbaum@gmail.com> | ||
| 6 | ;; Created: November 2022 | ||
| 7 | ;; Keywords: elixir languages tree-sitter | ||
| 8 | |||
| 9 | ;; This file is part of GNU Emacs. | ||
| 10 | |||
| 11 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 12 | ;; it under the terms of the GNU General Public License as published by | ||
| 13 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 14 | ;; (at your option) any later version. | ||
| 15 | |||
| 16 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 17 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 18 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 19 | ;; GNU General Public License for more details. | ||
| 20 | |||
| 21 | ;; You should have received a copy of the GNU General Public License | ||
| 22 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 23 | |||
| 24 | ;;; Commentary: | ||
| 25 | ;; | ||
| 26 | ;; This package provides `elixir-ts-mode' which is a major mode for editing | ||
| 27 | ;; Elixir files and embedded HEEx templates that uses Tree Sitter to parse | ||
| 28 | ;; the language. | ||
| 29 | ;; | ||
| 30 | ;; This package is compatible with and was tested against the tree-sitter grammar | ||
| 31 | ;; for Elixir found at https://github.com/elixir-lang/tree-sitter-elixir. | ||
| 32 | ;; | ||
| 33 | ;; Features | ||
| 34 | ;; | ||
| 35 | ;; * Indent | ||
| 36 | ;; | ||
| 37 | ;; `elixir-ts-mode' tries to replicate the indentation provided by | ||
| 38 | ;; mix format, but will come with some minor differences. | ||
| 39 | ;; | ||
| 40 | ;; * IMenu | ||
| 41 | ;; * Navigation | ||
| 42 | ;; * Which-fun | ||
| 43 | |||
| 44 | ;;; Code: | ||
| 45 | |||
| 46 | (require 'treesit) | ||
| 47 | (require 'heex-ts-mode) | ||
| 48 | (eval-when-compile (require 'rx)) | ||
| 49 | |||
| 50 | (declare-function treesit-parser-create "treesit.c") | ||
| 51 | (declare-function treesit-node-child "treesit.c") | ||
| 52 | (declare-function treesit-node-type "treesit.c") | ||
| 53 | (declare-function treesit-node-child-by-field-name "treesit.c") | ||
| 54 | (declare-function treesit-parser-language "treesit.c") | ||
| 55 | (declare-function treesit-parser-included-ranges "treesit.c") | ||
| 56 | (declare-function treesit-parser-list "treesit.c") | ||
| 57 | (declare-function treesit-node-parent "treesit.c") | ||
| 58 | (declare-function treesit-node-start "treesit.c") | ||
| 59 | (declare-function treesit-query-compile "treesit.c") | ||
| 60 | (declare-function treesit-node-eq "treesit.c") | ||
| 61 | (declare-function treesit-node-prev-sibling "treesit.c") | ||
| 62 | |||
| 63 | (defgroup elixir-ts nil | ||
| 64 | "Major mode for editing Elixir code." | ||
| 65 | :prefix "elixir-ts-" | ||
| 66 | :group 'languages) | ||
| 67 | |||
| 68 | (defcustom elixir-ts-indent-offset 2 | ||
| 69 | "Indentation of Elixir statements." | ||
| 70 | :version "30.1" | ||
| 71 | :type 'integer | ||
| 72 | :safe 'integerp | ||
| 73 | :group 'elixir-ts) | ||
| 74 | |||
| 75 | (defface elixir-ts-font-comment-doc-identifier-face | ||
| 76 | '((t (:inherit font-lock-doc-face))) | ||
| 77 | "Face used for @comment.doc tags in Elixir files.") | ||
| 78 | |||
| 79 | (defface elixir-ts-font-comment-doc-attribute-face | ||
| 80 | '((t (:inherit font-lock-doc-face))) | ||
| 81 | "Face used for @comment.doc.__attribute__ tags in Elixir files.") | ||
| 82 | |||
| 83 | (defface elixir-ts-font-sigil-name-face | ||
| 84 | '((t (:inherit font-lock-string-face))) | ||
| 85 | "Face used for @__name__ tags in Elixir files.") | ||
| 86 | |||
| 87 | (defconst elixir-ts--sexp-regexp | ||
| 88 | (rx bol | ||
| 89 | (or "call" "stab_clause" "binary_operator" "list" "tuple" "map" "pair" | ||
| 90 | "sigil" "string" "atom" "pair" "alias" "arguments" "atom" "identifier" | ||
| 91 | "boolean" "quoted_content") | ||
| 92 | eol)) | ||
| 93 | |||
| 94 | (defconst elixir-ts--test-definition-keywords | ||
| 95 | '("describe" "test")) | ||
| 96 | |||
| 97 | (defconst elixir-ts--definition-keywords | ||
| 98 | '("def" "defdelegate" "defexception" "defguard" "defguardp" | ||
| 99 | "defimpl" "defmacro" "defmacrop" "defmodule" "defn" "defnp" | ||
| 100 | "defoverridable" "defp" "defprotocol" "defstruct")) | ||
| 101 | |||
| 102 | (defconst elixir-ts--definition-keywords-re | ||
| 103 | (concat "^" (regexp-opt elixir-ts--definition-keywords) "$")) | ||
| 104 | |||
| 105 | (defconst elixir-ts--kernel-keywords | ||
| 106 | '("alias" "case" "cond" "else" "for" "if" "import" "quote" | ||
| 107 | "raise" "receive" "require" "reraise" "super" "throw" "try" | ||
| 108 | "unless" "unquote" "unquote_splicing" "use" "with")) | ||
| 109 | |||
| 110 | (defconst elixir-ts--kernel-keywords-re | ||
| 111 | (concat "^" (regexp-opt elixir-ts--kernel-keywords) "$")) | ||
| 112 | |||
| 113 | (defconst elixir-ts--builtin-keywords | ||
| 114 | '("__MODULE__" "__DIR__" "__ENV__" "__CALLER__" "__STACKTRACE__")) | ||
| 115 | |||
| 116 | (defconst elixir-ts--builtin-keywords-re | ||
| 117 | (concat "^" (regexp-opt elixir-ts--builtin-keywords) "$")) | ||
| 118 | |||
| 119 | (defconst elixir-ts--doc-keywords | ||
| 120 | '("moduledoc" "typedoc" "doc")) | ||
| 121 | |||
| 122 | (defconst elixir-ts--doc-keywords-re | ||
| 123 | (concat "^" (regexp-opt elixir-ts--doc-keywords) "$")) | ||
| 124 | |||
| 125 | (defconst elixir-ts--reserved-keywords | ||
| 126 | '("when" "and" "or" "not" "in" | ||
| 127 | "not in" "fn" "do" "end" "catch" "rescue" "after" "else")) | ||
| 128 | |||
| 129 | (defconst elixir-ts--reserved-keywords-re | ||
| 130 | (concat "^" (regexp-opt elixir-ts--reserved-keywords) "$")) | ||
| 131 | |||
| 132 | (defconst elixir-ts--reserved-keywords-vector | ||
| 133 | (apply #'vector elixir-ts--reserved-keywords)) | ||
| 134 | |||
| 135 | (defvar elixir-ts--capture-anonymous-function-end | ||
| 136 | (when (treesit-available-p) | ||
| 137 | (treesit-query-compile 'elixir '((anonymous_function "end" @end))))) | ||
| 138 | |||
| 139 | (defvar elixir-ts--capture-operator-parent | ||
| 140 | (when (treesit-available-p) | ||
| 141 | (treesit-query-compile 'elixir '((binary_operator operator: _ @val))))) | ||
| 142 | |||
| 143 | (defvar elixir-ts--syntax-table | ||
| 144 | (let ((table (make-syntax-table))) | ||
| 145 | (modify-syntax-entry ?| "." table) | ||
| 146 | (modify-syntax-entry ?- "." table) | ||
| 147 | (modify-syntax-entry ?+ "." table) | ||
| 148 | (modify-syntax-entry ?* "." table) | ||
| 149 | (modify-syntax-entry ?/ "." table) | ||
| 150 | (modify-syntax-entry ?< "." table) | ||
| 151 | (modify-syntax-entry ?> "." table) | ||
| 152 | (modify-syntax-entry ?_ "_" table) | ||
| 153 | (modify-syntax-entry ?? "w" table) | ||
| 154 | (modify-syntax-entry ?~ "w" table) | ||
| 155 | (modify-syntax-entry ?! "_" table) | ||
| 156 | (modify-syntax-entry ?' "\"" table) | ||
| 157 | (modify-syntax-entry ?\" "\"" table) | ||
| 158 | (modify-syntax-entry ?# "<" table) | ||
| 159 | (modify-syntax-entry ?\n ">" table) | ||
| 160 | (modify-syntax-entry ?\( "()" table) | ||
| 161 | (modify-syntax-entry ?\) ")(" table) | ||
| 162 | (modify-syntax-entry ?\{ "(}" table) | ||
| 163 | (modify-syntax-entry ?\} "){" table) | ||
| 164 | (modify-syntax-entry ?\[ "(]" table) | ||
| 165 | (modify-syntax-entry ?\] ")[" table) | ||
| 166 | (modify-syntax-entry ?: "'" table) | ||
| 167 | (modify-syntax-entry ?@ "'" table) | ||
| 168 | table) | ||
| 169 | "Syntax table for `elixir-ts-mode'.") | ||
| 170 | |||
| 171 | (defun elixir-ts--argument-indent-offset (node _parent &rest _) | ||
| 172 | "Return the argument offset position for NODE." | ||
| 173 | (if (treesit-node-prev-sibling node t) 0 elixir-ts-indent-offset)) | ||
| 174 | |||
| 175 | (defun elixir-ts--argument-indent-anchor (node parent &rest _) | ||
| 176 | "Return the argument anchor position for NODE and PARENT." | ||
| 177 | (let ((first-sibling (treesit-node-child parent 0 t))) | ||
| 178 | (if (and first-sibling (not (treesit-node-eq first-sibling node))) | ||
| 179 | (treesit-node-start first-sibling) | ||
| 180 | (elixir-ts--parent-expression-start node parent)))) | ||
| 181 | |||
| 182 | (defun elixir-ts--parent-expression-start (_node parent &rest _) | ||
| 183 | "Return the indentation expression start for NODE and PARENT." | ||
| 184 | ;; If the parent is the first expression on the line return the | ||
| 185 | ;; parent start of node position, otherwise use the parent call | ||
| 186 | ;; start if available. | ||
| 187 | (if (eq (treesit-node-start parent) | ||
| 188 | (save-excursion | ||
| 189 | (goto-char (treesit-node-start parent)) | ||
| 190 | (back-to-indentation) | ||
| 191 | (point))) | ||
| 192 | (treesit-node-start parent) | ||
| 193 | (let ((expr-parent | ||
| 194 | (treesit-parent-until | ||
| 195 | parent | ||
| 196 | (lambda (n) | ||
| 197 | (member (treesit-node-type n) | ||
| 198 | '("call" "binary_operator" "keywords" "list")))))) | ||
| 199 | (save-excursion | ||
| 200 | (goto-char (treesit-node-start expr-parent)) | ||
| 201 | (back-to-indentation) | ||
| 202 | (if (looking-at "|>") | ||
| 203 | (point) | ||
| 204 | (treesit-node-start expr-parent)))))) | ||
| 205 | |||
| 206 | (defvar elixir-ts--indent-rules | ||
| 207 | (let ((offset elixir-ts-indent-offset)) | ||
| 208 | `((elixir | ||
| 209 | ((parent-is "^source$") column-0 0) | ||
| 210 | ((parent-is "^string$") parent-bol 0) | ||
| 211 | ((parent-is "^quoted_content$") | ||
| 212 | (lambda (_n parent bol &rest _) | ||
| 213 | (save-excursion | ||
| 214 | (back-to-indentation) | ||
| 215 | (if (bolp) | ||
| 216 | (progn | ||
| 217 | (goto-char (treesit-node-start parent)) | ||
| 218 | (back-to-indentation) | ||
| 219 | (point)) | ||
| 220 | (point)))) | ||
| 221 | 0) | ||
| 222 | ((node-is "^|>$") parent-bol 0) | ||
| 223 | ((node-is "^|$") parent-bol 0) | ||
| 224 | ((node-is "^]$") ,'elixir-ts--parent-expression-start 0) | ||
| 225 | ((node-is "^}$") ,'elixir-ts--parent-expression-start 0) | ||
| 226 | ((node-is "^)$") ,'elixir-ts--parent-expression-start 0) | ||
| 227 | ((node-is "^else_block$") grand-parent 0) | ||
| 228 | ((node-is "^catch_block$") grand-parent 0) | ||
| 229 | ((node-is "^rescue_block$") grand-parent 0) | ||
| 230 | ((node-is "^after_block$") grand-parent 0) | ||
| 231 | ((parent-is "^else_block$") parent ,offset) | ||
| 232 | ((parent-is "^catch_block$") parent ,offset) | ||
| 233 | ((parent-is "^rescue_block$") parent ,offset) | ||
| 234 | ((parent-is "^rescue_block$") parent ,offset) | ||
| 235 | ((parent-is "^after_block$") parent ,offset) | ||
| 236 | ((parent-is "^access_call$") | ||
| 237 | ,'elixir-ts--argument-indent-anchor | ||
| 238 | ,'elixir-ts--argument-indent-offset) | ||
| 239 | ((parent-is "^tuple$") | ||
| 240 | ,'elixir-ts--argument-indent-anchor | ||
| 241 | ,'elixir-ts--argument-indent-offset) | ||
| 242 | ((parent-is "^list$") | ||
| 243 | ,'elixir-ts--argument-indent-anchor | ||
| 244 | ,'elixir-ts--argument-indent-offset) | ||
| 245 | ((parent-is "^pair$") parent ,offset) | ||
| 246 | ((parent-is "^map_content$") parent-bol 0) | ||
| 247 | ((parent-is "^map$") ,'elixir-ts--parent-expression-start ,offset) | ||
| 248 | ((node-is "^stab_clause$") parent-bol ,offset) | ||
| 249 | ((query ,elixir-ts--capture-operator-parent) grand-parent 0) | ||
| 250 | ((node-is "^when$") parent 0) | ||
| 251 | ((node-is "^keywords$") parent-bol ,offset) | ||
| 252 | ((parent-is "^body$") | ||
| 253 | (lambda (node parent _) | ||
| 254 | (save-excursion | ||
| 255 | ;; The grammar adds a comment outside of the body, so we have to indent | ||
| 256 | ;; to the grand-parent if it is available. | ||
| 257 | (goto-char (treesit-node-start | ||
| 258 | (or (treesit-node-parent parent) (parent)))) | ||
| 259 | (back-to-indentation) | ||
| 260 | (point))) | ||
| 261 | ,offset) | ||
| 262 | ((parent-is "^arguments$") | ||
| 263 | ,'elixir-ts--argument-indent-anchor | ||
| 264 | ,'elixir-ts--argument-indent-offset) | ||
| 265 | ;; Handle incomplete maps when parent is ERROR. | ||
| 266 | ((n-p-gp "^binary_operator$" "ERROR" nil) parent-bol 0) | ||
| 267 | ;; When there is an ERROR, just indent to prev-line. | ||
| 268 | ((parent-is "ERROR") prev-line 0) | ||
| 269 | ((node-is "^binary_operator$") | ||
| 270 | (lambda (node parent &rest _) | ||
| 271 | (let ((top-level | ||
| 272 | (treesit-parent-while | ||
| 273 | node | ||
| 274 | (lambda (node) | ||
| 275 | (equal (treesit-node-type node) | ||
| 276 | "binary_operator"))))) | ||
| 277 | (if (treesit-node-eq top-level node) | ||
| 278 | (elixir-ts--parent-expression-start node parent) | ||
| 279 | (treesit-node-start top-level)))) | ||
| 280 | (lambda (node parent _) | ||
| 281 | (cond | ||
| 282 | ((equal (treesit-node-type parent) "do_block") | ||
| 283 | ,offset) | ||
| 284 | ((equal (treesit-node-type parent) "binary_operator") | ||
| 285 | ,offset) | ||
| 286 | (t 0)))) | ||
| 287 | ((parent-is "^binary_operator$") | ||
| 288 | (lambda (node parent bol &rest _) | ||
| 289 | (treesit-node-start | ||
| 290 | (treesit-parent-while | ||
| 291 | parent | ||
| 292 | (lambda (node) | ||
| 293 | (equal (treesit-node-type node) "binary_operator"))))) | ||
| 294 | ,offset) | ||
| 295 | ((node-is "^pair$") first-sibling 0) | ||
| 296 | ((query ,elixir-ts--capture-anonymous-function-end) parent-bol 0) | ||
| 297 | ((node-is "^end$") standalone-parent 0) | ||
| 298 | ((parent-is "^do_block$") grand-parent ,offset) | ||
| 299 | ((parent-is "^anonymous_function$") | ||
| 300 | elixir-ts--treesit-anchor-grand-parent-bol ,offset) | ||
| 301 | ((parent-is "^else_block$") parent ,offset) | ||
| 302 | ((parent-is "^rescue_block$") parent ,offset) | ||
| 303 | ((parent-is "^catch_block$") parent ,offset) | ||
| 304 | ((parent-is "^keywords$") parent-bol 0) | ||
| 305 | ((node-is "^call$") parent-bol ,offset) | ||
| 306 | ((node-is "^comment$") parent-bol ,offset))))) | ||
| 307 | |||
| 308 | (defvar elixir-ts--font-lock-settings | ||
| 309 | (treesit-font-lock-rules | ||
| 310 | :language 'elixir | ||
| 311 | :feature 'elixir-comment | ||
| 312 | '((comment) @font-lock-comment-face) | ||
| 313 | |||
| 314 | :language 'elixir | ||
| 315 | :feature 'elixir-string | ||
| 316 | :override t | ||
| 317 | '([(string) (charlist)] @font-lock-string-face) | ||
| 318 | |||
| 319 | :language 'elixir | ||
| 320 | :feature 'elixir-string-interpolation | ||
| 321 | :override t | ||
| 322 | '((string | ||
| 323 | [ | ||
| 324 | quoted_end: _ @font-lock-string-face | ||
| 325 | quoted_start: _ @font-lock-string-face | ||
| 326 | (quoted_content) @font-lock-string-face | ||
| 327 | (interpolation | ||
| 328 | "#{" @font-lock-regexp-grouping-backslash "}" | ||
| 329 | @font-lock-regexp-grouping-backslash) | ||
| 330 | ]) | ||
| 331 | (charlist | ||
| 332 | [ | ||
| 333 | quoted_end: _ @font-lock-string-face | ||
| 334 | quoted_start: _ @font-lock-string-face | ||
| 335 | (quoted_content) @font-lock-string-face | ||
| 336 | (interpolation | ||
| 337 | "#{" @font-lock-regexp-grouping-backslash "}" | ||
| 338 | @font-lock-regexp-grouping-backslash) | ||
| 339 | ])) | ||
| 340 | |||
| 341 | :language 'elixir | ||
| 342 | :feature 'elixir-keyword | ||
| 343 | `(,elixir-ts--reserved-keywords-vector | ||
| 344 | @font-lock-keyword-face | ||
| 345 | (binary_operator | ||
| 346 | operator: _ @font-lock-keyword-face | ||
| 347 | (:match ,elixir-ts--reserved-keywords-re @font-lock-keyword-face))) | ||
| 348 | |||
| 349 | :language 'elixir | ||
| 350 | :feature 'elixir-doc | ||
| 351 | :override t | ||
| 352 | `((unary_operator | ||
| 353 | operator: "@" @elixir-ts-font-comment-doc-attribute-face | ||
| 354 | operand: (call | ||
| 355 | target: (identifier) @elixir-ts-font-comment-doc-identifier-face | ||
| 356 | ;; Arguments can be optional, so adding another | ||
| 357 | ;; entry without arguments. | ||
| 358 | ;; If we don't handle then we don't apply font | ||
| 359 | ;; and the non doc fortification query will take specify | ||
| 360 | ;; a more specific font which takes precedence. | ||
| 361 | (arguments | ||
| 362 | [ | ||
| 363 | (string) @font-lock-doc-face | ||
| 364 | (charlist) @font-lock-doc-face | ||
| 365 | (sigil) @font-lock-doc-face | ||
| 366 | (boolean) @font-lock-doc-face | ||
| 367 | ])) | ||
| 368 | (:match ,elixir-ts--doc-keywords-re | ||
| 369 | @elixir-ts-font-comment-doc-identifier-face)) | ||
| 370 | (unary_operator | ||
| 371 | operator: "@" @elixir-ts-font-comment-doc-attribute-face | ||
| 372 | operand: (call | ||
| 373 | target: (identifier) @elixir-ts-font-comment-doc-identifier-face) | ||
| 374 | (:match ,elixir-ts--doc-keywords-re | ||
| 375 | @elixir-ts-font-comment-doc-identifier-face))) | ||
| 376 | |||
| 377 | :language 'elixir | ||
| 378 | :feature 'elixir-unary-operator | ||
| 379 | `((unary_operator operator: "@" @font-lock-preprocessor-face | ||
| 380 | operand: [ | ||
| 381 | (identifier) @font-lock-preprocessor-face | ||
| 382 | (call target: (identifier) | ||
| 383 | @font-lock-preprocessor-face) | ||
| 384 | (boolean) @font-lock-preprocessor-face | ||
| 385 | (nil) @font-lock-preprocessor-face | ||
| 386 | ]) | ||
| 387 | |||
| 388 | (unary_operator operator: "&") @font-lock-function-name-face | ||
| 389 | (operator_identifier) @font-lock-operator-face) | ||
| 390 | |||
| 391 | :language 'elixir | ||
| 392 | :feature 'elixir-operator | ||
| 393 | '((binary_operator operator: _ @font-lock-operator-face) | ||
| 394 | (dot operator: _ @font-lock-operator-face) | ||
| 395 | (stab_clause operator: _ @font-lock-operator-face) | ||
| 396 | |||
| 397 | [(boolean) (nil)] @font-lock-constant-face | ||
| 398 | [(integer) (float)] @font-lock-number-face | ||
| 399 | (alias) @font-lock-type-face | ||
| 400 | (call target: (dot left: (atom) @font-lock-type-face)) | ||
| 401 | (char) @font-lock-constant-face | ||
| 402 | [(atom) (quoted_atom)] @font-lock-type-face | ||
| 403 | [(keyword) (quoted_keyword)] @font-lock-builtin-face) | ||
| 404 | |||
| 405 | :language 'elixir | ||
| 406 | :feature 'elixir-call | ||
| 407 | `((call | ||
| 408 | target: (identifier) @font-lock-keyword-face | ||
| 409 | (:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face)) | ||
| 410 | (call | ||
| 411 | target: (identifier) @font-lock-keyword-face | ||
| 412 | (:match ,elixir-ts--kernel-keywords-re @font-lock-keyword-face)) | ||
| 413 | (call | ||
| 414 | target: [(identifier) @font-lock-function-name-face | ||
| 415 | (dot right: (identifier) @font-lock-keyword-face)]) | ||
| 416 | (call | ||
| 417 | target: (identifier) @font-lock-keyword-face | ||
| 418 | (arguments | ||
| 419 | [ | ||
| 420 | (identifier) @font-lock-keyword-face | ||
| 421 | (binary_operator | ||
| 422 | left: (identifier) @font-lock-keyword-face | ||
| 423 | operator: "when") | ||
| 424 | ]) | ||
| 425 | (:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face)) | ||
| 426 | (call | ||
| 427 | target: (identifier) @font-lock-keyword-face | ||
| 428 | (arguments | ||
| 429 | (binary_operator | ||
| 430 | operator: "|>" | ||
| 431 | right: (identifier))) | ||
| 432 | (:match ,elixir-ts--definition-keywords-re @font-lock-keyword-face))) | ||
| 433 | |||
| 434 | :language 'elixir | ||
| 435 | :feature 'elixir-constant | ||
| 436 | `((binary_operator operator: "|>" right: (identifier) | ||
| 437 | @font-lock-function-name-face) | ||
| 438 | ((identifier) @font-lock-keyword-face | ||
| 439 | (:match ,elixir-ts--builtin-keywords-re | ||
| 440 | @font-lock-keyword-face)) | ||
| 441 | ((identifier) @font-lock-comment-face | ||
| 442 | (:match "^_" @font-lock-comment-face)) | ||
| 443 | (identifier) @font-lock-function-name-face | ||
| 444 | ["%"] @font-lock-keyward-face | ||
| 445 | ["," ";"] @font-lock-keyword-face | ||
| 446 | ["(" ")" "[" "]" "{" "}" "<<" ">>"] @font-lock-keyword-face) | ||
| 447 | |||
| 448 | :language 'elixir | ||
| 449 | :feature 'elixir-sigil | ||
| 450 | :override t | ||
| 451 | `((sigil | ||
| 452 | (sigil_name) @elixir-ts-font-sigil-name-face | ||
| 453 | quoted_start: _ @font-lock-string-face | ||
| 454 | quoted_end: _ @font-lock-string-face | ||
| 455 | (:match "^[sSwWpP]$" @elixir-ts-font-sigil-name-face)) | ||
| 456 | @font-lock-string-face | ||
| 457 | (sigil | ||
| 458 | (sigil_name) @elixir-ts-font-sigil-name-face | ||
| 459 | quoted_start: _ @font-lock-regex-face | ||
| 460 | quoted_end: _ @font-lock-regex-face | ||
| 461 | (:match "^[rR]$" @elixir-ts-font-sigil-name-face)) | ||
| 462 | @font-lock-regex-face | ||
| 463 | (sigil | ||
| 464 | "~" @font-lock-string-face | ||
| 465 | (sigil_name) @elixir-ts-font-sigil-name-face | ||
| 466 | quoted_start: _ @font-lock-string-face | ||
| 467 | quoted_end: _ @font-lock-string-face | ||
| 468 | (:match "^[HF]$" @elixir-ts-font-sigil-name-face))) | ||
| 469 | |||
| 470 | :language 'elixir | ||
| 471 | :feature 'elixir-string-escape | ||
| 472 | :override t | ||
| 473 | `((escape_sequence) @font-lock-regexp-grouping-backslash)) | ||
| 474 | "Tree-sitter font-lock settings.") | ||
| 475 | |||
| 476 | (defvar elixir-ts--treesit-range-rules | ||
| 477 | (when (treesit-available-p) | ||
| 478 | (treesit-range-rules | ||
| 479 | :embed 'heex | ||
| 480 | :host 'elixir | ||
| 481 | '((sigil (sigil_name) @name (:match "^[HF]$" @name) (quoted_content) @heex))))) | ||
| 482 | |||
| 483 | (defun elixir-ts--forward-sexp (&optional arg) | ||
| 484 | "Move forward across one balanced expression (sexp). | ||
| 485 | With ARG, do it many times. Negative ARG means move backward." | ||
| 486 | (or arg (setq arg 1)) | ||
| 487 | (funcall | ||
| 488 | (if (> arg 0) #'treesit-end-of-thing #'treesit-beginning-of-thing) | ||
| 489 | (if (eq (treesit-language-at (point)) 'heex) | ||
| 490 | heex-ts--sexp-regexp | ||
| 491 | elixir-ts--sexp-regexp) | ||
| 492 | (abs arg))) | ||
| 493 | |||
| 494 | (defun elixir-ts--treesit-anchor-grand-parent-bol (_n parent &rest _) | ||
| 495 | "Return the beginning of non-space characters for the parent node of PARENT." | ||
| 496 | (save-excursion | ||
| 497 | (goto-char (treesit-node-start (treesit-node-parent parent))) | ||
| 498 | (back-to-indentation) | ||
| 499 | (point))) | ||
| 500 | |||
| 501 | (defun elixir-ts--treesit-language-at-point (point) | ||
| 502 | "Return the language at POINT." | ||
| 503 | (let* ((range nil) | ||
| 504 | (language-in-range | ||
| 505 | (cl-loop | ||
| 506 | for parser in (treesit-parser-list) | ||
| 507 | do (setq range | ||
| 508 | (cl-loop | ||
| 509 | for range in (treesit-parser-included-ranges parser) | ||
| 510 | if (and (>= point (car range)) (<= point (cdr range))) | ||
| 511 | return parser)) | ||
| 512 | if range | ||
| 513 | return (treesit-parser-language parser)))) | ||
| 514 | (if (null language-in-range) | ||
| 515 | (when-let ((parser (car (treesit-parser-list)))) | ||
| 516 | (treesit-parser-language parser)) | ||
| 517 | language-in-range))) | ||
| 518 | |||
| 519 | (defun elixir-ts--defun-p (node) | ||
| 520 | "Return non-nil when NODE is a defun." | ||
| 521 | (member (treesit-node-text | ||
| 522 | (treesit-node-child-by-field-name node "target")) | ||
| 523 | (append | ||
| 524 | elixir-ts--definition-keywords | ||
| 525 | elixir-ts--test-definition-keywords))) | ||
| 526 | |||
| 527 | (defun elixir-ts--defun-name (node) | ||
| 528 | "Return the name of the defun NODE. | ||
| 529 | Return nil if NODE is not a defun node or doesn't have a name." | ||
| 530 | (pcase (treesit-node-type node) | ||
| 531 | ("call" (let ((node-child | ||
| 532 | (treesit-node-child (treesit-node-child node 1) 0))) | ||
| 533 | (pcase (treesit-node-type node-child) | ||
| 534 | ("alias" (treesit-node-text node-child t)) | ||
| 535 | ("call" (treesit-node-text | ||
| 536 | (treesit-node-child-by-field-name node-child "target") t)) | ||
| 537 | ("binary_operator" | ||
| 538 | (treesit-node-text | ||
| 539 | (treesit-node-child-by-field-name | ||
| 540 | (treesit-node-child-by-field-name node-child "left") "target") | ||
| 541 | t)) | ||
| 542 | ("identifier" | ||
| 543 | (treesit-node-text node-child t)) | ||
| 544 | (_ nil)))) | ||
| 545 | (_ nil))) | ||
| 546 | |||
| 547 | ;;;###autoload | ||
| 548 | (define-derived-mode elixir-ts-mode prog-mode "Elixir" | ||
| 549 | "Major mode for editing Elixir, powered by tree-sitter." | ||
| 550 | :group 'elixir-ts | ||
| 551 | :syntax-table elixir-ts--syntax-table | ||
| 552 | |||
| 553 | ;; Comments | ||
| 554 | (setq-local comment-start "# ") | ||
| 555 | (setq-local comment-start-skip | ||
| 556 | (rx "#" (* (syntax whitespace)))) | ||
| 557 | |||
| 558 | (setq-local comment-end "") | ||
| 559 | (setq-local comment-end-skip | ||
| 560 | (rx (* (syntax whitespace)) | ||
| 561 | (group (or (syntax comment-end) "\n")))) | ||
| 562 | |||
| 563 | ;; Compile | ||
| 564 | (setq-local compile-command "mix") | ||
| 565 | |||
| 566 | (when (treesit-ready-p 'elixir) | ||
| 567 | ;; The HEEx parser has to be created first for elixir to ensure elixir | ||
| 568 | ;; is the first language when looking for treesit ranges. | ||
| 569 | (if (treesit-ready-p 'heex) | ||
| 570 | (treesit-parser-create 'heex)) | ||
| 571 | |||
| 572 | (treesit-parser-create 'elixir) | ||
| 573 | |||
| 574 | (setq-local treesit-language-at-point-function | ||
| 575 | 'elixir-ts--treesit-language-at-point) | ||
| 576 | |||
| 577 | ;; Font-lock. | ||
| 578 | (setq-local treesit-font-lock-settings elixir-ts--font-lock-settings) | ||
| 579 | (setq-local treesit-font-lock-feature-list | ||
| 580 | '(( elixir-comment elixir-constant elixir-doc ) | ||
| 581 | ( elixir-string elixir-keyword elixir-unary-operator | ||
| 582 | elixir-call elixir-operator ) | ||
| 583 | ( elixir-sigil elixir-string-escape elixir-string-interpolation))) | ||
| 584 | |||
| 585 | ;; Imenu. | ||
| 586 | (setq-local treesit-simple-imenu-settings | ||
| 587 | '((nil "\\`call\\'" elixir-ts--defun-p nil))) | ||
| 588 | |||
| 589 | ;; Indent. | ||
| 590 | (setq-local treesit-simple-indent-rules elixir-ts--indent-rules) | ||
| 591 | |||
| 592 | ;; Navigation | ||
| 593 | (setq-local forward-sexp-function #'elixir-ts--forward-sexp) | ||
| 594 | (setq-local treesit-defun-type-regexp | ||
| 595 | '("call" . elixir-ts--defun-p)) | ||
| 596 | |||
| 597 | (setq-local treesit-defun-name-function #'elixir-ts--defun-name) | ||
| 598 | |||
| 599 | ;; Embedded Heex | ||
| 600 | (when (treesit-ready-p 'heex) | ||
| 601 | (setq-local treesit-range-settings elixir-ts--treesit-range-rules) | ||
| 602 | |||
| 603 | (setq-local treesit-simple-indent-rules | ||
| 604 | (append treesit-simple-indent-rules heex-ts--indent-rules)) | ||
| 605 | |||
| 606 | (setq-local treesit-font-lock-settings | ||
| 607 | (append treesit-font-lock-settings | ||
| 608 | heex-ts--font-lock-settings)) | ||
| 609 | |||
| 610 | (setq-local treesit-simple-indent-rules | ||
| 611 | (append treesit-simple-indent-rules | ||
| 612 | heex-ts--indent-rules)) | ||
| 613 | |||
| 614 | (setq-local treesit-font-lock-feature-list | ||
| 615 | '(( elixir-comment elixir-constant elixir-doc | ||
| 616 | heex-comment heex-keyword heex-doctype ) | ||
| 617 | ( elixir-string elixir-keyword elixir-unary-operator | ||
| 618 | elixir-call elixir-operator | ||
| 619 | heex-component heex-tag heex-attribute heex-string) | ||
| 620 | ( elixir-sigil elixir-string-escape | ||
| 621 | elixir-string-interpolation )))) | ||
| 622 | |||
| 623 | (treesit-major-mode-setup))) | ||
| 624 | |||
| 625 | (if (treesit-ready-p 'elixir) | ||
| 626 | (progn | ||
| 627 | (add-to-list 'auto-mode-alist '("\\.elixir\\'" . elixir-ts-mode)) | ||
| 628 | (add-to-list 'auto-mode-alist '("\\.ex\\'" . elixir-ts-mode)) | ||
| 629 | (add-to-list 'auto-mode-alist '("\\.exs\\'" . elixir-ts-mode)) | ||
| 630 | (add-to-list 'auto-mode-alist '("mix\\.lock" . elixir-ts-mode)))) | ||
| 631 | |||
| 632 | (provide 'elixir-ts-mode) | ||
| 633 | |||
| 634 | ;;; elixir-ts-mode.el ends here | ||
diff --git a/test/lisp/progmodes/elixir-ts-mode-resources/indent.erts b/test/lisp/progmodes/elixir-ts-mode-resources/indent.erts new file mode 100644 index 00000000000..748455cc3f2 --- /dev/null +++ b/test/lisp/progmodes/elixir-ts-mode-resources/indent.erts | |||
| @@ -0,0 +1,308 @@ | |||
| 1 | Code: | ||
| 2 | (lambda () | ||
| 3 | (setq indent-tabs-mode nil) | ||
| 4 | (elixir-ts-mode) | ||
| 5 | (indent-region (point-min) (point-max))) | ||
| 6 | |||
| 7 | Point-Char: $ | ||
| 8 | |||
| 9 | Name: Basic modules | ||
| 10 | |||
| 11 | =-= | ||
| 12 | defmodule Foobar do | ||
| 13 | def bar() do | ||
| 14 | "one" | ||
| 15 | end | ||
| 16 | end | ||
| 17 | =-= | ||
| 18 | defmodule Foobar do | ||
| 19 | def bar() do | ||
| 20 | "one" | ||
| 21 | end | ||
| 22 | end | ||
| 23 | =-=-= | ||
| 24 | |||
| 25 | Name: Map | ||
| 26 | |||
| 27 | =-= | ||
| 28 | map = %{ | ||
| 29 | "a" => 1, | ||
| 30 | "b" => 2 | ||
| 31 | } | ||
| 32 | =-=-= | ||
| 33 | |||
| 34 | Name: Map in function def | ||
| 35 | |||
| 36 | =-= | ||
| 37 | def foobar() do | ||
| 38 | %{ | ||
| 39 | one: "one", | ||
| 40 | two: "two", | ||
| 41 | three: "three", | ||
| 42 | four: "four" | ||
| 43 | } | ||
| 44 | end | ||
| 45 | =-=-= | ||
| 46 | |||
| 47 | Name: Map in tuple | ||
| 48 | |||
| 49 | =-= | ||
| 50 | def foo() do | ||
| 51 | {:ok, | ||
| 52 | %{ | ||
| 53 | state | ||
| 54 | | extra_arguments: extra_arguments, | ||
| 55 | max_children: max_children, | ||
| 56 | max_restarts: max_restarts, | ||
| 57 | max_seconds: max_seconds, | ||
| 58 | strategy: strategy | ||
| 59 | }} | ||
| 60 | end | ||
| 61 | =-=-= | ||
| 62 | |||
| 63 | Name: Nested maps | ||
| 64 | |||
| 65 | =-= | ||
| 66 | %{ | ||
| 67 | foo: "bar", | ||
| 68 | bar: %{ | ||
| 69 | foo: "bar" | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | def foo() do | ||
| 74 | %{ | ||
| 75 | foo: "bar", | ||
| 76 | bar: %{ | ||
| 77 | foo: "bar" | ||
| 78 | } | ||
| 79 | } | ||
| 80 | end | ||
| 81 | =-=-= | ||
| 82 | |||
| 83 | Name: Block assignments | ||
| 84 | |||
| 85 | =-= | ||
| 86 | foo = | ||
| 87 | if true do | ||
| 88 | "yes" | ||
| 89 | else | ||
| 90 | "no" | ||
| 91 | end | ||
| 92 | =-=-= | ||
| 93 | |||
| 94 | Name: Function rescue | ||
| 95 | |||
| 96 | =-= | ||
| 97 | def foo do | ||
| 98 | "bar" | ||
| 99 | rescue | ||
| 100 | e -> | ||
| 101 | "bar" | ||
| 102 | end | ||
| 103 | =-=-= | ||
| 104 | |||
| 105 | Name: With statement | ||
| 106 | =-= | ||
| 107 | with one <- one(), | ||
| 108 | two <- two(), | ||
| 109 | {:ok, value} <- get_value(one, two) do | ||
| 110 | {:ok, value} | ||
| 111 | else | ||
| 112 | {:error, %{"Message" => message}} -> | ||
| 113 | {:error, message} | ||
| 114 | end | ||
| 115 | =-=-= | ||
| 116 | |||
| 117 | Name: Pipe statements with fn | ||
| 118 | |||
| 119 | =-= | ||
| 120 | [1, 2] | ||
| 121 | |> Enum.map(fn num -> | ||
| 122 | num + 1 | ||
| 123 | end) | ||
| 124 | =-=-= | ||
| 125 | |||
| 126 | Name: Pipe statements stab clases | ||
| 127 | |||
| 128 | =-= | ||
| 129 | [1, 2] | ||
| 130 | |> Enum.map(fn | ||
| 131 | x when x < 10 -> x * 2 | ||
| 132 | x -> x * 3 | ||
| 133 | end) | ||
| 134 | =-=-= | ||
| 135 | |||
| 136 | Name: Pipe statements params | ||
| 137 | |||
| 138 | =-= | ||
| 139 | [1, 2] | ||
| 140 | |> foobar( | ||
| 141 | :one, | ||
| 142 | :two, | ||
| 143 | :three, | ||
| 144 | :four | ||
| 145 | ) | ||
| 146 | =-=-= | ||
| 147 | |||
| 148 | Name: Parameter maps | ||
| 149 | |||
| 150 | =-= | ||
| 151 | def something(%{ | ||
| 152 | one: :one, | ||
| 153 | two: :two | ||
| 154 | }) do | ||
| 155 | {:ok, "done"} | ||
| 156 | end | ||
| 157 | =-=-= | ||
| 158 | |||
| 159 | Name: Binary operator in else block | ||
| 160 | |||
| 161 | =-= | ||
| 162 | defp foobar() do | ||
| 163 | if false do | ||
| 164 | :foo | ||
| 165 | else | ||
| 166 | :bar |> foo | ||
| 167 | end | ||
| 168 | end | ||
| 169 | =-=-= | ||
| 170 | |||
| 171 | Name: Tuple indentation | ||
| 172 | |||
| 173 | =-= | ||
| 174 | tuple = { | ||
| 175 | :one, | ||
| 176 | :two | ||
| 177 | } | ||
| 178 | |||
| 179 | { | ||
| 180 | :one, | ||
| 181 | :two | ||
| 182 | } | ||
| 183 | =-=-= | ||
| 184 | |||
| 185 | Name: Spec and method | ||
| 186 | |||
| 187 | =-= | ||
| 188 | @spec foobar( | ||
| 189 | t, | ||
| 190 | acc, | ||
| 191 | (one, something -> :bar | far), | ||
| 192 | (two -> :bar | far) | ||
| 193 | ) :: any() | ||
| 194 | when chunk: any | ||
| 195 | def foobar(enumerable, acc, chunk_fun, after_fun) do | ||
| 196 | {_, {res, acc}} = | ||
| 197 | case after_fun.(acc) do | ||
| 198 | {:one, "one"} -> | ||
| 199 | "one" | ||
| 200 | |||
| 201 | {:two, "two"} -> | ||
| 202 | "two" | ||
| 203 | end | ||
| 204 | end | ||
| 205 | =-=-= | ||
| 206 | |||
| 207 | Name: Spec with multi-line result | ||
| 208 | |||
| 209 | =-= | ||
| 210 | @type result :: | ||
| 211 | {:done, term} | ||
| 212 | | {:two} | ||
| 213 | | {:one} | ||
| 214 | |||
| 215 | @type result :: | ||
| 216 | { | ||
| 217 | :done, | ||
| 218 | term | ||
| 219 | } | ||
| 220 | | {:two} | ||
| 221 | | {:one} | ||
| 222 | |||
| 223 | @type boo_bar :: | ||
| 224 | (foo :: pos_integer, bar :: pos_integer -> any()) | ||
| 225 | |||
| 226 | @spec foo_bar( | ||
| 227 | t, | ||
| 228 | (foo -> any), | ||
| 229 | (() -> any) | (foo, foo -> boolean) | module() | ||
| 230 | ) :: any | ||
| 231 | when foo: any | ||
| 232 | def foo(one, fun, other) | ||
| 233 | =-=-= | ||
| 234 | |||
| 235 | Name: String concatenation in call | ||
| 236 | |||
| 237 | =-= | ||
| 238 | IO.warn( | ||
| 239 | "one" <> | ||
| 240 | "two" <> | ||
| 241 | "bar" | ||
| 242 | ) | ||
| 243 | |||
| 244 | IO.warn( | ||
| 245 | "foo" <> | ||
| 246 | "bar" | ||
| 247 | ) | ||
| 248 | =-=-= | ||
| 249 | |||
| 250 | Name: Incomplete tuple | ||
| 251 | |||
| 252 | =-= | ||
| 253 | map = { | ||
| 254 | :foo | ||
| 255 | |||
| 256 | =-= | ||
| 257 | map = { | ||
| 258 | :foo | ||
| 259 | |||
| 260 | =-=-= | ||
| 261 | |||
| 262 | Name: Incomplete map | ||
| 263 | |||
| 264 | =-= | ||
| 265 | map = %{ | ||
| 266 | "a" => "a", | ||
| 267 | =-=-= | ||
| 268 | |||
| 269 | Name: Incomplete list | ||
| 270 | |||
| 271 | =-= | ||
| 272 | map = [ | ||
| 273 | :foo | ||
| 274 | |||
| 275 | =-= | ||
| 276 | map = [ | ||
| 277 | :foo | ||
| 278 | |||
| 279 | =-=-= | ||
| 280 | |||
| 281 | Name: String concatenation | ||
| 282 | |||
| 283 | =-= | ||
| 284 | "one" <> | ||
| 285 | "two" <> | ||
| 286 | "three" <> | ||
| 287 | "four" | ||
| 288 | =-=-= | ||
| 289 | |||
| 290 | Name: Tuple with same line first node | ||
| 291 | |||
| 292 | =-= | ||
| 293 | {:one, | ||
| 294 | :two} | ||
| 295 | |||
| 296 | {:ok, | ||
| 297 | fn one -> | ||
| 298 | one | ||
| 299 | |> String.upcase(one) | ||
| 300 | end} | ||
| 301 | =-=-= | ||
| 302 | |||
| 303 | Name: Long tuple | ||
| 304 | |||
| 305 | =-= | ||
| 306 | {"January", "February", "March", "April", "May", "June", "July", "August", "September", | ||
| 307 | "October", "November", "December"} | ||
| 308 | =-=-= | ||
diff --git a/test/lisp/progmodes/elixir-ts-mode-tests.el b/test/lisp/progmodes/elixir-ts-mode-tests.el new file mode 100644 index 00000000000..8e546ad5cc6 --- /dev/null +++ b/test/lisp/progmodes/elixir-ts-mode-tests.el | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | ;;; c-ts-mode-tests.el --- Tests for Tree-sitter-based C mode -*- lexical-binding: t; -*- | ||
| 2 | |||
| 3 | ;; Copyright (C) 2023 Free Software Foundation, Inc. | ||
| 4 | |||
| 5 | ;; This file is part of GNU Emacs. | ||
| 6 | |||
| 7 | ;; GNU Emacs is free software: you can redistribute it and/or modify | ||
| 8 | ;; it under the terms of the GNU General Public License as published by | ||
| 9 | ;; the Free Software Foundation, either version 3 of the License, or | ||
| 10 | ;; (at your option) any later version. | ||
| 11 | |||
| 12 | ;; GNU Emacs is distributed in the hope that it will be useful, | ||
| 13 | ;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| 15 | ;; GNU General Public License for more details. | ||
| 16 | |||
| 17 | ;; You should have received a copy of the GNU General Public License | ||
| 18 | ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. | ||
| 19 | |||
| 20 | ;;; Code: | ||
| 21 | |||
| 22 | (require 'ert) | ||
| 23 | (require 'ert-x) | ||
| 24 | (require 'treesit) | ||
| 25 | |||
| 26 | (ert-deftest elixir-ts-mode-test-indentation () | ||
| 27 | (skip-unless (and (treesit-ready-p 'elixir) (treesit-ready-p 'heex))) | ||
| 28 | (ert-test-erts-file (ert-resource-file "indent.erts"))) | ||
| 29 | |||
| 30 | (provide 'elixir-ts-mode-tests) | ||
| 31 | ;;; elixir-ts-mode-tests.el ends here | ||