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:
"Logs your mood"
(interactive "sHow are you feeling? ")
But the string form of argument to interactive is just a shortcut. You can easily supply some code instead, with a static completion list:
"Logs your mood"
(interactive (list (completing-read "How are you feeling? " '("happy""sad")))
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
((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)))
((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"
(let ((completion-ignore-case 't))
(concat "Enter subdir of " cdsrc-completions-prefix ": ")
(let ((path2 (concat cdsrc-completions-prefix path)))
(if (string-match mode-name "Shell")
(comint-send-string (current-buffer) (concat "cd " path2))
05:36 AM, 21 Feb 2012 by Mark Aufflick Permalink