Icicles - Defining Multi M-x

iciclesimage
Previous: Icicles - Multiple-Choice MenusIciclesIciclesIndexNext: Icicles - Multi-Commands the Hard Way

This page is for EmacsLisp programmers. It explains how the Icicles Multi M-x feature is implemented, providing an advanced illustration of using macro ‘icicle-define-command’.

How Multi `M-x' is Defined

The definition of ‘icicle-execute-extended-command’ provides an interesting illustration of using ‘icicle-define-command’. The candidate action function itself binds a candidate action function, in case the candidate is a command that reads input with completion. Here is a simplified version of the definition.

  (icicle-define-command 
     icicle-execute-extended-command   ; Bound to `M-x' in Icicle mode.
     "Read command name, then read its arguments and call it." ; Doc string
     icicle-execute-extended-command-1 ; Function to perform the action
     (format "Execute command%s: "     ; `completing-read' args
             (if current-prefix-arg
                 (format " (prefix %d)" (prefix-numeric-value current-prefix-arg))
                ""))
     obarray 'commandp t nil 'extended-command-history nil nil
     ((last-command last-command))    ; Save & restore `last-command'
      (use-file-dialog nil)           ; For mouse-2 in *Completions*
      icicle-new-last-cmd)            ; Set in `i-e-e-c-1'
     nil nil                          ; First code, undo code
     (setq this-command icicle-new-last-cmd)) ; Restore last command
  (defun icicle-execute-extended-command-1 (cmd-name)
    "Candidate action function for `icicle-execute-extended-command'."
     (when (get-buffer icicle-orig-buff)
       (set-buffer icicle-orig-buff))
     (when (window-live-p icicle-orig-window)
       (select-window icicle-orig-window))
     (when (string= "" cmd-name) (error "No command name"))
     (let* ((cmd (intern cmd-name))
            (icicle-candidate-action-fn
             (and icicle-candidate-action-fn ; nil after CMD is read
                  `(lambda (x)
                     (setq x (icicle-transform-multi-completion x))
                     (funcall ',cmd x))))
      (run-hooks 'post-command-hook)
      (run-hooks 'pre-command-hook)
       (let ((enable-recursive-minibuffers t)
             (this-command cmd))
         (call-interactively cmd 'record-it))
       (setq icicle-new-last-cmd  cmd)))

Variables ‘icicle-orig-buff’ and ‘icicle-orig-window’ are bound automatically by macro ‘icicle-define-command’ to the buffer and window where the multi-command (‘icicle-execute-extended-command’ in this case) was invoked.

The last several lines of the action function definition rebind ‘icicle-candidate-action-fn’ to a function that calls the candidate command on a single argument that it reads. This is useful if that command itself reads an input argument with completion. When that is the case, you can use completion on that input, and if you do that, you can use the candidate command as a multi-command. In other words, this binding allows for two levels of multi-commands.

There are a few things wrong with this definition, however. In the action function, the candidate command is applied to a candidate that is a string. What if it is a command, like ‘describe-variable’, that expects a symbol argument? Or a number argument? There is no way to know what kind of command will be used, and what kind of argument it will need. The solution is to first try a string candidate argument, then convert the string to a symbol or number. That is, bind this to ‘icicle-candidate-action-fn’:

 (lambda (x)
   (setq x  (icicle-transform-multi-completion x))
   (condition-case nil
       (funcall ',cmd x)                          ; Try to use a string candidate
     (wrong-type-argument                         ; If that did not work, use a
      (funcall ',cmd (car (read-from-string x)))))) ; symbol or number candidate.

A similar problem occurs if the action function called does not accept a (single) argument. The best thing to do in this case is punt – call ‘icicle-help-on-candidate’ to display help on the candidate. To the code above, we add another error handler:

 (wrong-number-of-arguments (funcall #'icicle-help-on-candidate))

And what if the command does something that changes the focus away from the minibuffer’s frame? That’s the case for ‘describe-variable’, for instance: it selects buffer ‘*Help*’. To fix this potential problem, the action function resets the focus back to the minibuffer and its frame:

 (lambda (x)
   (setq x  (icicle-transform-multi-completion x))
   (condition-case nil
       (funcall ',cmd x)
     (wrong-type-argument (funcall ',cmd (car (read-from-string x)))))
     (wrong-number-of-arguments (funcall #'icicle-help-on-candidate))
   (select-window (minibuffer-window))
   (select-frame-set-input-focus (window-frame (minibuffer-window))))

The actual definitions of the action function and the main command are even more complex. They need to take into account various subtleties, including those associated with recursive minibuffers and multiple invocations of ‘completing-read’. Evaluate (symbol-function 'icicle-execute-extended-command) to see the real definition.

See Also:


Previous: Icicles - Multiple-Choice MenusIciclesIciclesIndexNext: Icicles - Multi-Commands the Hard Way

DrewsElispLibraries referenced here: Lisp:icicles.el

CategoryCommands CategoryBufferSwitching CategoryCompletion CategoryRegexp CategoryDirectories CategoryFiles CategoryProgrammerUtils CategoryCode