emacs-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Thoughts on Refactoring In-Buffer Completion In message.el


From: Alexander Adolf
Subject: Re: Thoughts on Refactoring In-Buffer Completion In message.el
Date: Wed, 20 Jul 2022 22:59:56 +0200

Hello Stefan,

Many thanks for your swift response, and helpful comments!

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> [...]
> `group` is too generic a name (remember that those category names are
> "global" so they should be meaning in any other context than
> message.el).
> `newsgroup` maybe?

Good point; 'newsgroup' it is.

>> +:completions
>> +
>> +    The function that provides completions, and that obeys the
>> +    same rules as those of `completion-at-point-functions'.
>> +    In-buffer completion will be performed as if
>> +    `completion-at-point-functions' had been set to this value."
>
> I think this should be a completion table, not a CAPF function.

Why restrict it to a table? Perhaps we should allow both, functions and
tables? Practically, that could mean checking whether the value
satisfies `functionp`, and `funcall` it when that's the case; else use
it as a ready-made table.

It seems I am missing something?

>> +          (_
>> +           (let* ((recipe (alist-get message-email-recipient-header-regexp
>> +                                     message-completion-alist))
>> +                  (completions-function (plist-get recipe :completions)))
>> +             (funcall completions-function))))))))
>
> Hmm... `recipe` should be (car alist), rather than this
> weird (alist-get ...), no?

I confused myself (and apparently you, too). `recipe` is one and the
same as `fun`; no need for an extra variable.

> And then we should do the (skip-chars-forw/backward "^, \t\n") dance
> here, 

Added in the updated patch at the end of this message.

> as well as the metadata dance to add the `category` if specified by
> `recipe`.

Good point. I amended `message-completion-function` to add a metadata
property with category information.

> [...]
> Tho, now that I think about it, having those styles in
> `message-completion-alist` is weird: that var is a `defcustom`, hence
> a user setting, yet we put it into `completion-category-defaults` which
> is not meant to contain user settings (that's what
> `completion-category-overrides` is for).
>
> So maybe we should just hardcode
>
>     (add-to-list 'completion-category-defaults
>                  '(newsgroup (styles substring partial-completion))))
>     (add-to-list 'completion-category-defaults
>                  '(email (styles substring partial-completion))))
>
> and remove the `:styles` from `message-completion-alist` since the user
> should set `completion-category-overrides` instead.

I agree. I hadn't viewed completion-category-defaults as the global
setting it actually is.

Below is the updated patch.

I have made minimally invasive modifications only to
message-expand-name, and message-expand-group. Frankly, my goal is to
not have a message-expand-name at all, but to call some eudc-capf-*
function directly for email addresses.

I also have not added any checking whether individual properties are
present in the plist, or not. What would be the use-case for not
specifying any of the three?


Looking forward to your thoughts,

  --alexander

>From 87a6778db682395f61b87b629c9553ff90059902 Mon Sep 17 00:00:00 2001
From: Alexander Adolf <alexander.adolf@condition-alpha.com>
Date: Tue, 19 Jul 2022 22:31:58 +0200
Subject: [PATCH] Refactoring Message-Completion-Alist

* lisp/gnus/message.el (message-completion-alist): alist cdr replaced
by plist
(message-completion-function): handle new plist cdr type in
message-completion-alist, add completion category metadata from
message-completion-alist instead of hard coded values (FIXME), use
regex from message-completion-alist to determine prefix
(message-completion-alist-set-completions): new function to help in
writing user init files
(message-expand-group): new optional parameters to receive bounds from
message-completion-function
(message-expand-name): new optional parameters to receive bounds from
message-completion-function
---
 lisp/gnus/message.el | 180 +++++++++++++++++++++++++++++++------------
 1 file changed, 130 insertions(+), 50 deletions(-)

diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el
index 7c2b24c6ee..da63e3441d 100644
--- a/lisp/gnus/message.el
+++ b/lisp/gnus/message.el
@@ -3180,7 +3180,6 @@ message-mode
     (mail-abbrevs-setup))
    ((message-mail-alias-type-p 'ecomplete)
     (ecomplete-setup)))
-  (add-hook 'completion-at-point-functions #'eudc-capf-complete -1 t)
   (add-hook 'completion-at-point-functions #'message-completion-function nil t)
   (unless buffer-file-name
     (message-set-auto-save-file-name))
@@ -8244,14 +8243,68 @@ message-email-recipient-header-regexp
   :type 'regexp)
 
 (defcustom message-completion-alist
-  `((,message-newgroups-header-regexp . ,#'message-expand-group)
-    (,message-email-recipient-header-regexp . ,#'message-expand-name))
-  "Alist of (RE . FUN).  Use FUN for completion on header lines matching RE.
-FUN should be a function that obeys the same rules as those
-of `completion-at-point-functions'."
-  :version "27.1"
+  `((,message-newgroups-header-regexp . (:category newsgroup
+                                         :fieldsep-re "\\([:,]\\|^\\)[ \t]*"
+                                         :completions ,#'message-expand-group))
+    (,message-email-recipient-header-regexp . (:category email
+                                               :fieldsep-re "\\([:,]\\|^\\)[ 
\t]*"
+                                               :completions 
,#'message-expand-name)))
+  "Alist of (RE . RECIPE), defining completion contexts.
+This variable controls how `message-completion-function' performs
+in-buffer completion.  RECIPE is either a function (deprecated),
+or a plist.
+
+When `message-completion-function' is invoked, and point is on a
+line matching one of the REs in the alist, the settings in the
+corresponding RECIPE are applied.
+
+When RECIPE is a function, it is called for completion.  RECIPE
+should be a function that obeys the same rules as those of
+`completion-at-point-functions'.
+
+When RECIPE is a plist, the stored properties are used to control
+how in-buffer completion is performed.  The following properties
+are currently defined:
+
+:category
+
+    The symbol defining the category in
+    `completion-category-defaults' to use for completion.  Also
+    see `completion-category-overrides', and `completion-styles'.
+
+:fieldsep-re
+
+    The regular expression to use when scanning backwards in the
+    buffer.  All text between point, and any preceding text
+    matching this regular expression, will be used as the prefix
+    for finding completion candidates.
+
+:completions
+
+    The function that provides completions, and that obeys the
+    same rules as those of `completion-at-point-functions'.
+    In-buffer completion will be performed as if
+    `completion-at-point-functions' had been set to this value."
+  :version "29.1"
   :group 'message
-  :type '(alist :key-type regexp :value-type function))
+  :type '(alist :key-type regexp
+                :value-type (choice (plist)
+                                    (function
+                                     :tag "Completion function 
(deprecated)"))))
+
+(defun message-completion-alist-set-completions (cat compl)
+  "Set the completion function for category CAT to COMPL.
+Modifies the value of `message-completion-alist'.  This is a
+convenience function for use in init files."
+  (let ((elt (seq-find (lambda (x)
+                         (eq cat (plist-get (cdr x) :category)))
+                       message-completion-alist)))
+    (when elt
+      (setq message-completion-alist
+            (assoc-delete-all (car elt) message-completion-alist))
+      (push (cons (car elt) (plist-put (cdr elt) :completions compl))
+            message-completion-alist)))
+  nil)
 
 (defcustom message-expand-name-databases
   '(bbdb eudc)
@@ -8291,6 +8344,13 @@ mail-abbrev-mode-regexp
 
 (defvar message--old-style-completion-functions nil)
 
+;; set completion category defaults for categories defined by
+;; message mode
+(add-to-list 'completion-category-defaults
+            '(newsgroup (styles substring partial-completion))))
+(add-to-list 'completion-category-defaults
+            '(email (styles substring partial-completion))))
+
 (defun message-completion-function ()
   (let ((alist message-completion-alist))
     (while (and alist
@@ -8298,43 +8358,59 @@ message-completion-function
                  (not (mail-abbrev-in-expansion-header-p))))
       (setq alist (cdr alist)))
     (when (cdar alist)
-      (let ((fun (cdar alist)))
-        (if (member fun message--old-style-completion-functions)
-            (lambda ()
-              (funcall fun)
-              ;; Even if completion fails, return a non-nil value, so as to
-              ;; avoid falling back to message-tab-body-function.
-              'completion-attempted)
-          (let ((ticks-before (buffer-chars-modified-tick))
-                (data (funcall fun)))
-            (if (and (eq ticks-before (buffer-chars-modified-tick))
-                     (or (null data)
-                         (integerp (car-safe data))))
-                data
-              (push fun message--old-style-completion-functions)
-              ;; Completion was already performed, so just return a dummy
-              ;; function that prevents trying any further.
-              (lambda () 'completion-attempted))))))))
-
-(defun message-expand-group ()
+      (let ((recipe (cdar alist)))
+        (pcase recipe
+          ((pred functionp)
+           (if (member recipe message--old-style-completion-functions)
+               (lambda ()
+                 (funcall recipe)
+                 ;; Even if completion fails, return a non-nil value, so as to
+                 ;; avoid falling back to message-tab-body-function.
+                 'completion-attempted)
+             (let ((ticks-before (buffer-chars-modified-tick))
+                   (data (funcall recipe)))
+               (if (and (eq ticks-before (buffer-chars-modified-tick))
+                        (or (null data)
+                            (integerp (car-safe data))))
+                   data
+                 (push recipe message--old-style-completion-functions)
+                 ;; Completion was already performed, so just return a dummy
+                 ;; function that prevents trying any further.
+                 (lambda () 'completion-attempted)))))
+          (_
+           (let* ((completions (plist-get recipe :completions))
+                  (beg (save-excursion
+                         (re-search-backward (plist-get recipe :fieldsep-re))
+                         (match-end 0)))
+                  (end (point))
+                  (cat (plist-get recipe :category))
+                  (completion-table (if (functionp completions)
+                                        (funcall completions beg end)
+                                      completions)))
+             ;; TODO: Should we check whether completion-table has
+             ;;       category metadata already, and add it when
+             ;;       missing only?
+             (setq completion-table
+                   (cons completion-table
+                         `(metadata ((category . ,cat))))))))))))
+
+(defun message-expand-group (&optional pfx-beg pfx-end)
   "Expand the group name under point."
-  (let ((b (save-excursion
-            (save-restriction
-              (narrow-to-region
-               (save-excursion
-                 (beginning-of-line)
-                 (skip-chars-forward "^:")
-                 (1+ (point)))
-               (point))
-              (skip-chars-backward "^, \t\n") (point))))
+  (let ((b (or pfx-beg (save-excursion
+                        (save-restriction
+                          (narrow-to-region
+                           (save-excursion
+                             (beginning-of-line)
+                             (skip-chars-forward "^:")
+                             (1+ (point)))
+                           (point))
+                          (skip-chars-backward "^, \t\n") (point)))))
        (completion-ignore-case t)
-       (e (progn (skip-chars-forward "^,\t\n ") (point)))
+       (e (or pfx-end (progn (skip-chars-forward "^,\t\n ") (point))))
        (collection (when (and (boundp 'gnus-active-hashtb)
                               gnus-active-hashtb)
                      (hash-table-keys gnus-active-hashtb))))
     (when collection
-      ;; FIXME: Add `category' metadata to the collection, so we can use
-      ;; substring matching on it.
       (list b e collection))))
 
 (defcustom message-expand-name-standard-ui nil
@@ -8347,14 +8423,16 @@ message-expand-name-standard-ui
   :version "27.1"
   :type 'boolean)
 
-(defun message-expand-name ()
+(defun message-expand-name (&optional pfx-beg pfx-end)
   (cond (message-expand-name-standard-ui
-        (let ((beg (save-excursion
-                      (skip-chars-backward "^\n:,") (skip-chars-forward " \t")
-                      (point)))
-               (end (save-excursion
-                      (skip-chars-forward "^\n,") (skip-chars-backward " \t")
-                      (point))))
+        (let ((beg (or pfx-beg (save-excursion
+                                  (skip-chars-backward "^\n:,")
+                                  (skip-chars-forward " \t")
+                                  (point))))
+               (end (or pfx-end (save-excursion
+                                  (skip-chars-forward "^\n,")
+                                  (skip-chars-backward " \t")
+                                  (point)))))
            (when (< beg end)
              (list beg end (message--name-table (buffer-substring beg end))))))
        ((and (memq 'eudc message-expand-name-databases)
@@ -8372,9 +8450,6 @@ message-expand-name
        (t
         (expand-abbrev))))
 
-(add-to-list 'completion-category-defaults '(email (styles substring
-                                                           
partial-completion)))
-
 (defun message--bbdb-query-with-words (words)
   ;; FIXME: This (or something like this) should live on the BBDB side.
   (when (fboundp 'bbdb-records)
@@ -8402,7 +8477,12 @@ message--name-table
         bbdb-responses)
     (lambda (string pred action)
       (pcase action
-        ('metadata '(metadata (category . email)))
+        ('metadata (let* ((recipe (alist-get 
message-email-recipient-header-regexp
+                                             message-completion-alist))
+                          (cat (plist-get recipe :category)))
+                     (pcase recipe
+                       ((pred functionp) '(metadata (category . email)))
+                       (_ (when cat `(metadata (category . ,cat)))))))
         ('lambda t)
         ((or 'nil 't)
          (when orig-words
-- 
2.37.0


reply via email to

[Prev in Thread] Current Thread [Next in Thread]