Conditional hydra menus

| categories: hydra, emacs | tags:

Usually the hydra menu is hard coded in the defhydra macro. Sometimes, you would like conditional options, that is, depending on some condition we get different options when we run the hydra and not when it was defined. This is an open issue in hydra. Here we explore a way to achieve that. The idea is to construct the code for the hydra, then eval it, and run the hydra. In this example we make the conditional menu depend on whether we are on an even or odd numbered line. I use the `' syntax for defining the list of code. ` is a variation of ' (quote) that enables you to use the , operator to evaluate that element while in data mode. So, here is our first idea:

(defun my-hydra-1 ()
  (interactive)
  (eval
   `(defhydra my-hydra-1 (:color blue) "My hydra"
      ,(if (evenp (line-number-at-pos))
           '("e" (message-box "Even line") "Even")
         '("o" (message-box "Odd line") "Odd"))
      ,(when t '("a" (message-box "always true") "always"))
      ;; This does not work. you must return a legitimate hydra menu item
      ;;      ,(when nil '("n" (message-box "never") "never"))
      ))
  (my-hydra-1/body))

(my-hydra-1)
(my-hydra-1)
my-hydra

As long as it is not expensive to compute the conditionals, this seems like an easy enough way to get conditional options in a hydra. One limitation of the previous approach is our menu conditionals must return a hydra menu, and not nil. Here is an alternative approach to writing the function that solves the issue of the nil return in the last function. Here we build up the code list using append. It might seem like a macro should be used here, but I have not figured out how to get the macro to run the conditionals at the run-time. Note, we cannot use funcall on the defhydra because that is a macro.

(defun my-hydra-2 ()
  (interactive)
  (let ((conditionals '((if (evenp (line-number-at-pos))
                            '("e" (message-box "Even second") "Even")
                          '("o" (message-box "Odd second") "Odd"))
                        (when t '("a" (message-box "always true") "always"))
                        (when nil '("n" (message-box "never") "never")))))
    (eval
     (append
      '(defhydra my-hydra-2 (:color blue) "My hydra")
      (loop for cond in conditionals
            with result = (eval cond)
            if (eval cond)
            collect (eval cond))))
    (my-hydra-2/body)))

(my-hydra-2)
(my-hydra-2)

That works too. Let us try another type of syntax where the conditional statements have a cons cell with a conditional statement, and a hydra menu option for when the statement is true. This is functionally similar to our first method, but has some advantages in brevity and less quoting. We add a conditional hint here too (at some expense of additional quoting).

(defun my-hydra-3 ()
  (interactive)
  (let ((conditionals
         `(((evenp (line-number-at-pos)) . ("e" (message-box "Even second") ,(format "Even: %s" (line-number-at-pos))))
           ((oddp (line-number-at-pos)) . ("o" (message-box "Odd second") ,(format "Odd: %s" (line-number-at-pos))))
           (t . ("a" (message-box "always true") "always"))
           (nil . ("n" (message-box "never") "never")))))
    (eval
     (append
      '(defhydra my-hydra-3 (:color blue) "My hydra")
      (loop for cond in conditionals
            if (eval (car  cond))
            collect (cdr cond))))
    (my-hydra-3/body)))

(my-hydra-3)
(my-hydra-3)

I cannot figure out how to abstract this much further. There is a little redundancy in names, e.g. in the defhydra and at the end, but it is not too bad, which would usually be handled by a macro. I tried some defmacros to try this, but I could not figure out how to get the conditionals to expand at the right times, which is at run time, and not at macro expansion time. I need a macro that generates a function that has the call to defhydra in it! Maybe next year ;)

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

Clickable org-contacts in text files

| categories: orgmode, contacts | tags:

Continuing my adventures with clickable text (See clickable email addresses and clickable twitter handles ), here we consider how to get clickable names that are also in my org-contacts database. The goal is to have these names highlighted and clickable so that when I click on them I get a hydra menu of actions, e.g. to open the contact, email them, etc… We will again use button-lock to do the action. We will construct a fairly large regexp to match all the names in the org-contacts database. This turns out to be very easy using the regexp-opt function.

First, I formalize the code I used last time to get text around the point that has a text-property. We will use that to get the text that has been highlighted by button-lock.

(defun get-surrounding-text-with-property (property)
  "Return text surrounding point with the text-property PROPERTY."
  (let ((start) (end))
    (when (get-text-property (point) property)
      (save-excursion
        (while (get-text-property (point) property)
          (backward-char))
        (forward-char)
        (setq start (point))
        (while (get-text-property (point) property)
          (forward-char))
        (setq end (point)))
      (buffer-substring start end))))
get-surrounding-text-with-property

I want to use nicknames that are defined in my org-contacts database. We first try to return an assoc lookup, then the slower approach of looping through the entries to find a matching nickname.

(defun get-contact-from-name-or-nickname (name-or-nickname)
  "Return a contact from the org-contacts database for NAME-OR-NICKNAME."
  (or
   (assoc name-or-nickname (org-contacts-db))
   ;; no assoc, so now we have to find a nickname
   (catch 'contact
     (dolist (contact (org-contacts-db))
       (when (-contains? (s-split "," (or (cdr (assoc "NICKNAMES" (caddr contact))) " ")) name-or-nickname)
         (throw 'contact contact))))))
get-contact-from-name-or-nickname

Now, let us write a hydra function that will be our menu of actions. For some reason, when you click on a highlighted text the mouse moves to the end of the text, so in our hydra function we move back a char, and then get the info. Basically, we get the name, then get the contact, and extract what we need from there. Here we provide functionality to open a contact, email a contact or open the url of the contact (if it exists). I also want a conditional hydra, which doesn't seem to be an option yet, so we we roll our own here. Basically, we construct the code for a hydra, and only add a menu option to open the url if we find one in the contact. We will have to eval the code returned from this function to get the hydra body, and then call the body function in the action function for the highlighted text.

(defun conditional-hydra-actions ()
  "Construct code to create a hydra with conditional options."
  (let ((code  '(defhydra org-contacts (:color blue)
                  "Org contacts")))
    (setq code
          (append
           code
           '(("o" (progn
                    (backward-char)
                    (let* ((name (get-surrounding-text-with-property 'org-contact))
                           (contact (get-contact-from-name-or-nickname name))
                           (contact-marker (nth 1 contact)))
                      (switch-to-buffer (marker-buffer contact-marker))
                      (goto-char (marker-position contact-marker))
                      (show-subtree)))
              "Open contact"))))

    (setq code
          (append
           code '(("e" (progn
                         (backward-char)
                         (let* ((name (get-surrounding-text-with-property 'org-contact))
                                (contact (get-contact-from-name-or-nickname name))
                                (email (cdr (assoc "EMAIL" (caddr contact))))))
                         (mu4e~compose-mail email))
                   "Email contact"))))

    ;; conditional menu for opening a URL
    (let* ((name (get-surrounding-text-with-property 'org-contact))
           (contact (assoc name (org-contacts-db)))
           (url (cdr (assoc "URL" (caddr contact)))))
      (when url
        (setq code
              (append
               code '(("w" (progn
                             (backward-char)
                             (let* ((name (get-surrounding-text-with-property 'org-contact))
                                    (contact (get-contact-from-name-or-nickname name))
                                    (url (cdr (assoc "URL" (caddr contact)))))
                               (if url
                                   (browse-url url)
                                 (message "No url found."))))
                       "Open in browser"))))))
    code))
conditional-hydra-actions

I also want to have nicknames in this list, because sometimes I don't use the full names in my contact database. These are stored in a comma-separated property called NICKNAMES in entries that have them. A subtle point here is that it complicates looking up the contact in the database. Normally, I can get this by a simple assoc lookup. For the nicknames, that will fail, so we need a back up method. Now, the highlighting code. You can make the regexp by passing a list of strings to match to regexp-opt. We get our list of strings from:

(append
 (mapcar 'car (org-contacts-db))
 (let ((nicknames '()))
   (dolist (contact (org-contacts-db))
     (when (assoc "NICKNAMES" (caddr contact))
       (setq nicknames
             (append nicknames (s-split "," (cdr (assoc "NICKNAMES" (caddr contact))))))))
   nicknames))

I am not going to show them here to protect my contacts ;). Now, we create the function that highlights the contacts. and add it as a hook function to text-mode-hook.

(defun highlight-org-contacts ()
  (button-lock-set-button
   (regexp-opt
    (append
     (mapcar 'car (org-contacts-db))
     (let ((nicknames '()))
       (dolist (contact (org-contacts-db))
         (when (assoc "NICKNAMES" (caddr contact))
           (setq nicknames
                 (append
                  nicknames
                  (s-split "," (cdr (assoc "NICKNAMES" (caddr contact))))))))
       nicknames)))
   (lambda ()
     (interactive)
     (eval (conditional-hydra-actions))
     (org-contacts/body))
   :face '((:background "MistyRose1")
           (:underline t))
   :help-echo (format "An org contact")
   :keyboard-binding (kbd "RET")
   :additional-property 'org-contact))

(add-hook 'text-mode-hook 'highlight-org-contacts)

That does it. Now, whenever I open a text-based file, the names that are in my contacts are highlighted and actionable. This should be useful for meeting notes, etc…

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

Clickable email addresses in emacs

| categories: email, emacs | tags:

There are clickable mailto:jkitchin@andrew.cmu.edu links in org-mode, but standalone email addresses like jkitchin@cmu.edu are just ordinary text. Here, I want to explore using clickable text instead. I will use the button-lock package for this. I borrowed an email regexp from EmacsWiki: Regular Expression for this. The idea is to define a regular expression for an email address, and use it to make the emails clickable.

I am still not sure what the canonical way to get the value of the text that was highlighted. Here we use the :additional-property feature to set a property to t, and then use that property to get the characters that have a non-nil "email-address" property. It seems clunky, but it works. The main action is to compose an email in mu4e (my preferred email program in emacs). You could also put a call to helm here, or to a hydra for other options.

I make the email addresses stand out a little by giving them a gray background, and a tooltip so you can see why they are highlighted. I also bind RET so I don't have to use the mouse. Don't forget you can type C-h . to see the local help instead of mousing over it! Finally, we add a text-mode hook so this will get loaded when we open a text file (or one with a mode derived from text-mode like org-mode).

(defun highlight-email-addresses ()
  "Add button to email address. Clicking or RET will open a compose email window."
  (button-lock-set-button
   "\\w+\\(\\.\\w+\\)?@\\(\\w\\|\\.\\)+"
   (lambda ()
     (interactive)
     (let ((start) (end) (email-address))
       (while (get-text-property (point) 'email-address)
         (backward-char))
       (forward-char)
       (setq start (point))
       (while (get-text-property (point) 'email-address)
         (forward-char))
       (setq end (point))
       (setq email-address (buffer-substring start end))
       (mu4e~compose-mail email-address)))
     :face '((:background "gray80") (:underline t))
     :help-echo "click to send mu4e email"
     :keyboard-binding (kbd "RET")
     :additional-property 'email-address))

(add-hook 'text-mode-hook 'highlight-email-addresses)

That doesn't look too bad. Now, anytime I open an org-mode file with an email address in it, the address is highlighted in light gray, and underlined. I can click on it or put the cursor on it and press return and I get a compose email window open, with the email address pre-filled in! I am sure this will have some other applications.

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

ox-pandoc - org-mode + org-ref to docx with bibliographies

| categories: orgmode, pandoc, docx | tags:

There is a new org-mode exporter: ox-pandoc . It seems like it makes it easy to convert org-mode to other formats, including docx, and including references in a bibliography. Let us try it out.

1 The setup

We have to modify org-ref org-ref modifies helm-bibtex to insert citation links. We have to undo that here to insert LaTeX style citations. We do that here so that the key binding for inserting references from org-ref inserts the LaTeX citations. This is necessary for pandoc to convert the reference citations to the bibliography in the docx format. If you do not use org-ref, this is probably not necessary.

(setq helm-bibtex-format-citation-functions
      '((org-mode . (lambda (x) (insert (concat
                                         "\\cite{"
                                         (mapconcat 'identity x ",")
                                         "}")) ""))))
org-mode lambda (x) (insert (concat \cite{ (mapconcat (quote identity) x ,) }))

We have to add ox-pandoc and require it.

(add-to-list 'load-path (expand-file-name "ox-pandoc" starter-kit-dir))
(require 'ox-pandoc)

2 The document

Now, for some text. Grindy wrote this nice paper on approaching chemical accuracy with density functional calculations \cite{grindy-2013-approac}. Two other interesting papers include these ones \cite{guldner-1961,guerrini-2008-effec-feo}.

An equation: \(e^x = 4\).

And a figure with a caption:

Figure 1: Make sure this is in your org-file.

3 Summary

This is better than what I have seen in the past. ox-pandoc has some options that might tailor the bibliography to specific formats. You lose some functionality of org-ref cite links by using raw LaTeX, but if that is not a deal breaker this might be a good way to go for some purposes.

Here is the word document that results from this file: test-doc.docx

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

A python version of the s-exp bibtex entry

| categories: bibtex, ref, python | tags:

In this post we explored representing a bibtex entry in lisp s-exp notation, and showed interesting things that enables. Here, I explore something similar in Python. The s-exp notation in Python is really more like tuples. It looks almost identical, except we need a lot of commas for the Python syntax. One significant difference in Python is we need to define the functions in advance because otherwise the function symbols are undefined. Similar to lisp, we can define the field functions at run-time in a loop. We have to use an eval statement, which some Pythonistas find distasteful, but it is not that different to me than what we did in lisp.

The syntax for "executing" the data structure is quite different than in lisp, because this data is not code in Python. Instead, we have to deconstruct the data, knowing that the function is the first object, and it takes the remaining arguments in the tuple.

Here is the proof of concept:

def article(bibtex_key, *args):
    "Return the bibtex formatted entry"
    return ',\n'.join(['@article{{{0}}}'.format(bibtex_key)] +[arg[0](arg[1]) for arg in args[0]] + ['}'])

fields = ("author", "title", "journal", "pages", "number", "doi", "url", "eprint", "year")

for f in fields:
    locals()[f] = eval ('lambda x: "  {0} = {{{1}}}".format("' + f + '", x)')

entry = (article, "hallenbeck-2013-effec-o2",
         (author, "Hallenbeck, Alexander P. and Kitchin, John R."),
         (title, "Effects of \ce{O_2} and \ce{SO_2} on the capture capacity of a primary-amine based polymeric \ce{CO_2} sorbent"),
         (journal, "Industrial \& Engineering Chemistry Research"),
         (pages, "10788-10794"),
         (year, 2013),
         (number, 31),
         (doi, "10.1021/ie400582a"),
         (url, "http://pubs.acs.org/doi/abs/10.1021/ie400582a"),
         (eprint, "http://pubs.acs.org/doi/pdf/10.1021/ie400582a"))


print entry[0](entry[1], entry[2:])
@article{hallenbeck-2013-effec-o2},
  author = {Hallenbeck, Alexander P. and Kitchin, John R.},
  title = {Effects of \ce{O_2} and \ce{SO_2} on the capture capacity of a primary-amine based polymeric \ce{CO_2} sorbent},
  journal = {Industrial \& Engineering Chemistry Research},
  pages = {10788-10794},
  year = {2013},
  number = {31},
  doi = {10.1021/ie400582a},
  url = {http://pubs.acs.org/doi/abs/10.1021/ie400582a},
  eprint = {http://pubs.acs.org/doi/pdf/10.1021/ie400582a},
}

We can still get specific fields out. Since we used a tuple here, it is not quite as nice as using a dictionary, but it is neither too bad, and it can be wrapped in a reasonably convenient function.

def article(bibtex_key, *args):
    "Return the bibtex formatted entry"
    return ',\n'.join(['@article{{{0}}}'.format(bibtex_key)] +[arg[0](arg[1]) for arg in args[0]] + ['}'])

fields = ("author", "title", "journal", "pages", "number", "doi", "url", "eprint", "year")

for f in fields:
    locals()[f] = eval ('lambda x: "  {0} = {{{1}}}".format("' + f + '", x)')

entry = (article, "hallenbeck-2013-effec-o2",
         (author, "Hallenbeck, Alexander P. and Kitchin, John R."),
         (title, "Effects of \ce{O_2} and \ce{SO_2} on the capture capacity of a primary-amine based polymeric \ce{CO_2} sorbent"),
         (journal, "Industrial \& Engineering Chemistry Research"),
         (pages, "10788-10794"),
         (year, 2013),
         (number, 31),
         (doi, "10.1021/ie400582a"),
         (url, "http://pubs.acs.org/doi/abs/10.1021/ie400582a"),
         (eprint, "http://pubs.acs.org/doi/pdf/10.1021/ie400582a"))


for field in entry[2:]:
    if field[0] == author:
        print field

def get_field(entry, field):
    for element in entry[2:]:
        if element[0] == field:
            return element[1]
    else:
        return None

print get_field(entry, title)
print get_field(entry, "bad")
(<function <lambda> at 0x1005975f0>, 'Hallenbeck, Alexander P. and Kitchin, John R.')
Effects of \ce{O_2} and \ce{SO_2} on the capture capacity of a primary-amine based polymeric \ce{CO_2} sorbent
None

So, it seems Python can do some things like lisp in treating functions like first-class objects that can be used as functions, or keys. I still like the lisp s-exp better, but this is an interesting idea for Python too.

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 »