From: Wilhelm Kirschbaum <wkirschbaum@gmail.com>
To: Eli Zaretskii <eliz@gnu.org>
Cc: 61996@debbugs.gnu.org, theo@thornhill.no, casouri@gmail.com
Subject: bug#61996: 30.0.50; Submitting elixir-ts-mode and heex-ts-mode
Date: Sun, 12 Mar 2023 11:54:33 +0200 [thread overview]
Message-ID: <87sfeas30x.fsf@gmail.com> (raw)
In-Reply-To: <83fsaauywx.fsf@gnu.org>
[-- Attachment #1: Type: text/plain, Size: 1211 bytes --]
Eli Zaretskii <eliz@gnu.org> writes:
>> From: Wilhelm Kirschbaum <wkirschbaum@gmail.com>
>> Cc: 61996@debbugs.gnu.org, casouri@gmail.com, theo@thornhill.no
>> Date: Sat, 11 Mar 2023 20:01:32 +0200
>>
>> > Please accompany the changes with a commit log message
>> > according
>> > to our conventions (see CONTRIBUTE for the conventions;
>> > search for
>> > "ChangeLog" there). In this case, just "New file" log should
>> > be
>> > sufficient for the new files you add.
>> >
>> Thanks, was not aware of it. I hope it is correct in the new
>> patches.
>
> Yes, it is. Thanks.
>
>> The new patches should hopefully cover all of the above issues.
>
> Almost there. Byte compiler shows warnings, which I think are
> real
> problems in the code:
>
> In elixir-ts--call-parent-start:
> progmodes/elixir-ts-mode.el:459:38: Warning: Unused lexical
> argument `node'
> progmodes/elixir-ts-mode.el:463:15: Warning: reference to free
> variable `parent'
>
> In elixir-ts--forward-sexp:
> progmodes/elixir-ts-mode.el:482:8: Warning: reference to free
> variable `heex-ts--sexp-regexp'
Ah, not sure how I missed them. The new patches have further
tweaks and
should resolve the above issue.
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Add heex-ts-mode --]
[-- Type: text/x-patch, Size: 10234 bytes --]
From 2ad22dd0255a1a609e8bc13edff6e871c7c846d4 Mon Sep 17 00:00:00 2001
From: Wilhelm H Kirschbaum <wkirschbaum@gmail.com>
Date: Sun, 12 Mar 2023 11:44:36 +0200
Subject: [PATCH 1/2] Add heex-ts-mode (Bug#61996)
---
admin/notes/tree-sitter/build-module/batch.sh | 1 +
admin/notes/tree-sitter/build-module/build.sh | 3 +
etc/NEWS | 3 +
lisp/progmodes/heex-ts-mode.el | 185 ++++++++++++++++++
.../heex-ts-mode-resources/indent.erts | 47 +++++
test/lisp/progmodes/heex-ts-mode-tests.el | 9 +
6 files changed, 248 insertions(+)
create mode 100644 lisp/progmodes/heex-ts-mode.el
create mode 100644 test/lisp/progmodes/heex-ts-mode-resources/indent.erts
create mode 100644 test/lisp/progmodes/heex-ts-mode-tests.el
diff --git a/admin/notes/tree-sitter/build-module/batch.sh b/admin/notes/tree-sitter/build-module/batch.sh
index 58272c74549..8b0072782e8 100755
--- a/admin/notes/tree-sitter/build-module/batch.sh
+++ b/admin/notes/tree-sitter/build-module/batch.sh
@@ -10,6 +10,7 @@ languages=
'dockerfile'
'go'
'go-mod'
+ 'heex'
'html'
'javascript'
'json'
diff --git a/admin/notes/tree-sitter/build-module/build.sh b/admin/notes/tree-sitter/build-module/build.sh
index 9dc674237ca..78ecfb5bc82 100755
--- a/admin/notes/tree-sitter/build-module/build.sh
+++ b/admin/notes/tree-sitter/build-module/build.sh
@@ -36,6 +36,9 @@ grammardir=
lang="gomod"
org="camdencheek"
;;
+ "heex")
+ org="phoenixframework"
+ ;;
"typescript")
sourcedir="tree-sitter-typescript/typescript/src"
grammardir="tree-sitter-typescript/typescript"
diff --git a/etc/NEWS b/etc/NEWS
index 13d073c7fb8..ed74d0be1a1 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -230,6 +230,9 @@ This replaces 'doc-view-svg-foreground' and 'doc-view-svg-background'.
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.
+
---
** The highly accessible Modus themes collection has six items.
The 'modus-operandi' and 'modus-vivendi' are the main themes that have
diff --git a/lisp/progmodes/heex-ts-mode.el b/lisp/progmodes/heex-ts-mode.el
new file mode 100644
index 00000000000..68a537b9229
--- /dev/null
+++ b/lisp/progmodes/heex-ts-mode.el
@@ -0,0 +1,185 @@
+;;; 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
diff --git a/test/lisp/progmodes/heex-ts-mode-resources/indent.erts b/test/lisp/progmodes/heex-ts-mode-resources/indent.erts
new file mode 100644
index 00000000000..500ddb2b536
--- /dev/null
+++ b/test/lisp/progmodes/heex-ts-mode-resources/indent.erts
@@ -0,0 +1,47 @@
+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>
+=-=-=
diff --git a/test/lisp/progmodes/heex-ts-mode-tests.el b/test/lisp/progmodes/heex-ts-mode-tests.el
new file mode 100644
index 00000000000..b59126e136a
--- /dev/null
+++ b/test/lisp/progmodes/heex-ts-mode-tests.el
@@ -0,0 +1,9 @@
+(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)
--
2.39.2
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: Add elixir-ts-mode --]
[-- Type: text/x-patch, Size: 31585 bytes --]
From 6b7d72facfb397c8cfdc165829d79cf75ab68098 Mon Sep 17 00:00:00 2001
From: Wilhelm H Kirschbaum <wkirschbaum@gmail.com>
Date: Sun, 12 Mar 2023 11:45:11 +0200
Subject: [PATCH 2/2] 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.
---
admin/notes/tree-sitter/build-module/batch.sh | 1 +
admin/notes/tree-sitter/build-module/build.sh | 3 +
etc/NEWS | 4 +
lisp/progmodes/eglot.el | 2 +-
lisp/progmodes/elixir-ts-mode.el | 620 ++++++++++++++++++
.../elixir-ts-mode-resources/indent.erts | 233 +++++++
test/lisp/progmodes/elixir-ts-mode-tests.el | 31 +
7 files changed, 893 insertions(+), 1 deletion(-)
create mode 100644 lisp/progmodes/elixir-ts-mode.el
create mode 100644 test/lisp/progmodes/elixir-ts-mode-resources/indent.erts
create mode 100644 test/lisp/progmodes/elixir-ts-mode-tests.el
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=
'css'
'c-sharp'
'dockerfile'
+ 'elixir'
'go'
'go-mod'
'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 @@ grammardir=
"cmake")
org="uyha"
;;
+ "elixir")
+ org="elixir-lang"
+ ;;
"go-mod")
# The parser is called "gomod".
lang="gomod"
diff --git a/etc/NEWS b/etc/NEWS
index ed74d0be1a1..d40fa51d80d 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -233,6 +233,10 @@ 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
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 @@ eglot-server-programs
((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")))
diff --git a/lisp/progmodes/elixir-ts-mode.el b/lisp/progmodes/elixir-ts-mode.el
new file mode 100644
index 00000000000..08d73cd55eb
--- /dev/null
+++ b/lisp/progmodes/elixir-ts-mode.el
@@ -0,0 +1,620 @@
+;;; 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")
+
+(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'.")
+
+(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 "^|$") parent-bol 0)
+ ((node-is "^}$") parent-bol 0)
+ ((node-is "^)$")
+ (lambda (_node parent &rest _)
+ (elixir-ts--call-parent-start parent))
+ 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 "^tuple$") parent-bol ,offset)
+ ((parent-is "^list$") parent-bol ,offset)
+ ((parent-is "^pair$") parent ,offset)
+ ((parent-is "^map_content$") parent-bol 0)
+ ((parent-is "^map$") parent-bol ,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$")
+ ;; If there is no previous sibling indent
+ ;; to the call parent, otherwise
+ ;; indent to the same column as the prev-sibling.
+ (lambda (node parent &rest _)
+ (let ((prev-sibling (treesit-node-prev-sibling node t)))
+ (if prev-sibling
+ (treesit-node-start prev-sibling)
+ (elixir-ts--call-parent-start parent))))
+ (lambda (node parent &rest _)
+ (if (treesit-node-prev-sibling node t) 0 ,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 1)
+ ((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--call-parent-start 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$")
+ (lambda (_node parent &rest _)
+ (elixir-ts--call-parent-start 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--call-parent-start (node)
+ "Return the closest parent of NODE that is of type call."
+ (let ((call-parent
+ (or (treesit-parent-until
+ node
+ (lambda (n)
+ (equal (treesit-node-type n) "call")))
+ node)))
+ (save-excursion
+ (goto-char (treesit-node-start call-parent))
+ (back-to-indentation)
+ ;; For pipes we ignore the call indentation.
+ (if (looking-at "|>")
+ (point)
+ (treesit-node-start call-parent)))))
+
+(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
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..e2c97a787b5
--- /dev/null
+++ b/test/lisp/progmodes/elixir-ts-mode-resources/indent.erts
@@ -0,0 +1,233 @@
+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: 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: 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"
+=-=-=
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 @@
+;;; 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
--
2.39.2
next prev parent reply other threads:[~2023-03-12 9:54 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2023-03-06 7:04 bug#61996: 30.0.50; Submitting elixir-ts-mode and heex-ts-mode Wilhelm Kirschbaum
2023-03-06 11:59 ` Eli Zaretskii
2023-03-06 17:23 ` Wilhelm Kirschbaum
2023-03-06 18:36 ` Eli Zaretskii
2023-03-06 19:24 ` Wilhelm Kirschbaum
2023-03-06 20:14 ` Eli Zaretskii
2023-03-11 9:16 ` Eli Zaretskii
2023-03-11 14:16 ` Dmitry Gutov
2023-03-11 18:27 ` Wilhelm Kirschbaum
2023-03-11 18:01 ` Wilhelm Kirschbaum
2023-03-12 9:00 ` Eli Zaretskii
2023-03-12 9:54 ` Wilhelm Kirschbaum [this message]
2023-03-12 11:37 ` Eli Zaretskii
2023-03-12 12:23 ` Wilhelm Kirschbaum
2023-03-12 12:32 ` Wilhelm Kirschbaum
2023-03-12 15:14 ` Wilhelm Kirschbaum
2023-03-12 15:46 ` Eli Zaretskii
2023-03-12 18:02 ` Wilhelm Kirschbaum
2023-03-06 16:41 ` Dmitry Gutov
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87sfeas30x.fsf@gmail.com \
--to=wkirschbaum@gmail.com \
--cc=61996@debbugs.gnu.org \
--cc=casouri@gmail.com \
--cc=eliz@gnu.org \
--cc=theo@thornhill.no \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).