Helm at the Emacs

| categories: helm, emacs | tags:

I have written several (intro , multiple args , prefix args) times about using Helm in Emacs so far. Today, I want to share a way I use helm to get me where I want to be in Emacs for my daily activities. This came out of a desire to have single command that would give me a lot of options to open exactly the buffer/file I wanted when I need it. I call the command hotspots, and it is bound to f9 for me, so when I press f9 I get a helm buffer to select what I want from.

So, what kinds of things do I want. First, I want to be able to open my mail, calendar, News feed or agenda from this command. Second, I have a list of hotspots I developed using the code at http://ergoemacs.org/emacs/emacs_hotkey_open_file_fast.html , which I want easy access to. Third, I want to be able to open any org-file in my agenda list. Fourth, any bookmark I have, or to set a bookmark. Fifth, I want recent files as candidates. There is certainly some redundancy in their, but that is ok, it gets me where I want to be.

Here is the code that does that for me. There are six helm sources that provide candidates and actions.

(defun hotspots ()
  "helm interface to my hotspots, which includes my locations,
org-files and bookmarks"
  (interactive)
  (helm :sources `(((name . "Mail and News")
                    (candidates . (("Mail" . (lambda ()
                                               (if (get-buffer "*mu4e-headers*")
                                                   (progn
                                                     (switch-to-buffer "*mu4e-headers*")
                                                     (delete-other-windows))

                                                 (mu4e))))
                                   ("Calendar" . (lambda ()  (browse-url "https://www.google.com/calendar/render")))
                                   ("RSS" . elfeed)
                                   ("Agenda" . (lambda () (org-agenda "" "w")))))
                    (action . (("Open" . (lambda (x) (funcall x))))))
                   ((name . "My Locations")
                    (candidates . (("master" . "~/Dropbox/org-mode/master.org")
                                   (".emacs.d" . "~/Dropbox/kitchingroup/jmax" )
                                   ("blog" . "~/blogofile-jkitchin.github.com/_blog/blog.org")
                                   ("ese" . "~/Dropbox/books/ese-book/ese.org" )
                                   ("passwords" . "~/Dropbox/org-mode/passwords.org.gpg")
                                   ("Pycse" . "~/Dropbox/books/pycse/pycse.org")
                                   ("references" . "~/Dropbox/bibliography/references.bib")
                                   ("notes" . "~/Dropbox/bibliography/notes.org")
                                   ("journal" . "~/Dropbox/org-mode/journal.org")
                                   ("tasks" . "~/Dropbox/org-mode/tasks.org")))
                    (action . (("Open" . (lambda (x) (find-file x))))))

                   ((name . "My org files")
                    (candidates . ,(f-entries "~/Dropbox/org-mode"))
                    (action . (("Open" . (lambda (x) (find-file x))))))
                   helm-source-recentf
                   helm-source-bookmarks
                   helm-source-bookmark-set)))

Interesting to me is that there are not a lot of actions in here. I mostly use this command for navigation to various places. For example, I press f9, type meet, and I can quickly get to the meetings file in my agenda list, or I can type the first few letters of a student's name and open the org-file associated with them. Or I press f9 and go down an entry to open my calendar, etc… I find this enormously helpful because it opens these files no matter where I am in Emacs, and it relieves my mind from remembering where they are, or the keystrokes/commands to get to them.

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

helm and prefix functions

| categories: helm, emacs | tags:

Helm modifies how you use prefix arguments in Emacs. A prefix argument is when you type C-u before a command to modify its behavior. There are a few variations of prefix arguments. Basically, pressing C-u once sets a prefix variable to '(4), pressing twice sets it to '(16). Alternatively, C-u 7 sets the prefix to 7. In regular emacs commands, you type the prefix keys before the command. In helm, you type the after you enter the helm selection buffer, and before you press enter or select your action. In helm, you access the prefix arg in the variable helm-current-prefix-arg. Let us look at how you might use it.

We make an action function that does something conditionally depending on the prefix arg. Yes, you could write several functions to accomplish that too, but maybe there is just a little difference that you can use the prefix arg to handle. What you cannot remember 4 prefix options? You do write good doc strings on your functions right ;) If not, you probably ought to write four functions with meaningful names, and meaningful helm descriptions!

(defun action (candidate)
  "Our action function.
with no prefix message no prefix arg
with one prefix arg message C-u
with two prefix args message C-u C-u
with a numeric prefix arg, message the number."
  (interactive "p")
  (cond
   ((eq nil helm-current-prefix-arg)
    (message-box "no prefix arg"))
   ((equal helm-current-prefix-arg '(4))
    (message-box "C-u"))
   ((equal helm-current-prefix-arg '(16))
    (message-box "C-u C-u"))
   (t
    (message-box (format "C-u %s" helm-current-prefix-arg)))))

(setq some-helm-source
      '((name . "HELM at the Emacs")
        (candidates . (1 2 3 4))
        (action . action)))

(helm :sources '(some-helm-source))
C-u (64)

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

helm actions when there is no match

| categories: uncategorized | tags:

Sometimes you run out of matches in a helm selection buffer, and all that is left is the pattern you have typed in. It turns out you can perform some action on that pattern! Why would you do that? Suppose you are searching your bibliography, and you do not find what you are looking for. Then, you may want to send the pattern to Google, or some other search engine to see what comes up.

The key to handling this situation is to use two sources in your helm session. One that works on the candidates and deals with actions on them, and one that has no candidates, and works on the pattern. The variable helm-pattern contains what you typed in. We call the second source the Fallback option. The second source has no candidates, and we use (dummy) in place of the candidates.

It easy to add two sources. Here we define the sources as variables, and use the variables in the :sources list to the helm command.

(defun some-action (arg)
  (message-box "%s\n%s"
    (helm-get-selection)
    (helm-marked-candidates)))

(defun default-action (candidate)
  (browse-url
   (format
    "http://www.google.com/search?q=%s" (url-hexify-string helm-pattern))))

(defvar source1 '((name . "HELM")
                  (candidates . (1 2 3 4))
                  (action . (("open" . some-action)))))

(defvar fallback-source '((name . "fallback")
                          (dummy)
                          (action . (("Google" . default-action)))))

(helm :sources '(source1 fallback-source))
#<process open http://www.google.com/search?q=addtion%20pul>

When you run this, if you run out of search candidates, all that will be left is the fallback option, and when you press enter, it will launch a browser pointing to the google search for your pattern.

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

Handling multiple selections in helm

| categories: helm, emacs | tags:

The basic usage pattern of helm is you run a command which opens a buffer of choices. You enter some text in the minibuffer which eliminates choices that do not match what you type in. You can select multiple choices by using C-spc, or M-a to mark them all. When you press enter, the current selection is sent to the default action defined. The action is a function that does something, usually on the selected item(s). Here, we explore writing the action function to do what we want. The reason this is somewhat tricky is that when you mark an item in helm, the "cursor" moves to the next item, which means when you press enter it may be possible that the current highlighted item is not part of the items you have marked. If your action will perform a delete action, for example, you may have wanted to delete the marked items, and not the current selection! So, what we need is a way to get what we want.

An action function in helm should normally take one argument, which is going to be the currently selected item from helm. However, we can use two different functions to access either the selected item (helm-get-selection) or the marked items (helm-marked-candidates). So, we can write our function to do "do what we mean". Note, even if you do not mark any candidates, (helm-marked-candidates) will return a list that has the current selection in it. So we can write our action function to act on this list so it works on what is marked or what is selected if nothing is marked. That is probably "what we mean".

Here is one way to work on a selection or marked list of selections. We define an action function that takes an arg, but inside we operate on each element of the marked candidates.

(defun some-action (candidate)
  (loop for cand in (helm-marked-candidates)
        do
        (message-box "working on %s" cand)))

(helm :sources '(((name . "HELM")
                  (candidates . (1 2 3 4))
                  (action . (("open" . some-action))))))

Here is an alternative approach. Here we define the action function to work on one candidate. That might be helpful for testing, for example. Then, we use mapc to apply the function to each marked candidate.

(defun some-action (candidate)
  (message-box "single working on %s" candidate))

(helm :sources '(((name . "HELM")
                  (candidates . (1 2 3 4))
                  (action . (("open" . (lambda (candidate)
                                         (mapc
                                          'some-action
                                          (helm-marked-candidates)))))))))

A little more verbose method might be like this. Here we just pull out the lambda function to another function, to make the helm source definition a little shorter. I cannot tell if this is easier to follow, it is just another option.

(defun some-action (candidate)
  (message-box "single2 working on %s" candidate))

(defun some-actions (candidate)
  (mapc 'some-action (helm-marked-candidates)))

(helm :sources '(((name . "HELM")
                  (candidates . (1 2 3 4))
                  (action . some-actions))))

So there you have it. You can select multiple things in helm, and then operate on them with your action function!

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter

Turn an ISBN to a bibtex entry

| categories: bibtex | tags:

Occasionally, I need a bibtex entry for a book. Books are often identified by an ISBN number. Similar to using Crossref to get metadata about a DOI, we can use a web service to get metadata about an ISBN. From that, we might be able to construct a bibtex entry.

Here is an example of what we can get for ISBN 9780309095211. It does not seem to matter if there are dashes in the ISBN or not.

(with-current-buffer
        (url-retrieve-synchronously
"http://xisbn.worldcat.org/webservices/xid/isbn/9780309095211?method=getMetadata&format=json&fl=*")
      (buffer-substring url-http-end-of-headers (point-max)))
{
 "stat":"ok",
 "list":[{
	"url":["http://www.worldcat.org/oclc/224969280?referer=xid"],
	"publisher":"National Academies Press",
	"form":["BC"],
	"lccn":["2006016786"],
	"lang":"eng",
	"city":"Washington, D.C.",
	"author":"Committee on the Guide to Recruiting and Advancing Women Scientists and Engineers in Academia, Committee on Women in Science and Engineering, Policy and Global Affairs, National Research Council of the National Academies.",
	"ed":"[Online-Ausg.]",
	"year":"2006",
	"isbn":["9780309095211"],
	"title":"To recruit and advance women students and faculty in science and engineering",
	"oclcnum":["224969280",
	 "70060944",
	 "756709329",
	 "804792476",
	 "817950524",
	 "833420290",
	 "836338922",
	 "704551455"]}]}

We get a nice json data string back. We can parst that to get an actual data structure.

(with-current-buffer
        (url-retrieve-synchronously
"http://xisbn.worldcat.org/webservices/xid/isbn/9780309095211?method=getMetadata&format=json&fl=*")
      (json-read-from-string
        (buffer-substring url-http-end-of-headers (point-max))))
((list .
       [((oclcnum .
                  ["224969280" "70060944" "756709329" "804792476" "817950524" "833420290" "836338922" "704551455"])
         (title . "To recruit and advance women students and faculty in science and engineering")
         (isbn .
               ["9780309095211"])
         (year . "2006")
         (ed . "[Online-Ausg.]")
         (author . "Committee on the Guide to Recruiting and Advancing Women Scientists and Engineers in Academia, Committee on Women in Science and Engineering, Policy and Global Affairs, National Research Council of the National Academies.")
         (city . "Washington, D.C.")
         (lang . "eng")
         (lccn .
               ["2006016786"])
         (form .
               ["BC"])
         (publisher . "National Academies Press")
         (url .
              ["http://www.worldcat.org/oclc/224969280?referer=xid"]))])
 (stat . "ok"))

Ok, so we should check that stat is ok, then build the bibtex entry. Accessing the metadata below seems pretty hacky; but it works, and I don't understand the deep nesting of results, and there seems to be a vector in there.

(let* ((results  (with-current-buffer
                    (url-retrieve-synchronously
                     "http://xisbn.worldcat.org/webservices/xid/isbn/9780309095211?method=getMetadata&format=json&fl=*")
                  (json-read-from-string
                   (buffer-substring url-http-end-of-headers (point-max)))))
       (status (cdr (nth 1 results)))
       (metadata (aref (cdar results) 0)))

  (unless (string= "ok" status)
    (error "Status is %s" status))

  (concat "@book{,\n"
          (mapconcat (lambda (x)
                       (format "  %s={%s}," (car x) (cdr x)))
                     metadata "\n")
          "}\n"))
@book{,
  oclcnum={[224969280 70060944 756709329 804792476 817950524 833420290 836338922 704551455]},
  title={To recruit and advance women students and faculty in science and engineering},
  isbn={[9780309095211]},
  year={2006},
  ed={[Online-Ausg.]},
  author={Committee on the Guide to Recruiting and Advancing Women Scientists and Engineers in Academia, Committee on Women in Science and Engineering, Policy and Global Affairs, National Research Council of the National Academies.},
  city={Washington, D.C.},
  lang={eng},
  lccn={[2006016786]},
  form={[BC]},
  publisher={National Academies Press},
  url={[http://www.worldcat.org/oclc/224969280?referer=xid]},}

That looks good to me. Let us finally wrap it into a function that will take an ISBN and bibtex file interactively, create a bibtex entry, and insert it if there is not an entry with a key like that already. If we have selected region, lI should note this code uses some functionality from my org-ref package (and when I am done here, I am adding it to the doi-utils package inside org-ref). This is a fancy function, built from the experience I have from writing doi-utils.

(defun isbn-to-bibtex (isbn bibfile)
  "Get bibtex entry for ISBN and insert it into BIBFILE unless an
entry with the generated key already exists in the file."
  (interactive
   (list
    (read-input
     "ISBN: "
     ;; now set initial input
     (cond
      ;; If region is active and it starts with a number, we use it
      ((and  (region-active-p)
             (s-match "^[0-9]" (buffer-substring (region-beginning) (region-end))))
       (buffer-substring (region-beginning) (region-end)))
      ;; if first entry in kill ring starts with a number assume it is an isbn
      ;; and use it as the guess
      ((if (s-match "^[0-9]" (car kill-ring))
           (car kill-ring)))
      ;; type or paste it in
      (t
       nil)))
    (ido-completing-read
     "Bibfile: "
     (append (f-entries "." (lambda (f) (f-ext? f "bib")))
             org-ref-default-bibliography))))

  (let* ((results (with-current-buffer
                      (url-retrieve-synchronously
                       (format
                        "http://xisbn.worldcat.org/webservices/xid/isbn/%s?method=getMetadata&format=json&fl=*"
                        isbn))
                    (json-read-from-string
                     (buffer-substring url-http-end-of-headers (point-max)))))
         (status (cdr (nth 1 results)))
         (metadata (aref (cdar results) 0))
         (new-entry)
         (new-key))

    ;; check if we got something
    (unless (string= "ok" status)
      (error "Status is %s" status))

    ;; construct an alphabetically sorted bibtex entry. I assume ISBN numbers go
    ;; with book entries.
    (setq new-entry
          (concat "\n@book{,\n"
                  (mapconcat
                   'identity
                   (loop for field in (-sort 'string-lessp (mapcar 'car metadata))
                         collect
                         (format "  %s={%s}," field (cdr (assoc field metadata))))
                   "\n")
                  "\n}\n"))

    ;; build entry in temp buffer to get the key so we can check for duplicates
    (setq new-entry (with-temp-buffer
                      (insert new-entry)
                      (org-ref-clean-bibtex-entry)
                      (setq new-key (bibtex-key-in-head))
                      (buffer-string)))
    (find-file bibfile)
    (goto-char (point-min))
    (when (search-forward new-key nil t)
      (beep)
      (setq new-key (read-input
                     (format  "%s already exists. Enter new key (C-g to cancel): " new-key)
                     new-key)))
    (goto-char (point-max))
    (insert new-entry)
    ;; set key. It is simplest to just replace it, even if it is the same.
    (bibtex-beginning-of-entry)
    (re-search-forward bibtex-entry-maybe-empty-head)
    (if (match-beginning bibtex-key-in-head)
        (delete-region (match-beginning bibtex-key-in-head)
                       (match-end bibtex-key-in-head)))
    (insert new-key)
    (bibtex-fill-entry)
    (save-buffer)))
isbn-to-bibtex

That is it, for the one ISBN I have tested it on, I get a nicely sorted bibtex entry in the file I select! Hopefully that means no more tedious bibtex entry entering for books! If you use org-ref, just update to the latest version and you should be able to use this function.

Now, back to that proposal I am writing that needs a lot of citations to books that are not in my bibtex file yet, but will be soon ;)

Copyright (C) 2015 by John Kitchin. See the License for information about copying.

org-mode source

Org-mode version = 8.2.10

Discuss on Twitter
« Previous Page -- Next Page »