How to write helm extensions

From WikEmacs
Revision as of 13:35, 2 February 2015 by Elvince (talk | contribs) (helm more than one action)
Jump to navigation Jump to search

This documentation was initiated thanks to John Kitchin's articles on his blog.

So you have been integrating helm into your emacs work flows almost anywhere and you need to make interactive selections and do something with them ? In this post, we will go through the simplest helm examples we can think of that get you to writing your own example.

Basic call

To run a helm selection process you basically just call a function that calls this minimal function:

(helm :sources '(some-helm-source))

In that code, the symbol some-helm-source will provide the input for the helm buffer.

A list of candidates and an action

Let us look at the simplest example here. Each source should have

  • a name,
  • a list of candidates,
  • and an action that works on the selected candidate.

We construct a source as a list of cons cells. Here, we make a source with the name "HELM at the Emacs", a static list of candidates, which are simply a list of numbers, and a single action that will operate on the selected candidate.

If you run this block, you will get a helm buffer, you can select an entry, press enter, and you should see a message box pop up telling you what entry you selected. I like to separate the source definition from the helm call like this, but only for readability.

(setq some-helm-source
      '((name . "HELM at the Emacs")
        (candidates . (1 2 3 4))
        (action . (lambda (candidate)
                    (message-box "%s" candidate)))))

(helm :sources '(some-helm-source))
   3

Dynamic candidates

Not bad, but what if we want some dynamic candidates? The usual way we will do that is to define a function that calculates the candidates for us. Let us work out an example that just shows us random numbers between 0 and 10 to select from. In a real example, you would use this function to generate a list of candidates like bibtex keys, email-addresses, etc…

(defun random-candidates ()
  "Return a list of 4 random numbers from 0 to 10"
  (loop for i below 4 collect (random 10)))

(setq some-helm-source
      '((name . "HELM at the Emacs")
        (candidates . random-candidates)
        (action . (lambda (candidate)
                    (message "%s" candidate)))))

(helm :sources '(some-helm-source))

Separate candidates' sets for searching and for actions

So far, we have looked at the simplest list of candidates: a simple list. It may be that this is not the most convenient way to see the candidates. We might like to have one set of candidates that we use for searching, but another set of equivalent candidates used for the action. For example, we might want a list of names for selecting, but then have the action work on the corresponding email address. Let us consider a case where we have a list of cons cells of names and email addresses.

We use the `, way to create the source variable to make sure our list of candidates is constructed. Then, in our function we take the selection and get the corresponding entry in the data a-list.

(setq data '(("John" . "john@email.com")
             ("Jim" . "jim@email.com")
             ("Jane" . "jane@email.com")
             ("Jill" . "jill@email.com")))

(setq some-helm-source
      `((name . "HELM at the Emacs")
        (candidates . ,(mapcar 'car data))
        (action . (lambda (candidate)
                    (message "%s" (cdr (assoc candidate data)))))))

(helm :sources '(some-helm-source))
   jim@email.com

That is not too bad, and might be a general way to get to the data you want. But, helm can integrate this directly by using the a-list directly as the list of candidates. Helm will show you the car of each cell, but return the cdr of the selected entry.

Let us try this to make a function that will give us a helm buffer to select some names from, and then insert a comma separated list of emails from our selection at the point. We make our action function just return the list of marked candidates. Then we create a function that calls helm, and inserts a concatenated string.

(setq data '(("John" . "john@email.com")
             ("Jim" . "jim@email.com")
             ("Jane" . "jane@email.com")
             ("Jill" . "jill@email.com")))

(setq some-helm-source
      `((name . "HELM at the Emacs")
        (candidates . ,data)
        (action . (lambda (candidate)
                    (helm-marked-candidates)))))

(defun helm-select-and-insert-emails ()
  (interactive)
  (insert
   (mapconcat 'identity
              (helm :sources '(some-helm-source))
              ",")))
   helm-select-and-insert-emails

Here is what I get when we run the command, select John and Jill, and press enter: john@email.com,jill@email.com

More than one action

We now consider how to have more than one action for a selection. When you press enter, helm runs the default action defined, but you can define more than one action, and choose which one to run. How do you know if there are multiple actions? Press C-z in helm and you will get a new helm buffer showing the actions. The first action is the default, and you can select the the actions with function keys, e.g. f1 is the first action, f2 is the second action, or you can select the action and press enter.

The main difference in setting up multiple actions is that instead of a single function for action in the source definition, we provide a list of cons cells for the action element of the helm source. Each action cons cell should have a descriptive string as the car that identifies the action. This will be shown in the helm buffer. The cdr should be the function to run on the candidate. The function will be called with the selection, so the function must take one argument.

Here is an example where we have two actions. The default action will just show us the email address of the selected candidates in a message box. It will show as a list. The second action opens an email window and inserts the selected candidates in the To: field as a comma separated list. I use helm-selected-candidates in these functions instead of the just the current selected candidate so we can have multiple selections. I define the first function as a lambda function, and the second one as a defun to illustrate how to use both approaches. You can have as many actions as you want, so you could consider functions that open notes about the person, or open your contacts to look up a phone number, or functions with template emails you send often, etc…

Now, you have these options to run those actions.

  • Make a selection and press enter. That runs the first (and default) action to show you a message box.
  • Make a selection and press C-z to see what actions are available. Select the action you want, and press enter.
  • Make a selection and press F1 to run the default action, or F2 to run the second action.

Here is our code:

(setq data '(("John" . "john@email.com")
             ("Jim" . "jim@email.com")
             ("Jane" . "jane@email.com")
             ("Jill" . "jill@email.com")))


(defun open-email (candidates)
  "Compose an email to the candidates. Fill in the addresses and
move point to the subject."
  (compose-mail)
  (message-goto-to)
  (insert
   (mapconcat
    'identity
    (helm-marked-candidates)
    ","))
  (message-goto-subject))

(setq some-helm-source
      `((name . "HELM at the Emacs")
        (candidates . ,data)
        (action . (("show email address" . (lambda (candidate)
                                             (message-box
                                              "selected: %s"
                                              (helm-marked-candidates))))
                   ("send email" . open-email)))))

(helm :sources '(some-helm-source))
   t

Now, you can define multiple actions for your selection in helm!