unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
From: Daniel Mendler <mail@daniel-mendler.de>
To: Juri Linkov <juri@linkov.net>
Cc: Gregory Heytings <gregory@heytings.org>,
	Dmitry Gutov <dgutov@yandex.ru>,
	Stefan Monnier <monnier@iro.umontreal.ca>,
	"emacs-devel@gnu.org" <emacs-devel@gnu.org>
Subject: Re: [PATCH] `completing-read`: Add `group-function` support to completion metadata (REVISED PATCH VERSION 2)
Date: Fri, 30 Apr 2021 01:55:30 +0200	[thread overview]
Message-ID: <69fd42ed-a1a0-adcb-ac8b-caad80cb0967@daniel-mendler.de> (raw)
In-Reply-To: <878s5090e9.fsf@mail.linkov.net>

[-- Attachment #1: Type: text/plain, Size: 1158 bytes --]

On 4/30/21 12:54 AM, Juri Linkov wrote:
> Then there are only minor details remaining:
> 
> 1. for read-char-by-name, the first candidate of a group
>    is displayed at the end of the same line with the group title
>    (perhaps easy to fix);

I fixed this. There was a missing "\n" after the group titles.

> 2. it would be nice to support vertical/horizontal formats
>    inside every group, not only one-column.

Yes, this should be added. The current patch contains a TODO above the
`completion--insert-strings` function. I intend to rework the
`completion--insert-strings` function, splitting it into three
functions, one for each format. This should make this code easier to
maintain. Then I will also add support for group titles. I will
implement a second patch, which implements these changes.

I attached the current version of the patch (REVISED PATCH VERSION 2).
In comparison to the previous "REVISED PATCH" I made minor
cleanups and changes and fixed the "\n" issue you noticed. There is the
question if the `completions-detailed` variable should be reused to also
guard the grouping (See the NOTE in the commit message of the patch).

Daniel

[-- Attachment #2: 0001-completing-read-Add-group-function-to-completion-met.patch --]
[-- Type: text/x-diff, Size: 18849 bytes --]

From 86caf835bf491660e3d29058b94a7fd52fbe91f4 Mon Sep 17 00:00:00 2001
From: Daniel Mendler <mail@daniel-mendler.de>
Date: Sun, 25 Apr 2021 13:07:29 +0200
Subject: [PATCH] (completing-read): Add `group-function` to completion
 metadata

(NOTE: There is also the guard variable `completions-detailed`. This
variable is used to guard a *single usage* of the
`affixation-function`. This variable could be generalized to guard
both affixations and grouping.  Instead of checking the variable
invidually in each completion table, the check could be performed in
minibuffer.el)

A completion table can specify a `group-function` in its metadata.
The group function takes two arguments, a completion candidate and a
transform argument. The group function is used to group the candidates
after sorting and to enhance the completion UI with group titles.

If the transform argument is nil, the function must return the title
of the group to which the completion candidate belongs. The function
may also return nil in case the candidate does not belong to a group.

Otherwise the function must return the transformed candidate. The
transformation allows for example to remove a part of the candidate,
which is then displayed in the title.

The grouping functionality guarded by the variable `completions-group`
and turned off by default for the *Completions* buffer.

The specific form of the `group-function` has been chosen in order to
allow allocation-free grouping. This is important for completion UIs,
which continously update the displayed set of candidates (Icomplete,
Vertico, Ivy, etc.). Only when the transform argument is non-nil the
candidate transformation is performed, which may involve a string
allocation as for example in `xref--completing-read-group`.

The function `xref-show-definitions-completing-read` makes use of the
`group-function`, by moving the file name prefix to the title. If
grouping is enabled, the *Completions* are displayed as
"linenum:summary" instead of "file:linenum:summary". This way the
*Completions* buffer resembles the *Occur* buffer.

* doc/lispref/minibuf.texi: Add documentation.

* lisp/minibuffer.el (completion-metadata): Describe `group-function`
in the docstring.
(completions-group): Add guard variable, by default off.
(completions-group-format): Add format string for group titles.
(completions-group-title): Add face for group titles.
(completions-group-separator): Add face for group separator.
(minibuffer--group-by): New grouping function.
(minibuffer-completion-help): Use it.
(display-completion-list): Add optional GROUP-FUN argument.
(completion--insert-strings): Add optional GROUP-FUN argument. Insert
group titles if `completions-format` is `one-column`. Transform each
candidate with the GROUP-FUN. Attach the untransformed candidate to
the property `completion--string`.

* lisp/simple.el (choose-completion): Retrieve the untransformed
completion candidate from the property `completion--string`.

* lisp/progmodes/xref.el:
(xref--completing-read-group): New grouping function.
(xref-show-definitions-completing-read): Use it.
---
 doc/lispref/minibuf.texi |  10 ++++
 lisp/minibuffer.el       | 123 ++++++++++++++++++++++++++++++++-------
 lisp/progmodes/xref.el   |  18 ++++--
 lisp/simple.el           |  11 ++--
 4 files changed, 131 insertions(+), 31 deletions(-)

diff --git a/doc/lispref/minibuf.texi b/doc/lispref/minibuf.texi
index bc8868b58d..ca01c418ad 100644
--- a/doc/lispref/minibuf.texi
+++ b/doc/lispref/minibuf.texi
@@ -1943,6 +1943,16 @@ Programmed Completion
 a suffix displayed after the completion string.  This function
 takes priority over @code{annotation-function}.
 
+@item group-function
+The value should be a function for grouping the completion candidates.
+The function must take two arguments, @var{completion}, which is a
+completion candidate and @var{transform}, which is a boolean flag.  If
+@var{transform} is @code{nil}, the function must return a group title,
+to which the candidate belongs. The returned title can also
+@code{nil}.  Otherwise the function must return the transformed
+candidate. The transformation can for example remove a redundant
+prefix, which is displayed in the group title instead.
+
 @item display-sort-function
 The value should be a function for sorting completions.  The function
 should take one argument, a list of completion strings, and return a
diff --git a/lisp/minibuffer.el b/lisp/minibuffer.el
index 2400624953..c1f6a7d64e 100644
--- a/lisp/minibuffer.el
+++ b/lisp/minibuffer.el
@@ -126,6 +126,13 @@ completion-metadata
    three-element lists: completion, its prefix and suffix.  This
    function takes priority over `annotation-function' when both are
    provided, so only this function is used.
+- `group-function': function for grouping the completion candidates.
+   Takes two arguments: a completion candidate (COMPLETION) and a
+   boolean flag (TRANSFORM).  If TRANSFORM is nil, the function
+   returns a group title, to which the candidate belongs.  The
+   returned title may be nil.  Otherwise the function returns the
+   transformed candidate.  The transformation can remove a redundant
+   prefix, which is displayed in the group title instead.
 - `display-sort-function': function to sort entries in *Completions*.
    Takes one argument (COMPLETIONS) and should return a new list
    of completions.  Can operate destructively.
@@ -1138,6 +1145,30 @@ completion-cycle-threshold
   :version "24.1"
   :type completion--cycling-threshold-type)
 
+(defcustom completions-group nil
+  "Enable grouping of completion candidates in the *Completions* buffer.
+See also `completions-group-format'."
+  :type 'boolean
+  :version "28.1")
+
+(defcustom completions-group-format
+  (concat
+   (propertize "    " 'face 'completions-group-separator)
+   (propertize " %s " 'face 'completions-group-title)
+   (propertize " " 'face 'completions-group-separator
+               'display '(space :align-to right)))
+  "Format string used for the group title."
+  :type 'string
+  :version "28.1")
+
+(defface completions-group-title '((t :inherit shadow :slant italic))
+  "Face used for the title text of the candidate group headlines."
+  :version "28.1")
+
+(defface completions-group-separator '((t :inherit shadow :strike-through t))
+  "Face used for the separator lines of the candidate groups."
+  :version "28.1")
+
 (defun completion--cycle-threshold (metadata)
   (let* ((cat (completion-metadata-get metadata 'category))
          (over (completion--category-override cat 'cycle)))
@@ -1401,6 +1432,17 @@ minibuffer--sort-preprocess-history
                      (substring c base-size)))
                  hist)))))
 
+(defun minibuffer--group-by (fun elems)
+  "Group ELEMS by FUN."
+  (let ((groups))
+    (dolist (cand elems)
+      (let* ((key (funcall fun cand nil))
+             (group (assoc key groups)))
+        (if group
+            (setcdr group (cons cand (cdr group)))
+          (push (list key cand) groups))))
+    (mapcan (lambda (x) (nreverse (cdr x))) (nreverse groups))))
+
 (defun completion-all-sorted-completions (&optional start end)
   (or completion-all-sorted-completions
       (let* ((start (or start (minibuffer-prompt-end)))
@@ -1747,11 +1789,17 @@ completions-detailed
   :type 'boolean
   :version "28.1")
 
-(defun completion--insert-strings (strings)
+;; TODO: Split up this function in one function per `completions-format'.
+;; TODO: Add group title support for horizontal and vertical format.
+(defun completion--insert-strings (strings &optional group-fun)
   "Insert a list of STRINGS into the current buffer.
-Uses columns to keep the listing readable but compact.
-It also eliminates runs of equal strings."
+Uses columns to keep the listing readable but compact.  It also
+eliminates runs of equal strings.  GROUP-FUN is a `group-function'
+used for grouping the completion."
   (when (consp strings)
+    ;; FIXME: Currently grouping is enabled only for the 'one-column format.
+    (unless (eq completions-format 'one-column)
+      (setq group-fun nil))
     (let* ((length (apply #'max
 			  (mapcar (lambda (s)
 				    (if (consp s)
@@ -1768,6 +1816,7 @@ completion--insert-strings
 		     (max 1 (/ (length strings) 2))))
 	   (colwidth (/ wwidth columns))
            (column 0)
+           (last-title nil)
 	   (rows (/ (length strings) columns))
 	   (row 0)
            (first t)
@@ -1780,6 +1829,13 @@ completion--insert-strings
       ;; The insertion should be "sensible" no matter what choices were made
       ;; for the parameters above.
       (dolist (str strings)
+        ;; Add group titles.
+        (when group-fun
+          (let ((title (funcall group-fun (if (consp str) (car str) str) nil)))
+            (unless (equal title last-title)
+              (when title
+                (insert (format completions-group-format title) "\n"))
+              (setq last-title title))))
 	(unless (equal laststring str) ; Remove (consecutive) duplicates.
 	  (setq laststring str)
           ;; FIXME: `string-width' doesn't pay attention to
@@ -1825,8 +1881,15 @@ completion--insert-strings
 		  nil))))
             (setq first nil)
             (if (not (consp str))
-                (put-text-property (point) (progn (insert str) (point))
-                                   'mouse-face 'highlight)
+                (add-text-properties
+                 (point)
+                 (progn
+                   (insert
+                    (if group-fun
+                        (funcall group-fun str 'transform)
+                      str))
+                   (point))
+                 `(mouse-face highlight completion--string ,str))
               ;; If `str' is a list that has 2 elements,
               ;; then the second element is a suffix annotation.
               ;; If `str' has 3 elements, then the second element
@@ -1837,8 +1900,15 @@ completion--insert-strings
                   (let ((beg (point))
                         (end (progn (insert prefix) (point))))
                     (put-text-property beg end 'mouse-face nil)))
-                (put-text-property (point) (progn (insert (car str)) (point))
-                                   'mouse-face 'highlight)
+                (add-text-properties
+                 (point)
+                 (progn
+                   (insert
+                    (if group-fun
+                         (funcall group-fun (car str) 'transform)
+                      (car str)))
+                   (point))
+                 `(mouse-face highlight completion--string ,(car str)))
                 (let ((beg (point))
                       (end (progn (insert suffix) (point))))
                   (put-text-property beg end 'mouse-face nil)
@@ -1923,7 +1993,7 @@ completion-hilit-commonality
         completions)
        base-size))))
 
-(defun display-completion-list (completions &optional common-substring)
+(defun display-completion-list (completions &optional common-substring group-fun)
   "Display the list of completions, COMPLETIONS, using `standard-output'.
 Each element may be just a symbol or string
 or may be a list of two strings to be printed as if concatenated.
@@ -1933,7 +2003,8 @@ display-completion-list
 The actual completion alternatives, as inserted, are given `mouse-face'
 properties of `highlight'.
 At the end, this runs the normal hook `completion-setup-hook'.
-It can find the completion buffer in `standard-output'."
+It can find the completion buffer in `standard-output'.
+GROUP-FUN is a `group-function' used for grouping the completion."
   (declare (advertised-calling-convention (completions) "24.4"))
   (if common-substring
       (setq completions (completion-hilit-commonality
@@ -1946,7 +2017,7 @@ display-completion-list
 	(let ((standard-output (current-buffer))
 	      (completion-setup-hook nil))
           (with-suppressed-warnings ((callargs display-completion-list))
-	    (display-completion-list completions common-substring)))
+	    (display-completion-list completions common-substring group-fun)))
 	(princ (buffer-string)))
 
     (with-current-buffer standard-output
@@ -1954,7 +2025,7 @@ display-completion-list
       (if (null completions)
           (insert "There are no possible completions of what you have typed.")
         (insert "Possible completions are:\n")
-        (completion--insert-strings completions))))
+        (completion--insert-strings completions group-fun))))
 
   (run-hooks 'completion-setup-hook)
   nil)
@@ -2067,6 +2138,9 @@ minibuffer-completion-help
              (aff-fun (or (completion-metadata-get all-md 'affixation-function)
                           (plist-get completion-extra-properties
                                      :affixation-function)))
+             (sort-fun (completion-metadata-get all-md 'display-sort-function))
+             (group-fun (and completions-group
+                             (completion-metadata-get all-md 'group-function)))
              (mainbuf (current-buffer))
              ;; If the *Completions* buffer is shown in a new
              ;; window, mark it as softly-dedicated, so bury-buffer in
@@ -2098,15 +2172,22 @@ minibuffer-completion-help
                       ;; Remove the base-size tail because `sort' requires a properly
                       ;; nil-terminated list.
                       (when last (setcdr last nil))
-                      (setq completions
-                            ;; FIXME: This function is for the output of all-completions,
-                            ;; not completion-all-completions.  Often it's the same, but
-                            ;; not always.
-                            (let ((sort-fun (completion-metadata-get
-                                             all-md 'display-sort-function)))
-                              (if sort-fun
-                                  (funcall sort-fun completions)
-                                (sort completions 'string-lessp))))
+
+                      ;; Sort first using the `display-sort-function'.
+                      ;; FIXME: This function is for the output of
+                      ;; all-completions, not
+                      ;; completion-all-completions.  Often it's the
+                      ;; same, but not always.
+                      (setq completions (if sort-fun
+                                            (funcall sort-fun completions)
+                                          (sort completions 'string-lessp)))
+
+                      ;; After sorting, group the candidates using the
+                      ;; `group-function'.
+                      (when group-fun
+                        (setq completions
+                              (minibuffer--group-by group-fun completions)))
+
                       (cond
                        (aff-fun
                         (setq completions
@@ -2152,7 +2233,7 @@ minibuffer-completion-help
                                                      (if (eq (car bounds) (length result))
                                                          'exact 'finished)))))))
 
-                      (display-completion-list completions)))))
+                      (display-completion-list completions nil group-fun)))))
           nil)))
     nil))
 
diff --git a/lisp/progmodes/xref.el b/lisp/progmodes/xref.el
index 7fc7181acc..2a4fb2c417 100644
--- a/lisp/progmodes/xref.el
+++ b/lisp/progmodes/xref.el
@@ -1044,6 +1044,12 @@ xref-show-definitions-buffer-at-bottom
 (define-obsolete-function-alias 'xref--show-defs-buffer-at-bottom
   #'xref-show-definitions-buffer-at-bottom "28.1")
 
+(defun xref--completing-read-group (cand transform)
+  "Return group title of candidate CAND or TRANSFORM the candidate."
+  (if transform
+      (substring cand (1+ (next-single-property-change 0 'xref--group cand)))
+    (get-text-property 0 'xref--group cand)))
+
 (defun xref-show-definitions-completing-read (fetcher alist)
   "Let the user choose the target definition with completion.
 
@@ -1072,10 +1078,12 @@ xref-show-definitions-completing-read
                                     (format #("%d:" 0 2 (face xref-line-number))
                                             line)
                                   ""))
+                               (group-prefix
+                                (substring group group-prefix-length))
                                (group-fmt
-                                (propertize
-                                 (substring group group-prefix-length)
-                                 'face 'xref-file-header))
+                                (propertize group-prefix
+                                            'face 'xref-file-header
+                                            'xref--group group-prefix))
                                (candidate
                                 (format "%s:%s%s" group-fmt line-fmt summary)))
                           (push (cons candidate xref) xref-alist-with-line-info)))))
@@ -1087,7 +1095,9 @@ xref-show-definitions-completing-read
                          (lambda (string pred action)
                            (cond
                             ((eq action 'metadata)
-                             '(metadata . ((category . xref-location))))
+                             `(metadata
+                               . ((category . xref-location)
+                                  (group-function . ,#'xref--completing-read-group))))
                             (t
                              (complete-with-action action collection string pred)))))
                         (def (caar collection)))
diff --git a/lisp/simple.el b/lisp/simple.el
index 26eb8cad7f..ad36ad5a3e 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -8867,18 +8867,17 @@ choose-completion
           (choice
            (save-excursion
              (goto-char (posn-point (event-start event)))
-             (let (beg end)
+             (let (beg)
                (cond
                 ((and (not (eobp)) (get-text-property (point) 'mouse-face))
-                 (setq end (point) beg (1+ (point))))
+                 (setq beg (1+ (point))))
                 ((and (not (bobp))
                       (get-text-property (1- (point)) 'mouse-face))
-                 (setq end (1- (point)) beg (point)))
+                 (setq beg (point)))
                 (t (error "No completion here")))
                (setq beg (previous-single-property-change beg 'mouse-face))
-               (setq end (or (next-single-property-change end 'mouse-face)
-                             (point-max)))
-               (buffer-substring-no-properties beg end)))))
+               (substring-no-properties
+                (get-text-property beg 'completion--string))))))
 
       (unless (buffer-live-p buffer)
         (error "Destination buffer is dead"))
-- 
2.20.1


  reply	other threads:[~2021-04-29 23:55 UTC|newest]

Thread overview: 81+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-04-25 13:32 [PATCH] `completing-read`: Add `group-function` support to completion metadata Daniel Mendler
2021-04-25 19:35 ` Dmitry Gutov
2021-04-25 19:47   ` Daniel Mendler
2021-04-25 21:50     ` Dmitry Gutov
2021-04-25 22:10       ` Daniel Mendler
2021-04-25 22:40         ` Dmitry Gutov
2021-04-25 22:58           ` Daniel Mendler
2021-04-26  4:51             ` Protesilaos Stavrou
2021-04-27 16:53               ` Juri Linkov
2021-04-28  6:18                 ` Protesilaos Stavrou
2021-04-25 23:33           ` Stefan Monnier
2021-04-26 10:01             ` Daniel Mendler
2021-04-26 13:50               ` Stefan Monnier
2021-04-27  1:46             ` Dmitry Gutov
2021-04-27  1:59               ` tumashu
2021-04-27  2:45                 ` Daniel Mendler
2021-04-27 15:47                 ` Dmitry Gutov
2021-04-27  3:41               ` Stefan Monnier
2021-04-28  0:08                 ` Dmitry Gutov
2021-04-28  3:21                   ` Stefan Monnier
2021-04-25 19:38 ` [PATCH] `completing-read`: Add `group-function` support to completion metadata (REVISED PATCH) Daniel Mendler
2021-04-25 20:45   ` Juri Linkov
2021-04-25 21:26     ` Daniel Mendler
2021-04-29 16:20   ` Juri Linkov
2021-04-29 16:52     ` Daniel Mendler
2021-04-29 17:07     ` Stefan Monnier
2021-04-29 17:13       ` Daniel Mendler
2021-04-29 22:54         ` Juri Linkov
2021-04-29 23:55           ` Daniel Mendler [this message]
2021-04-30  9:00             ` [PATCH] `completing-read`: Add `group-function` support to completion metadata (REVISED PATCH VERSION 2) Daniel Mendler
2021-04-30 17:01               ` Juri Linkov
2021-04-30 18:11                 ` Daniel Mendler
2021-04-30 18:30                   ` Daniel Mendler
2021-05-01 19:57                     ` Juri Linkov
2021-05-02  0:43                       ` Daniel Mendler
2021-05-02  7:07                         ` Eli Zaretskii
2021-05-02 11:01                           ` Daniel Mendler
2021-04-30 16:51             ` Juri Linkov
2021-04-30 18:13               ` Daniel Mendler
2021-05-01 19:54                 ` Juri Linkov
2021-05-02  0:32                   ` Daniel Mendler
2021-05-02 21:38                     ` Juri Linkov
2021-05-07 17:03                       ` Juri Linkov
2021-05-07 17:55                         ` Daniel Mendler
2021-05-08  6:24                           ` Daniel Mendler
2021-05-08  8:45                             ` [PATCH] `completing-read`: Add `group-function` support to completion metadata (REVISED PATCH VERSION 4) Daniel Mendler
2021-05-08  9:10                               ` Daniel Mendler
2021-05-09 17:59                                 ` Juri Linkov
2021-05-09 18:50                                   ` Daniel Mendler
2021-05-09 18:56                                     ` Stefan Monnier
2021-05-09 19:11                                       ` Daniel Mendler
2021-05-10 20:47                                     ` Juri Linkov
2021-05-11  7:51                                       ` [PATCH] `completing-read`: Add `group-function` support to completion metadata (REVISED PATCH VERSION 5) Daniel Mendler
2021-05-11 17:59                                         ` Juri Linkov
2021-05-08 13:15                         ` [PATCH] `completing-read`: Add `group-function` support to completion metadata (REVISED PATCH VERSION 2) Stefan Monnier
2021-05-09 18:05                           ` Juri Linkov
2021-05-09 18:37                             ` Eli Zaretskii
2021-05-11 18:06                               ` Juri Linkov
2021-05-11 18:44                                 ` Eli Zaretskii
2021-05-11 18:58                                   ` Daniel Mendler
2021-05-11 19:22                                     ` Eli Zaretskii
2021-05-11 19:46                                       ` Daniel Mendler
2021-05-11 19:59                                         ` Eli Zaretskii
2021-05-11 20:30                                           ` [PATCH] `completing-read`: Add `group-function` support to completion metadata (REVISED PATCH VERSION 6) Daniel Mendler
2021-05-13 10:32                                             ` Eli Zaretskii
2021-05-13 11:45                                               ` [PATCH] `completing-read`: Add `group-function` support to completion metadata (REVISED PATCH VERSION 7) Daniel Mendler
2021-05-20  9:39                                                 ` Daniel Mendler
2021-05-20 17:53                                                   ` Juri Linkov
2021-05-20 18:51                                                     ` Daniel Mendler
2021-04-29 17:09     ` [PATCH] `completing-read`: Add `group-function` support to completion metadata (REVISED PATCH) Dmitry Gutov
2021-04-29 17:16       ` Daniel Mendler
2021-04-29 17:55         ` Dmitry Gutov
2021-04-29 18:31           ` [External] : " Drew Adams
2021-04-29 20:25             ` Dmitry Gutov
2021-04-29 22:15               ` Drew Adams
2021-04-29 22:28                 ` Dmitry Gutov
2021-04-29 23:31                   ` Drew Adams
2021-04-29 19:21           ` Daniel Mendler
2021-05-02 14:29   ` [PATCH] `completing-read`: Add `group-function` support to completion metadata (REVISED PATCH VERSION 3) Daniel Mendler
2021-05-02 21:49     ` Juri Linkov
2021-05-03 14:40       ` Daniel Mendler

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=69fd42ed-a1a0-adcb-ac8b-caad80cb0967@daniel-mendler.de \
    --to=mail@daniel-mendler.de \
    --cc=dgutov@yandex.ru \
    --cc=emacs-devel@gnu.org \
    --cc=gregory@heytings.org \
    --cc=juri@linkov.net \
    --cc=monnier@iro.umontreal.ca \
    /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).