Icicles - Multi-Commands the Hard Way

Previous: Icicles - Multi M-xIciclesIciclesIndexNext: Icicles - Global Filters

Defining Multi-Commands the Hard Way

This section is for EmacsLisp programmers. It gives you a taste of what is involved behind the scene when you effortlessly use ‘icicle-define-command’ or ‘icicle-define-file-command’ to define a multi-command.

It can be good to know this, if only for the case where you need to define a multi-command that has special behavior not provided by ‘icicle-define-command’ and ‘icicle-define-file-command’ out of the box. For example, if you want the normal, single-choice ‘RET’ behavior to be different from the multiple-choice ‘C-RET’ behavior, then you might want to roll your own. Likewise, if you want to define your own help on individual candidates, to be invoked when users use ‘C-M-RET’ and so on.

To write your own multi-command, you must make the command do this:

1. Call ‘completing-read’ or ‘read-file-name’, and perform some action on the completed input.

2. Bind one or more of these variables to action functions, which each take a completion candidate as argument:

#1 just lets people use the command normally, to perform the #1 action on a completion candidate entered with ‘RET’. Because of #2, people can perform the #2 action(s) on any completion candidates, while still continuing to cycle or complete candidates. ‘icicle-candidate-action-fn’ is often the same as the action for #1, but nothing prevents you from using different actions.

When internal variable ‘icicle-candidate-action-fn’ is not bound, the default action is performed: display help on the current completion candidate. When ‘icicle-candidate-help-fn’ is not bound, the default help display is used.

Instead of binding ‘icicle-delete-candidate-object’ to a deletion action function, you can bind it to a symbol (variable) whose value is a list of completion-candidate objects. See Icicles - More About Multi-Commands for more information.

Here is a definition of a simple (not multi-) command that reads a font name and then changes the selected frame to use that font. By virtue of calling ‘completing-read’, Icicles completion and cycling are available, using all available font names as the pool of candidates.

  (defun change-font ()
    "Change font of selected frame."
     (list (cons 'font (completing-read
                        "Font: " (mapcar #'list (x-list-fonts "*"))
                        nil t)))))

Here’s a definition of a multi-command ‘change-font’ that takes advantage of an action function when cycling candidates:

  1  (defun change-font ()
  2    "Change font of current frame."
  3    (interactive)
  4   (let* ((orig-frame  (selected-frame))
  5          (orig-font   (frame-parameter nil 'font))
  6          (icicle-candidate-action-fn
  7           ;; Perform the action on a candidate, without leaving
  8           ;; `completing-read'.  You can do this over and over.
  9           (lambda (font)
  10             (modify-frame-parameters orig-frame
  11                                      (list (cons 'font font))))))
  12     (condition-case nil
  13         (modify-frame-parameters
  14          orig-frame
  15          (list
  16           (cons 'font
  17                 ;; Perform the action on your final choice.
  18                 (completing-read
  19                  "Font: "
  20                  (mapcar #'list (x-list-fonts "*")) nil t))))
  21       ((quit error)
  22        (modify-frame-parameters
  23         orig-frame
  24         (list (cons 'font orig-font)))))))

As you can see, there is a lot more going on here than in the simple-command version. These are the points to keep in mind, when defining a multi-command by hand:

1. Save anything you need to restore, so you can, in effect, undo the action in case of ‘C-g’ (lines 4-5).

2. Bind ‘icicle-candidate-action-fn’ to the action to perform (lines 6-11).

3. Perform the action, using ‘completing-read’ to provide the target candidate (lines 13-20). Do this in the body of a ‘condition-case’ (lines 12-24).

4. Restore the original context in the error-handling part of the ‘condition-case’ (lines 22-24). Include ‘quit’ in the error-type list.

The above definition is not quite complete, in fact. To let ‘icicle-all-candidates’ be able to report on failures, the ‘icicle-candidate-action-fn’ code should also trap errors and return ‘nil’ as a success indicator.

In fact, things can get even hairier (much hairier) still, if the function at the core of your command does things like create a new frame – especially on MS Windows, with its click-to-focus window manager. The action of ‘change-font’ does not do that, but if it did, you would need to redirect the focus back to the minibuffer frame, using ‘select-frame-set-input-focus’. As an illustration of what’s involved, here’s a definition that would deal with such problems. It also traps ‘icicle-candidate-action-fn’ errors, returning ‘nil’ to report success and the error message to report failure.

  (defun change-font ()
    "Change font of current frame."
    (let* ((icicle-orig-buff    (current-buffer))
           (icicle-orig-window  (selected-window))
           (orig-frame          (selected-frame))
           (orig-font           (frame-parameter nil 'font))
            (lambda (candidate)
              (condition-case action-fn-return
                     orig-frame (list (cons 'font candidate)))
                     (window-frame (minibuffer-window)))
                    nil) ; Return nil to report success.
                ;; Return error message to report error.
                (error (error-message-string action-fn-return))))))
      (condition-case act-on-choice
           (list (cons 'font
                        "Font: " (mapcar #'list (x-list-fonts "*"))
                        nil t nil nil nil nil))))
        (quit (switch-to-buffer icicle-orig-buff)
               orig-frame (list (cons 'font orig-font))))
        (error (switch-to-buffer icicle-orig-buff)
                orig-frame (list (cons 'font orig-font)))
               (error "%s" (error-message-string act-on-choice))))))

That’s a lot of (error-prone) work! You obviously do not want to be doing that a lot. Whenever you can, you should use macro ‘icicle-define-command’ or ‘icicle-define-file-command’ to define your multi-commands.

Take a quick look at the definition of command ‘icicle-find-file’ – both the simple definition that you would write and its expanded version, that is, the real definition created by macro ‘icicle-define-file-command’: Icicles - icicle-find-file Definition. Seeing it once will make you appreciate macros ‘icicle-define-command’ and ‘icicle-define-file-command’!

See Also:

Previous: Icicles - Multi M-xIciclesIciclesIndexNext: Icicles - Global Filters

DrewsElispLibraries referenced here: Lisp:icicles.el

CategoryCommands CategoryBufferSwitching CategoryCompletion CategoryRegexp CategoryDirectories CategoryFiles CategoryProgrammerUtils CategoryCode