Discussion:
[elpa] externals/ebdb 9e7a96f: Add experimental ebdb-completion-at-point-function
(too old to reply)
Stefan Monnier
2018-03-23 05:18:51 UTC
Permalink
+;; Experimental completion-at-point function. I'm not sure this is a
+;; good idea yet -- with a large enough EBDB database, nearly any
+;; string is completable, meaning the other completion-at-point
+;; functions will rarely get a chance.
+(defun ebdb-completion-at-point-function ()
[...]
+ (when completions
+ (list start (point)
+ (mapcar
+ (lambda (str)
+ ;; Gross.
+ str
+ (capitalize str)))
+ completions)
+ '(:exclusive no)))))
Completion-at-point-functions are expected to be cheap/fast (it's normal
to call it in post-command-hook) and in order to work correctly the
completion table it returns should ideally not depend on the text
between START and END (i.e. it's OK to look at the text between
START..END in order to choose between an email completion table and
a file completion table, but it shouldn't throw away emails just
because they don't seem to match the text between START..END).

Also in order to be effective, you want them to be selective, e.g. only
match when we're pretty sure that the completion-table we return is
relevant (e.g. we're on a "To:" line in a message-mode buffer), so it
usually depends on the major mode in which it's used.

EBDB might elect not to provide a completion-at-point-function but
instead to provide only a completion-table (or a bunch of completion
tables). Then message-mode could use that completion-table when it
determines that we're completing an email address.


Stefan
Eric Abrahamsen
2018-03-23 05:50:31 UTC
Permalink
Post by Stefan Monnier
+;; Experimental completion-at-point function. I'm not sure this is a
+;; good idea yet -- with a large enough EBDB database, nearly any
+;; string is completable, meaning the other completion-at-point
+;; functions will rarely get a chance.
+(defun ebdb-completion-at-point-function ()
[...]
+ (when completions
+ (list start (point)
+ (mapcar
+ (lambda (str)
+ ;; Gross.
+ str
+ (capitalize str)))
+ completions)
+ '(:exclusive no)))))
Completion-at-point-functions are expected to be cheap/fast (it's normal
to call it in post-command-hook) and in order to work correctly the
completion table it returns should ideally not depend on the text
between START and END (i.e. it's OK to look at the text between
START..END in order to choose between an email completion table and
a file completion table, but it shouldn't throw away emails just
because they don't seem to match the text between START..END).
Also in order to be effective, you want them to be selective, e.g. only
match when we're pretty sure that the completion-table we return is
relevant (e.g. we're on a "To:" line in a message-mode buffer), so it
usually depends on the major mode in which it's used.
EBDB might elect not to provide a completion-at-point-function but
instead to provide only a completion-table (or a bunch of completion
tables). Then message-mode could use that completion-table when it
determines that we're completing an email address.
Thanks for these notes!

In part I was wondering if it would be useful to provide completion on
contact names in other contexts besides message headers, but I suppose
the right thing to do is just add contact names to the user's personal
spelling dictionary, and allow them to complete names as part of
spell-checking/ispell-complete-word. EBDB provides other tools for
inserting contact information in random contexts.

So this completion function should be targeted specifically at
message/mail-mode header lines. I'll no longer override <TAB> in that
message-mode, but instead tie into what's already there.

In message-mode, c-a-p-f contains `message-completion-function', which
consults `message-completion-alist' (which contains a FIXME: "Make it
possible to use the standard completion UI."), which hands off to
`message-expand-name', which is hard-coded to use either eudc, bbdb, or
`expand-abbrev'. There is a user option,
`message-expand-name-databases', but it's mostly useless as putting new
databases in there won't actually let you use them.

Looks like you added that FIXME! If you outline how you think this ought
to look, I can take a stab at patching message.el. At what level should
these functions be intervening?

My only reservation is that BBDB/EBDB mail completion first completes a
contact mail address, and subsequently cycles through that contact's
other addresses. Is this something that the standard mechanisms can
replicate?

Thanks,
Eric
Thomas Fitzsimmons
2018-03-23 10:54:10 UTC
Permalink
+;; Experimental completion-at-point function. I'm not sure this is a
+;; good idea yet -- with a large enough EBDB database, nearly any
+;; string is completable, meaning the other completion-at-point
+;; functions will rarely get a chance.
See also: eudc-expand-inline, which works at point as long as the
to-be-completed string is the only thing between beginning-of-line and
point. This is what I use for inline BBDB and LDAP completion anywhere
in Emacs, not just in message-mode headers. It's useful, for example,
when adding someone's email address to an Org-mode entry (type a few
letters of their first name, then M-x eudc-expand-inline).

Maybe we could add an EBDB backend for EUDC.

Thomas
Eric Abrahamsen
2018-03-23 12:10:55 UTC
Permalink
Post by Thomas Fitzsimmons
+;; Experimental completion-at-point function. I'm not sure this is a
+;; good idea yet -- with a large enough EBDB database, nearly any
+;; string is completable, meaning the other completion-at-point
+;; functions will rarely get a chance.
See also: eudc-expand-inline, which works at point as long as the
to-be-completed string is the only thing between beginning-of-line and
point. This is what I use for inline BBDB and LDAP completion anywhere
in Emacs, not just in message-mode headers. It's useful, for example,
when adding someone's email address to an Org-mode entry (type a few
letters of their first name, then M-x eudc-expand-inline).
Maybe we could add an EBDB backend for EUDC.
To be honest, I never looked at what EUDC actually is. I'd be happy to
add an EBDB backend, I'll put that on the list.

Eric
Eric Abrahamsen
2018-03-23 12:11:57 UTC
Permalink
Post by Eric Abrahamsen
Post by Stefan Monnier
+;; Experimental completion-at-point function. I'm not sure this is a
+;; good idea yet -- with a large enough EBDB database, nearly any
+;; string is completable, meaning the other completion-at-point
+;; functions will rarely get a chance.
+(defun ebdb-completion-at-point-function ()
[...]
+ (when completions
+ (list start (point)
+ (mapcar
+ (lambda (str)
+ ;; Gross.
+ str
+ (capitalize str)))
+ completions)
+ '(:exclusive no)))))
Completion-at-point-functions are expected to be cheap/fast (it's normal
to call it in post-command-hook) and in order to work correctly the
completion table it returns should ideally not depend on the text
between START and END (i.e. it's OK to look at the text between
START..END in order to choose between an email completion table and
a file completion table, but it shouldn't throw away emails just
because they don't seem to match the text between START..END).
Also in order to be effective, you want them to be selective, e.g. only
match when we're pretty sure that the completion-table we return is
relevant (e.g. we're on a "To:" line in a message-mode buffer), so it
usually depends on the major mode in which it's used.
EBDB might elect not to provide a completion-at-point-function but
instead to provide only a completion-table (or a bunch of completion
tables). Then message-mode could use that completion-table when it
determines that we're completing an email address.
[...]
Post by Eric Abrahamsen
My only reservation is that BBDB/EBDB mail completion first completes a
contact mail address, and subsequently cycles through that contact's
other addresses. Is this something that the standard mechanisms can
replicate?
Never mind, obviously the completion functions can do whatever they like
with the string to be completed.
Stefan Monnier
2018-03-23 12:23:07 UTC
Permalink
Post by Eric Abrahamsen
Looks like you added that FIXME! If you outline how you think this ought
to look, I can take a stab at patching message.el. At what level should
these functions be intervening?
One of the main issue is preserving backward compatibility with existing
functions the user may have set in message-completion-alist.

I have already some local patches to try and do some of that, so see
patch below (I hand-edited it to remove irrelevant other cosmetic
changes, so don't try to pass it to `patch`).
Post by Eric Abrahamsen
My only reservation is that BBDB/EBDB mail completion first completes a
contact mail address, and subsequently cycles through that contact's
other addresses. Is this something that the standard mechanisms can
replicate?
You can get cycling via completion-cycle-threshold, yes.


Stefan


diff --git a/lisp/gnus/message.el b/lisp/gnus/message.el
index e452c80e26..c99708845d 100644
--- a/lisp/gnus/message.el
+++ b/lisp/gnus/message.el
@@ -7930,6 +7936,8 @@ message-tab

(defvar mail-abbrev-mode-regexp)

+(defvar message--old-style-completion-functions nil)
+
(defun message-completion-function ()
(let ((alist message-completion-alist))
(while (and alist
@@ -7938,9 +7946,21 @@ message-completion-function
(setq alist (cdr alist)))
(when (cdar alist)
(let ((fun (cdar alist)))
- ;; Even if completion fails, return a non-nil value, so as to avoid
- ;; falling back to message-tab-body-function.
- (lambda () (funcall fun) 'completion-attempted)))))
+ (if (member fun message--old-style-completion-functions)
+ ;; Even if completion fails, return a non-nil value, so as to avoid
+ ;; falling back to message-tab-body-function.
+ (lambda () (funcall fun) '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 ()
"Expand the group name under point."
@@ -7966,7 +8083,9 @@ message-expand-group
group)
collection))
gnus-active-hashtb))
- (completion-in-region b e collection)))
+ ;; FIXME: Add `category' metadata to the collection, so we can use
+ ;; substring matching on it.
+ (list b e collection)))

(defun message-expand-name ()
(cond ((and (memq 'eudc message-expand-name-databases)
Eric Abrahamsen
2018-04-14 01:02:58 UTC
Permalink
Post by Stefan Monnier
Post by Eric Abrahamsen
Looks like you added that FIXME! If you outline how you think this ought
to look, I can take a stab at patching message.el. At what level should
these functions be intervening?
One of the main issue is preserving backward compatibility with existing
functions the user may have set in message-completion-alist.
I have already some local patches to try and do some of that, so see
patch below (I hand-edited it to remove irrelevant other cosmetic
changes, so don't try to pass it to `patch`).
Okay, I'm finally coming back around to this, and I need a bit more of
an overview to know what to do. Lars seems to have come in from the
cold, so I'm copying him here.

Leaving backward compatibility aside for a second, here's my take on
things:

What we've got is:

message-mode binds TAB to `message-tab', which calls
`completion-at-point' and, if that doesn't work, falls back to other
things. message-mode adds `message-completion-function' to
`completion-functions-at-point', so TAB ends up calling that function
first. If point is in a viable header, the function calls
`message-expand-group' or `message-expand-name' depending on the header,
but either way _always_ returns a value so that it prevents any other
capf functions from running. If point isn't in a viable header, we get
the "falls back to other things" behavior.

`message-expand-name' is the one that hands off to EUDC, BBDB, etc. The
whole issue is that these package functions do their own completion,
rather than interfacing with `completion-at-point'.

What we _want_ is (and I'm partially guessing here):

message-mode adds message-expand-group and message-tab-body-function to
`completion-at-point-functions'. Both of these functions check if
they're in an appropriate location, and bail if not, allowing other
functions to do their thing.

Packages such as EUDC and BBDB put their own functions in
`completion-at-point-functions' (in the message-mode hook). The first
thing these functions do is test if they're in an appropriate header,
and bail if not. Otherwise they return an appropriate value for c-a-p,
ie (START END COLLECTION), rather than doing their own completion.

Does this seem about right? Backwards compatibility is still an issue,
but that's what Stefan's patch addresses.

Eric
Stefan Monnier
2018-04-14 01:17:03 UTC
Permalink
Post by Eric Abrahamsen
message-mode adds message-expand-group and message-tab-body-function to
`completion-at-point-functions'. Both of these functions check if
they're in an appropriate location, and bail if not, allowing other
functions to do their thing.
I think it's OK for Gnus to keep using message-completion-alist, but
message-expand-name should use a completion table which can be extended,
so EUDC, BBDB, ecomplete, and YouNameIt can add themselves to it.
Post by Eric Abrahamsen
Packages such as EUDC and BBDB put their own functions in
`completion-at-point-functions' (in the message-mode hook).
That doesn't sound right: the code which decides if we're inside
a message header, and which header contains email addresses, and how
they're separated (i.e. the code which knows about the format of
messages) should squarely belong to message-mode and not to
BBDB/EUDC/...

Instead these backends should only provide completion tables that
provide user names, email addresses, or such data.

I.e. the completion-at-point-function should come from message.el and
the completion-tables it returns should come from BBDB/EUDC/...


Stefan
Eric Abrahamsen
2018-04-14 02:33:27 UTC
Permalink
Post by Stefan Monnier
Post by Eric Abrahamsen
message-mode adds message-expand-group and message-tab-body-function to
`completion-at-point-functions'. Both of these functions check if
they're in an appropriate location, and bail if not, allowing other
functions to do their thing.
I think it's OK for Gnus to keep using message-completion-alist, but
message-expand-name should use a completion table which can be extended,
so EUDC, BBDB, ecomplete, and YouNameIt can add themselves to it.
Post by Eric Abrahamsen
Packages such as EUDC and BBDB put their own functions in
`completion-at-point-functions' (in the message-mode hook).
That doesn't sound right: the code which decides if we're inside
a message header, and which header contains email addresses, and how
they're separated (i.e. the code which knows about the format of
messages) should squarely belong to message-mode and not to
BBDB/EUDC/...
Instead these backends should only provide completion tables that
provide user names, email addresses, or such data.
I.e. the completion-at-point-function should come from message.el and
the completion-tables it returns should come from BBDB/EUDC/...
Good! Thanks, that's the direction I needed.

My next question is, how does one "extend" a completion table?
Specifically: if c-a-p expects to receive (START END COLLECTION), should
the message-mode capf function marshal collections from various
backends, and offer them up as part of that one return value?

So say we add a message-name-completion-functions option, each backend
adds its function there, and message-mode calls all those functions,
gathers the results, and returns them to `completion-at-point'?

Almost there,
Eric
Stefan Monnier
2018-04-14 17:37:51 UTC
Permalink
Post by Eric Abrahamsen
My next question is, how does one "extend" a completion table?
Not sure what you mean, but you can combine completion tables for
example by using completion-table-in-turn, or by doing something
similar to what it does.
Post by Eric Abrahamsen
Specifically: if c-a-p expects to receive (START END COLLECTION), should
^^^^^^^^^^
BTW, this is what I call "completion table".


Stefan
Eric Abrahamsen
2018-04-25 19:24:13 UTC
Permalink
Post by Stefan Monnier
Post by Eric Abrahamsen
My next question is, how does one "extend" a completion table?
Not sure what you mean, but you can combine completion tables for
example by using completion-table-in-turn, or by doing something
similar to what it does.
Yes, that's what I meant.
Post by Stefan Monnier
Post by Eric Abrahamsen
Specifically: if c-a-p expects to receive (START END COLLECTION), should
^^^^^^^^^^
BTW, this is what I call "completion table".
Right, that much I understood. But otherwise I was very undereducated
about how completion works, and so have gone down some rabbit holes in
the course of writing the attached patch, which is underwhelming for how
long it took me.

I doubt this will be acceptable as-is, but I do hope it will get us (me)
a step closer. What I ended up with was an option,
`message-expand-name-tables', that contact-management/addressbook
packages can add completion tables to. This is very simple, but probably
too simple: it leaves no mechanism for these packages to add extra PROPS
data, like :predicate or :annotation, and also doesn't give them the
opportunity to alter START and END (though maybe it shouldn't?).

Anyway, for what it is, it works. It also add six spurious blank spaces
to the end of any completion, at least in my test setup, but who's
counting!?

Hopefully we can go somewhere from here.

Lars Ingebrigtsen
2018-04-14 12:59:26 UTC
Permalink
Post by Eric Abrahamsen
message-mode adds message-expand-group and message-tab-body-function to
`completion-at-point-functions'. Both of these functions check if
they're in an appropriate location, and bail if not, allowing other
functions to do their thing.
Does this also tie into ecomplete somehow? :-)
--
(domestic pets only, the antidote for overdose, milk.)
bloggy blog: http://lars.ingebrigtsen.no
Eric Abrahamsen
2018-04-14 16:17:59 UTC
Permalink
Post by Lars Ingebrigtsen
Post by Eric Abrahamsen
message-mode adds message-expand-group and message-tab-body-function to
`completion-at-point-functions'. Both of these functions check if
they're in an appropriate location, and bail if not, allowing other
functions to do their thing.
Does this also tie into ecomplete somehow? :-)
I think the goal is that ecomplete would be one of the $contact_packages
that can add themselves as sources for completions. But I'm still not
sure of the exact mechanism.
Loading...