Sending Tasks to OmniFocus from Emacs

August 13, 2015

Wouldn’t it be great if you could take the current region in Emacs and use it to create a new task in OmniFocus? I thought so, and it turns out this problem has been solved at least three times before. However, I either had some trouble getting the previous solutions to work or wished they worked differently. For example omnifocus-capture.el by Ken Case defines a function which sends the region to OmniFocus’s quick entry window, but then you still have to trigger the quick entry keyboard shortcut to save the task to the OmniFocus Inbox (or elsewhere). That introduced too much friction for my taste, so I decided to write a different function based on Ken’s original one.

A Task from Emacs in Quick Entry Limbo
A Task from Emacs in Quick Entry Limbo

The communication between Emacs and OmniFocus needs to happen using AppleScript, so I first borrowed Ken’s applescript-quote-string function for properly escaping strings in AppleScript (with a couple of very minor modifications):

(defun applescript-quote-string (argument)
  "Quote a string for passing as a string to AppleScript."
  (if (or (not argument) (string-equal argument ""))
      "\"\""
    ;; Quote using double quotes, but escape any existing quotes or
    ;; backslashes in the argument with backslashes.
    (let ((result "")
          (start 0)
          end)
      (save-match-data
        (if (or (null (string-match "[^\"\\]" argument))
                (< (match-end 0) (length argument)))
            (while (string-match "[\"\\]" argument start)
              (setq end (match-beginning 0)
                    result (concat result (substring argument start end)
                                   "\\" (substring argument end (1+ end)))
                    start (1+ end))))
        (concat "\"" result (substring argument start) "\"")))))

Then I defined a new send-region-to-omnifocus function. The most important difference for me is that the task is sent straight to the OmniFocus Inbox rather than sitting around in the quick entry window waiting to be entered. This is a personal preference of mine, since I tend to first “capture” tasks quickly and process them later, when I clean them up and assign projects and contexts.

An Inbox Task from Emacs with a Title
An Inbox Task from Emacs with a Title

The new function is also different in how it handles the contents of the region. It uses the first line of the region as the task name and any subsequent lines as the task note. It also appends both a note indicating that the task was added from Emacs and a timestamp. Finally, the new function is also a little more efficient. Ken’s original function created the AppleScript as a string, wrote it to a temporary file, and then ran the script in a shell using osascript. Instead, this version generates the AppleScript in a string and calls do-applescript directly (which perhaps did not exist when the original function was written).

Sending the Emacs Region to OmniFocus
Sending the Emacs Region to OmniFocus
(defun send-region-to-omnifocus (beg end)
  "Send the selected region to OmniFocus.
Use the first line of the region as the task name and the second
and subsequent lines as the task note."
  (interactive "r")
  (let* ((region (buffer-substring-no-properties beg end))
         (match (string-match "^\\(.*\\)$" region))
         (name (substring region (match-beginning 1) (match-end 1)))
         (note (if (< (match-end 0) (length region))
                   (concat (substring region (+ (match-end 0) 1) nil) "\n\n")
                 "")))
    (do-applescript
     (format "set theDate to current date
              set taskName to %s
              set taskNote to %s
              set taskNote to (taskNote) & \"Added from Emacs on \" & (theDate as string)
              tell front document of application \"OmniFocus\"
                make new inbox task with properties {name:(taskName), note:(taskNote)}
              end tell"
             (applescript-quote-string name)
             (applescript-quote-string note)))))

I call send-region-to-omnifocus by pressing C-c o via the following global keybinding:

(global-set-key (kbd "C-c o") 'send-region-to-omnifocus)

You can download both functions here: omnifocus-capture.el.

If you’ve read this far you probably have OmniFocus already. If not, you can purchase the Mac and iPhone/iPad versions at either of these locations:

References:

  1. Sending text from Emacs to OmniFocus by Ken Case on the Omni Group Forums.
  2. Omnifocus Quick Entry from Emacs by Tim Prouty.
  3. omnifocus.el by Rob Bevan on GitHub.