Re: [PATCH] emacs: Tab completion for notmuch-search and notmuch-search-filter

Subject: Re: [PATCH] emacs: Tab completion for notmuch-search and notmuch-search-filter

Date: Sat, 4 Jun 2011 17:55:24 -0400

To: Daniel Schoepe

Cc: notmuch@notmuchmail.org

From: Austin Clements


Quoth Daniel Schoepe on Jun 04 at  9:55 pm:
> On Sat, 4 Jun 2011 11:32:15 -0400, Austin Clements <amdragon@mit.edu> wrote:
> > Dynamic scoping is obnoxious, but I think programmed completion is
> > steeped in the assumption that you'll use it.  This code would be much
> > simpler if notmuch-query-completions took only `string' and used the
> > dynamically-bound all-compls (which should probably be renamed
> > notmuch-completions or something if you do this).  Then this could be
> > just
> >   (minibuffer-completion-table (completion-table-dynamic
> > #'notmuch-query-completions)))
> > and there'd be no need for quasiquoting, comments, and fake lexical scoping.
> 
> Sounds reasonable, I guess I really should stop fighting all those ugly
> parts of elisp with unreadable constructs like that. I made it a global
> variable though to avoid compilation warnings about notmuch-completion
> being a free variable. Since it's contents are not dependent on
> how/where notmuch-read-query is called, this shouldn't cause any
> problems, except my personal discomfort arising from the use of side
> effects for something as simple as this. :)

Oh, sorry, I wasn't suggesting setq'ing a global.  I agree that that's
really ugly.  Rather, something like

(defun notmuch-query-completions (string)
  ... as you have it now ...)

(defun notmuch-read-query (prompt)
  (let ((notmuch-completions (append (list "folder:" ...)))
        (keymap ...)
        (minibuffer-completion-table ...))
    (read-from-minibuffer ...)))

Unfortunately, you still need the global defvar to avoid compilation
warnings, but this at least avoids the side-effects, and is probably
how programmed completion was intended to be used.

Alternatively, here's a completely different way to structure this
that avoids globals and dynamic scoping entirely.  This is how some of
the standard completing read functions appear to work:

(defun notmuch-read-query (prompt)
  "Read a notmuch-query from the minibuffer with completion.

PROMPT is the string to prompt with."
  (lexical-let ((completions
		 (append (list "folder:" "thread:" "id:" "date:" "from:" "to:"
			       "subject:" "attachment:")
			 (mapcar (lambda (tag)
				   (concat "tag:" tag))
				 (process-lines "notmuch" "search-tags")))))
    (let ((minibuffer-completion-table
	   (completion-table-dynamic
	    (lambda (string)
	      (cond
	       ;; this ugly regexp is used to get the last word of the
	       ;; input possibly preceded by a '('
	       ((string-match "\\(^\\|.* (?\\)\\([^ ]*\\)$" string)
		(mapcar (lambda (compl)
			  (concat (match-string-no-properties 1 string) compl))
			(all-completions (match-string-no-properties 2 string)
					 completions)))
	       (t (list string))))))
	  (keymap (copy-keymap minibuffer-local-map)))
      ;; this was simpler than convincing completing-read to accept spaces:
      (define-key keymap (kbd "<tab>") 'minibuffer-complete)
    (read-from-minibuffer prompt nil keymap nil minibuffer-history nil nil))))

> > > +    (define-key keymap (kbd "<tab>") 'minibuffer-complete)
> > 
> > This probably deserves a comment about why you're doing so much work
> > to avoid completing-read (which I assume is because it also binds SPC,
> > even if require-match is nil, which is unfortunate).
> 
> Yes, that was the reason.
> 
> Another thing that bugs me, is that I did not find a better way of doing
> the completion: Ideally I'd like to just specify a list of completions
> for individual words and have emacs handle separating the input string
> into individual words, but I couldn't find any options to accomplish
> that.

Yeah, I futzed with it for a bit, swearing that there had to be a
better way, but didn't find one either.

Thread: