From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Alexander Adolf Newsgroups: gmane.emacs.devel Subject: Re: Thoughts on Refactoring In-Buffer Completion In message.el Date: Wed, 27 Jul 2022 23:16:48 +0200 Message-ID: References: <82e662dc46347e410c0b1d871e998b00@condition-alpha.com> <5b04a4a1f5a497f030397f6813551ad2@condition-alpha.com> Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=-=-=" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="40933"; mail-complaints-to="usenet@ciao.gmane.io" Cc: emacs-devel@gnu.org To: Stefan Monnier Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Wed Jul 27 23:19:00 2022 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1oGoQm-000ARF-8k for ged-emacs-devel@m.gmane-mx.org; Wed, 27 Jul 2022 23:19:00 +0200 Original-Received: from localhost ([::1]:41856 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1oGoQk-000339-Bt for ged-emacs-devel@m.gmane-mx.org; Wed, 27 Jul 2022 17:18:58 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:46066) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oGoOu-0001WZ-0H for emacs-devel@gnu.org; Wed, 27 Jul 2022 17:17:04 -0400 Original-Received: from smtprelay04.ispgateway.de ([80.67.18.16]:23798) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1oGoOo-0007lu-3g for emacs-devel@gnu.org; Wed, 27 Jul 2022 17:17:03 -0400 Original-Received: from [46.244.218.85] (helo=condition-alpha.com) by smtprelay04.ispgateway.de with esmtpsa (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1oGoP2-0006bP-Eq; Wed, 27 Jul 2022 23:17:13 +0200 In-Reply-To: X-Df-Sender: YWxleGFuZGVyLmFkb2xmQGNvbmRpdGlvbi1hbHBoYS5jb20= Received-SPF: pass client-ip=80.67.18.16; envelope-from=alexander.adolf@condition-alpha.com; helo=smtprelay04.ispgateway.de X-Spam_score_int: -18 X-Spam_score: -1.9 X-Spam_bar: - X-Spam_report: (-1.9 / 5.0 requ) BAYES_00=-1.9, RCVD_IN_DNSWL_NONE=-0.0001, RCVD_IN_MSPIKE_H3=0.001, RCVD_IN_MSPIKE_WL=0.001, SPF_HELO_PASS=-0.001, SPF_PASS=-0.001, T_FILL_THIS_FORM_SHORT=0.01, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:292754 Archived-At: --=-=-= Content-Type: text/plain Hello Stefan, I have updated the patch I sent before. It had two extra parentheses (now fixed), and the splicing of the metadata into the completion table was broke (now commented out). You could run this now if you wanted to. --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0001-Refactoring-Message-Completion-Alist.patch >From 68ee0fba5d939d1056f7803e886bda6b834bf316 Mon Sep 17 00:00:00 2001 From: Alexander Adolf Date: Tue, 19 Jul 2022 22:31:58 +0200 Subject: [PATCH 1/2] 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 | 183 +++++++++++++++++++++++++++++++------------ 1 file changed, 133 insertions(+), 50 deletions(-) diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el index 00a27fb5f5..07abab4396 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)) @@ -8243,14 +8242,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) @@ -8290,6 +8343,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 @@ -8297,43 +8357,62 @@ 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) + (setq 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))))) + ;; (message "completion-table = %s" completion-table) + completion-table))))))) + +(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 @@ -8346,14 +8425,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) @@ -8371,9 +8452,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) @@ -8401,7 +8479,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.1 --=-=-= Content-Type: text/plain The second patch builds on the first one, and is somewhat "heavier". It removes all ecomplete, and mailabbrev stuff, and all completion UI code. I have also removed message-expand-name, and message--name-table, and instead am calling out to EUDC. EUDC is enhanced by two new backends for ecomplete, and mailabbrev. Thus, in terms of functionality, end users should see no difference. To use the new EUDC back-ends you'll need to do one or both of: (require 'eudcb-ecomplete) (add-to-list 'eudc-server-hotlist '("localhost" . ecomplete)) (require 'eudcb-mailabbrev) (add-to-list 'eudc-server-hotlist '("localhost" . mailabbrev)) I have also added a new hook for email address snarfing, so that there is now generic mechanisms for this, too. Perhaps a new function for in ecomplete would be helpful, which can readily be added to the new snarfing hook. I'll have a look to that soon. --=-=-= Content-Type: text/x-patch Content-Disposition: inline; filename=0002-Use-completion-at-point-and-EUDC-for-email-address-c.patch >From 7042e888454a935a905df4126063f10e330ddfea Mon Sep 17 00:00:00 2001 From: Alexander Adolf Date: Mon, 25 Jul 2022 16:33:11 +0200 Subject: [PATCH 2/2] Use completion-at-point and EUDC for email address completion * lisp/gnus/message.el: remove all ecomplete, and mailabbrev related code; remove all completion UI code; use completion-at-point for email address completion only (message-completion-alist): make EUDC the default provider for email address completion candidates (message-mail-address-snarf-hook): new generic mechanism to capture email addresses into databases used by the user * lisp/net/eudc-capf.el (eudc-capf-message-expand-name): new optional parameters for prefix beginning and end * lisp/net/eudcb-ecomplete.el: new file; ecomplete back-end for EUDC * lisp/net/eudcb-mailabbrev.el: new file; mailabbrev back-end for EUDC --- lisp/gnus/message.el | 252 +++++++++-------------------------- lisp/net/eudc-capf.el | 15 +-- lisp/net/eudcb-ecomplete.el | 110 +++++++++++++++ lisp/net/eudcb-mailabbrev.el | 100 ++++++++++++++ 4 files changed, 278 insertions(+), 199 deletions(-) create mode 100644 lisp/net/eudcb-ecomplete.el create mode 100644 lisp/net/eudcb-mailabbrev.el diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el index 07abab4396..528053ef8a 100644 --- a/lisp/gnus/message.el +++ b/lisp/gnus/message.el @@ -51,6 +51,7 @@ (require 'yank-media) (require 'mailcap) (require 'sendmail) +(require 'eudc-capf) (autoload 'mailclient-send-it "mailclient") @@ -1385,26 +1386,13 @@ message-send-method-alist PREDICATE returns non-nil. FUNCTION is called with one parameter -- the prefix.") -(defcustom message-mail-alias-type 'abbrev - "What alias expansion type to use in Message buffers. -The default is `abbrev', which uses mailabbrev. `ecomplete' uses -an electric completion mode. nil switches mail aliases off. -This can also be a list of values." - :group 'message - :link '(custom-manual "(message)Mail Aliases") - :type '(choice (const :tag "Use Mailabbrev" abbrev) - (const :tag "Use ecomplete" ecomplete) - (const :tag "No expansion" nil))) - -(defcustom message-self-insert-commands '(self-insert-command) - "List of `self-insert-command's used to trigger ecomplete. -When one of those commands is invoked to enter a character in To or Cc -header, ecomplete will suggest the candidates of recipients (see also -`message-mail-alias-type'). If you use some tool to enter non-ASCII -text and it replaces `self-insert-command' with the other command, e.g. -`egg-self-insert-command', you may want to add it to this list." - :group 'message-various - :type '(repeat function)) +(make-obsolete-variable 'message-mail-alias-type + "use `eudc-server-hotlist' and `message-mail-address-snarf-hook' instead." + "29.1") + +(make-obsolete-variable 'message-self-insert-commands + "now uses `completion-at-point'." + "29.1") (defcustom message-auto-save-directory (if (file-writable-p message-directory) @@ -1813,6 +1801,21 @@ message-sent-hook :group 'message-various :type 'hook) +(defcustom message-mail-address-snarf-hook nil + "Hook run to snarf email addresses. +This hook is run just after the message was sent as mail. + +The functions on this hook are called once for each header line +where email addresses were found. They take a single argument, a +list of cons cells as returned by `mail-header-parse-addresses'. +Each cons cell corresponds to an email address found. The car of +each cons cell contains the email address. When the cons cell +has a cdr, and its value is not nil, it contains the phrase or +comment part as detected by `mail-header-parse-addresses'." + :version "29.1" + :group 'message-various + :type 'hook) + (defvar message-send-coding-system 'binary "Coding system to encode outgoing mail.") @@ -1934,7 +1937,8 @@ message-draft-article (defvar message-mime-part nil) (defvar message-posting-charset nil) (defvar message-inserted-headers nil) -(defvar message-inhibit-ecomplete nil) +(make-obsolete-variable 'message-inhibit-ecomplete 'eudc-server-hotlist + "29.1") ;; Byte-compiler warning (defvar gnus-active-hashtb) @@ -2942,9 +2946,7 @@ message-mode-map "C-c C-p" #'message-insert-screenshot "C-a" #'message-beginning-of-line - "TAB" #'message-tab - - "M-n" #'message-display-abbrev) + "TAB" #'message-tab) (easy-menu-define message-mode-menu message-mode-map "Message Menu." @@ -3094,9 +3096,6 @@ message-strip-forbidden-properties "Strip forbidden properties between BEGIN and END, ignoring the third arg. This function is intended to be called from `after-change-functions'. See also `message-forbidden-properties'." - (when (and (message-mail-alias-type-p 'ecomplete) - (memq this-command message-self-insert-commands)) - (message-display-abbrev)) (when (and message-strip-special-text-properties (message-tamago-not-in-use-p begin)) (let ((inhibit-read-only t)) @@ -3174,12 +3173,7 @@ message-mode ;; Mmmm... Forbidden properties... (add-hook 'after-change-functions #'message-strip-forbidden-properties nil 'local) - ;; Allow mail alias things. - (cond - ((message-mail-alias-type-p 'abbrev) - (mail-abbrevs-setup)) - ((message-mail-alias-type-p 'ecomplete) - (ecomplete-setup))) + ;; email address completion uses completion-at-point (add-hook 'completion-at-point-functions #'message-completion-function nil t) (unless buffer-file-name (message-set-auto-save-file-name)) @@ -4440,10 +4434,11 @@ message-send (save-excursion (run-hooks 'message-sent-hook)) (message "Sending...done") - ;; Do ecomplete address snarfing. - (when (and (message-mail-alias-type-p 'ecomplete) - (not message-inhibit-ecomplete)) - (message-put-addresses-in-ecomplete)) + ;; Do address snarfing. + (dolist (header '("to" "cc" "from" "reply-to")) + (let* ((value (message-field-value header)) + (parsed (mail-header-parse-addresses value))) + (run-hook-with-args 'message-mail-address-snarf-hook parsed))) ;; Mark the buffer as unmodified and delete auto-save. (set-buffer-modified-p nil) (delete-auto-save-file-if-necessary t) @@ -8017,8 +8012,7 @@ message-resend ;; message has already been encoded. (let ((case-fold-search t)) (re-search-forward "^mime-version:" nil t))) - (message-inhibit-ecomplete t) - ;; We don't want smtpmail.el to encode anything, either. + ;; We don't want smtpmail.el to encode anything, either. (sendmail-coding-system 'raw-text) (select-safe-coding-system-function nil) message-required-mail-headers @@ -8247,7 +8241,7 @@ message-completion-alist :completions ,#'message-expand-group)) (,message-email-recipient-header-regexp . (:category email :fieldsep-re "\\([:,]\\|^\\)[ \t]*" - :completions ,#'message-expand-name))) + :completions ,#'eudc-capf-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), @@ -8305,12 +8299,9 @@ message-completion-alist-set-completions message-completion-alist))) nil) -(defcustom message-expand-name-databases - '(bbdb eudc) - "List of databases to try for name completion (`message-expand-name'). -Each element is a symbol and can be `bbdb' or `eudc'." - :group 'message - :type '(set (const bbdb) (const eudc))) +(make-obsolete-variable 'message-expand-name-databases + "use `eudc-server-hotlist' instead." + "29.1") (defcustom message-tab-body-function nil "Function to execute when `message-tab' (TAB) is executed in the body. @@ -8415,97 +8406,19 @@ message-expand-group (when collection (list b e collection)))) -(defcustom message-expand-name-standard-ui nil - "If non-nil, use the standard completion UI in `message-expand-name'. -E.g. this means it will obey `completion-styles' and other such settings. +(make-obsolete-variable 'message-expand-name-standard-ui + "the UI is provided by `completion-at-point'." + "29.1") -If this variable is non-nil and `message-mail-alias-type' is -`ecomplete', `message-self-insert-commands' should probably be -set to nil." - :version "27.1" - :type 'boolean) +(make-obsolete 'message-expand-name 'eudc-capf-expand-name + "29.1") -(defun message-expand-name (&optional pfx-beg pfx-end) - (cond (message-expand-name-standard-ui - (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) - (boundp 'eudc-protocol) - eudc-protocol) - (eudc-expand-inline)) - ((and (memq 'bbdb message-expand-name-databases) - (fboundp 'bbdb-complete-name)) - (let ((starttick (buffer-modified-tick))) - (or (bbdb-complete-name) - ;; Apparently, bbdb-complete-name can return nil even when - ;; completion took place. So let's double check the buffer was - ;; not modified. - (/= starttick (buffer-modified-tick))))) - (t - (expand-abbrev)))) - -(defun message--bbdb-query-with-words (words) - ;; FIXME: This (or something like this) should live on the BBDB side. - (when (fboundp 'bbdb-records) - (require 'bbdb) ;FIXME: `bbdb-records' is incorrectly autoloaded! - (bbdb-records) ;Make sure BBDB and its database is initialized. - (defvar bbdb-hashtable) - (declare-function bbdb-record-mail "bbdb" (record)) - (declare-function bbdb-dwim-mail "bbdb-com" (record &optional mail)) - (declare-function bbdb-completion-predicate "bbdb-com" (key records)) - (let ((records '()) - (responses '())) - (dolist (word words) - (dolist (c (all-completions word bbdb-hashtable - #'bbdb-completion-predicate)) - (dolist (record (gethash c bbdb-hashtable)) - (cl-pushnew record records)))) - (dolist (record records) - (dolist (mail (bbdb-record-mail record)) - (push (bbdb-dwim-mail record mail) responses))) - responses))) - -(defun message--name-table (orig-string) - (let ((orig-words (split-string orig-string "[ \t]+")) - eudc-responses - bbdb-responses) - (lambda (string pred action) - (pcase action - ('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 - (when (and (memq 'eudc message-expand-name-databases) - (boundp 'eudc-protocol) - eudc-protocol) - (setq eudc-responses (eudc-query-with-words orig-words))) - (when (memq 'bbdb message-expand-name-databases) - (setq bbdb-responses (message--bbdb-query-with-words orig-words))) - (ecomplete-setup) - (setq orig-words nil)) - (let ((candidates - ;; FIXME: Add `expand-abbrev'! - (append (all-completions string eudc-responses pred) - (all-completions string bbdb-responses pred) - (when (and (bound-and-true-p ecomplete-database) - (fboundp 'ecomplete-completion-table)) - (all-completions string - (ecomplete-completion-table 'mail) - pred))))) - (if action candidates (try-completion string candidates)))))))) +(make-obsolete 'message--bbdb-query-with-words + "use `eudc-server-hotlist' instead." + "29.1") + +(make-obsolete 'message--name-table 'eudc-capf-expand-name + "29.1") ;;; Help stuff. @@ -8708,62 +8621,19 @@ message-hide-header-p (not result) result))) -(declare-function ecomplete-add-item "ecomplete" (type key text)) -(declare-function ecomplete-save "ecomplete" ()) - -(defun message-put-addresses-in-ecomplete () - (require 'ecomplete) - (dolist (header '("to" "cc" "from" "reply-to")) - (let ((value (message-field-value header))) - (dolist (string (mail-header-parse-addresses value 'raw)) - (setq string - (string-replace - "\n" "" - (replace-regexp-in-string "^ +\\| +$" "" string))) - (ecomplete-add-item 'mail (car (mail-header-parse-address string)) - string)))) - (ecomplete-save)) - -(autoload 'ecomplete-display-matches "ecomplete") - -(defun message--in-tocc-p () - (and (memq (char-after (point-at-bol)) '(?C ?T ?\t ? )) - (message-point-in-header-p) - (save-excursion - (beginning-of-line) - (while (and (memq (char-after) '(?\t ? )) - (zerop (forward-line -1)))) - (looking-at "To:\\|Cc:")))) - -(defun message-display-abbrev (&optional choose) - "Display the next possible abbrev for the text before point." - (interactive (list t) message-mode) - (when (message--in-tocc-p) - (let* ((end (point)) - (start (save-excursion - (and (re-search-backward "[\n\t ]" nil t) - (1+ (point))))) - (word (when start (buffer-substring start end))) - (match (when (and word - (not (zerop (length word)))) - (ecomplete-display-matches 'mail word choose)))) - (when (and choose match) - (delete-region start end) - (insert match))))) - -(defun message-ecomplete-capf () - "Return completion data for email addresses in Ecomplete. -Meant for use on `completion-at-point-functions'." - (when (and (bound-and-true-p ecomplete-database) - (fboundp 'ecomplete-completion-table) - (message--in-tocc-p)) - (let ((end (save-excursion - (skip-chars-forward "^, \t\n") - (point))) - (start (save-excursion - (skip-chars-backward "^, \t\n") - (point)))) - `(,start ,end ,(ecomplete-completion-table 'mail))))) +(make-obsolete 'message-put-addresses-in-ecomplete 'message-mail-address-snarf-hook + "29.1") + +(make-obsolete 'message--in-tocc-p 'message-completion-function + "29.1") + +(make-obsolete 'message-display-abbrev + "now uses `completion-at-point'." + "29.1") + +(make-obsolete 'message-ecomplete-capf + "use `eudcb-ecomplete' in `eudc-server-hotlist' instead." + "29.1") ;; To send pre-formatted letters like the example below, you can use ;; `message-send-form-letter': diff --git a/lisp/net/eudc-capf.el b/lisp/net/eudc-capf.el index 92f0c80493..884d6e371f 100644 --- a/lisp/net/eudc-capf.el +++ b/lisp/net/eudc-capf.el @@ -107,21 +107,20 @@ eudc-capf-complete (eudc-capf-message-expand-name))) ;;;###autoload -(defun eudc-capf-message-expand-name () +(defun eudc-capf-message-expand-name (&optional pfx-beg pfx-end) "Email address completion function for `message-completion-alist'. -When this function is added to `message-completion-alist', -replacing any existing entry for `message-expand-name' there, -with an appropriate regular expression such as for example +When this function is added to `message-completion-alist', with +an appropriate regular expression such as for example `message-email-recipient-header-regexp', then EUDC will be queried for email addresses, and the results delivered to `completion-at-point'." (if (or eudc-server eudc-server-hotlist) (progn - (let* ((beg (save-excursion - (re-search-backward "\\([:,]\\|^\\)[ \t]*") - (match-end 0))) - (end (point)) + (let* ((beg (or pfx-beg (save-excursion + (re-search-backward "\\([:,]\\|^\\)[ \t]*") + (match-end 0)))) + (end (or pfx-end (point))) (prefix (save-excursion (buffer-substring-no-properties beg end)))) (list beg end (completion-table-with-cache diff --git a/lisp/net/eudcb-ecomplete.el b/lisp/net/eudcb-ecomplete.el new file mode 100644 index 0000000000..6d12ab9231 --- /dev/null +++ b/lisp/net/eudcb-ecomplete.el @@ -0,0 +1,110 @@ +;;; eudcb-ecomplete.el --- EUDC - ecomplete backend -*- lexical-binding: t -*- + +;; Copyright (C) 2022 condition-alpha.com + +;; This program 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. +;; +;; This program 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 this program. If not, see . + +;;; Commentary: +;; This library provides an interface to the ecomplete package as +;; an EUDC data source. + +;;; Usage: +;; To load the library, first `require' it: +;; +;; (require 'eudcb-ecomplete) +;; +;; In the simplest case then just use: +;; +;; (eudc-ecomplete-set-server "localhost") +;; +;; When using `eudc-server-hotlist', instead use: +;; +;; (add-to-list 'eudc-server-hotlist '("localhost" . ecomplete)) + +;;; Code: + +(require 'eudc) +(require 'ecomplete) +(require 'mail-parse) + +(defvar eudc-ecomplete-attributes-translation-alist + '((email . mail)) + "See `eudc-protocol-attributes-translation-alist'. +The back-end-specific attribute names are used as the \"type\" of +entry when searching, and they must hence match the types you use +in your ecmompleterc database file.") + +;; hook ourselves into the EUDC framework +(eudc-protocol-set 'eudc-query-function + 'eudc-ecomplete-query-internal + 'ecomplete) +(eudc-protocol-set 'eudc-list-attributes-function + nil + 'ecomplete) +(eudc-protocol-set 'eudc-protocol-attributes-translation-alist + 'eudc-ecomplete-attributes-translation-alist + 'ecomplete) +(eudc-protocol-set 'eudc-protocol-has-default-query-attributes + nil + 'ecomplete) + +(defun eudc-ecomplete-query-internal (query &optional _return-attrs) + "Query `ecomplete' with QUERY. +QUERY is a list of cons cells (ATTR . VALUE). Since `ecomplete' +does not provide attributes in the usual sense, the +back-end-specific attribute names in +`eudc-ecomplete-attributes-translation-alist' are used as the +KEY (that is, the \"type\" of match) when looking for matches in +`ecomplete-database'. + +RETURN-ATTRS is a list of attributes to return, defaulting to +`eudc-default-return-attributes'." + (ecomplete-setup) + (let ((email-attr (car (eudc-translate-attribute-list '(email)))) + result) + (dolist (term query) + (let* ((attr (car term)) + (value (cdr term)) + (matches (ecomplete-get-matches attr value))) + (when matches + (dolist (match (split-string (string-trim (substring-no-properties + matches)) + "[\n\r]")) + ;; special case email: try to decompose + (let* ((decoded (mail-header-parse-address match t)) + (name (cdr decoded)) + (email (car decoded))) + (if (and decoded (eq attr email-attr)) + ;; email could be decomposed, push individual fields + (push `((,attr . ,email) + ,@(when name (list (cons 'name name)))) + result) + ;; else, just forward the value as-is + (push (list (cons attr match)) result))))))) + result)) + +(defun eudc-ecomplete-set-server (dummy) + "Set the EUDC server to `ecomplete'. +The server in DUMMY is not actually used, since this backend +always and implicitly uses the ecomplete package in the current +Emacs instance running on the local host." + (interactive) + (eudc-set-server dummy 'ecomplete) + (message "[eudc] ecomplete server selected")) + +(eudc-register-protocol 'ecomplete) + +(provide 'eudcb-ecomplete) + +;;; eudcb-ecomplete.el ends here diff --git a/lisp/net/eudcb-mailabbrev.el b/lisp/net/eudcb-mailabbrev.el new file mode 100644 index 0000000000..816ce7b1e9 --- /dev/null +++ b/lisp/net/eudcb-mailabbrev.el @@ -0,0 +1,100 @@ +;;; eudcb-mailabbrev.el --- EUDC - mailabbrev backend -*- lexical-binding: t -*- + +;; Copyright (C) 2022 condition-alpha.com + +;; This program 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. +;; +;; This program 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 this program. If not, see . + +;;; Commentary: +;; This library provides an interface to the mailabbrev package as +;; an EUDC data source. + +;;; Usage: +;; To load the library, first `require' it: +;; +;; (require 'eudcb-mailabbrev) +;; +;; In the simplest case then just use: +;; +;; (eudc-mailabbrev-set-server "localhost") +;; +;; When using `eudc-server-hotlist', instead use: +;; +;; (add-to-list 'eudc-server-hotlist '("localhost" . mailabbrev)) + +;;; Code: + +(require 'eudc) +(require 'mailabbrev) +(require 'mail-parse) + +;; hook ourselves into the EUDC framework +(eudc-protocol-set 'eudc-query-function + 'eudc-mailabbrev-query-internal + 'mailabbrev) +(eudc-protocol-set 'eudc-list-attributes-function + nil + 'mailabbrev) +(eudc-protocol-set 'eudc-protocol-attributes-translation-alist + nil + 'mailabbrev) +(eudc-protocol-set 'eudc-protocol-has-default-query-attributes + nil + 'mailabbrev) + +(defun eudc-mailabbrev-query-internal (query &optional _return-attrs) + "Query `mailabbrev' with QUERY. +QUERY is a list of cons cells (ATTR . VALUE). Since `mailabbrev' +does not provide attributes in the usual sense, the +back-end-specific attribute names in +`eudc-mailabbrev-attributes-translation-alist' are used as the +KEY (that is, the \"type\" of match) when looking for matches in +`mailabbrev-database'. + +RETURN-ATTRS is a list of attributes to return, defaulting to +`eudc-default-return-attributes'." + (mail-abbrevs-setup) + (let (result) + (dolist (term query) + (let* ((attr (car term)) + (value (cdr term)) + (match (symbol-value (intern-soft value mail-abbrevs)))) + (when (and match + (memq attr '(email firstname name))) + ;; try to decompose email construct + (let* ((decoded (mail-header-parse-address match t)) + (name (cdr decoded)) + (email (car decoded))) + (if decoded + ;; decoding worked, push individual fields + (push `((email . ,email) + ,@(when name (list (cons 'name name)))) + result) + ;; else, just forward the value as-is + (push (list (cons 'email match)) result)))))) + result)) + +(defun eudc-mailabbrev-set-server (dummy) + "Set the EUDC server to `mailabbrev'. +The server in DUMMY is not actually used, since this backend +always and implicitly uses the mailabbrev package in the current +Emacs instance running on the local host." + (interactive) + (eudc-set-server dummy 'mailabbrev) + (message "[eudc] mailabbrev server selected")) + +(eudc-register-protocol 'mailabbrev) + +(provide 'eudcb-mailabbrev) + +;;; eudcb-mailabbrev.el ends here -- 2.37.1 --=-=-= Content-Type: text/plain I haven't invested any time in scratching my head about a NEWS entry, or any modifications to the texi documentation, but wanted to get you view first. Looking forward to your thoughts, --alexander --=-=-=--