Begin main content

Emacs Programmed Completion

Subtitle - how (completing-read) stole my afternoon.

I thought I'd post my experiences since completing-read is so poorly understood based on the forum and blog posts out there. When you write an interactive Elisp command, many will know that you can easily read a value from the user. Eg: this will ask you how you feel and log it:

(defun mood(mood)
"Logs your mood"
(interactive "sHow are you feeling? ")
(message mood))

But the string form of argument to interactive is just a shortcut. You can easily supply some code instead, with a static completion list:

(defun mood(mood)
"Logs your mood"
(interactive (list (completing-read "How are you feeling? " '("happy""sad")))
(insert arg))
(message mood))

If you want something more complex though, instead of supplying a completion list, you can supply a function and that function receives as one of it's arguments the string to be completed. But it's not as simple as that function just returning a list unfortunately, and that's where people come unstuck. Your function is now embedded in the guts of the Emacs completion mechanism and as such it has to answer a whole range of questions about completions. It is reasonably well documented (if a bit terse) in the Programmed Completion section of the Emacs manual. There is a second argument which contains an optional predicate function (I'm ignoring that), but the important bit is the third argument which effectively sets the mode in which your function is being called.

In fact, your function will usually be called multiple times for each completion tab press. Essentially, your function may be asked to provide the best completion string possible, if there string is an exact match, if there are no matches, or to provide a list of possible matches. There is one final thing that will be asked, and that is the substring your completion is for. So in the example you're about to see I am completing directories, so the completion being offered at any given time only applies to the part after the last forward slash.

An example is as good as a thousand or so words, so here's my interactive function cdsrc. Basically it's a convenience function to quickly change directory into one of my client projects, which may be one or two levels deep in my main company source directory. The hard work is done by cdsrc-completions and you can see in the (cond) at the end where it responds to the different modes discussed briefly above and in detail in Programmed Completion.

(setq cdsrc-completions-prefix "~/src/_Pumptheory/")

(defun safe-fill-common-string-prefix (s1 s2)
(if (and s1 s2)
(fill-common-string-prefix s1 s2) nil))

(defun cdsrc-completions (str pred mode)
(let ((dirname (replace-regexp-in-string "[^/]+$""" str))
(filename (replace-regexp-in-string "^.*/""" str))
(slashpos (or (string-match "/.*$" str) 0)))

(let ((completions (all-completions filename (delq nil
(mapcar (lambda (x)
(and (file-directory-p (concat cdsrc-completions-prefix dirname x))
(not (string-match "^\\." x))
(concat x "/")))
(directory-files (concat cdsrc-completions-prefix dirname)))))))

;; return differently based on what mode we were called in ;; see http://www.gnu.org/software/emacs/manual/html_node/elisp/Programmed-Completion.html
(cond
((not mode) (cond
((and (eq 0 (length completions)) (eq 0 (length filename))) 't);; in our case, only report exact match when no nested dirs left
((> (length completions) 1)
(concat dirname (or (reduce 'safe-fill-common-string-prefix completions) filename)))
((eq (length completions) 1)
(concat dirname (car completions)))
('t nil)))

((eq mode 't) completions)

((eq mode 'lambda) (member (concat filename "/") completions))

('t (cons (list 'boundaries slashpos) (length filename))));; let completion know our completions only apply after the last / )))

(defun cdsrc (path)
"Changes to a subdir of cdsrc-completions-prefix in the current active interactive shell buffer"
(interactive (list
(let ((completion-ignore-case 't))
(completing-read
(concat "Enter subdir of " cdsrc-completions-prefix ": ")
'cdsrc-completions))))
(let ((path2 (concat cdsrc-completions-prefix path)))
(cd path2)
(if (string-match mode-name "Shell")
(progn (end-of-buffer)
(comint-kill-input)
(comint-send-string (current-buffer) (concat "cd " path2))
(comint-send-input)))))

This is out of my emacs.d, specifically aufflick-shell-hooks.el.

05:36 AM, 21 Feb 2012 by Mark Aufflick Permalink

jack13

"While the industry is working on adding different fungicides, and crop breeders are getting better at incorporating different types of resistance, they attempt to save yield, not increase it, and there is usually a cost of some sort."panvoya

by Unregistered Visitor on 10/28/14

jack13

“You can look like a cop and we’ll call you a cop, but you don’t really have any way of being a cop,” Wardwell said. “Because we’re not going to train you, we won’t provide safety equipment. The salary will be so bad that we won’t be able to recruit anybody of talent.”Ingreso Cybernetico

by Unregistered Visitor on 10/30/14

Add comment