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.
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.
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).
(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:
- Directly from The Omni Group (Mac)
- The Mac App Store (Mac)
- The App Store on iTunes (iPhone and iPad, universal)
References:
- Sending text from Emacs to OmniFocus by Ken Case on the Omni Group Forums.
- Omnifocus Quick Entry from Emacs by Tim Prouty.
- omnifocus.el by Rob Bevan on GitHub.