Just about 5 years ago, I wrote a Capture (really) everywhere article where I described a simple AppleScript application that could be launched from Spotlight and that would call org-capture in a running instance of Emacs.
I had a really nice time with that app, until sandboxing became stricter, and it became difficult to reliably control applications with keystrokes sent from System Events.
I eventually rewrote the application, and it basically became a simple call to emacsclient:
set myCaptureCommand to myEmacsclient & " -e \"(org-capture)\" -n"
do shell script myCaptureCommand
Here again, the app worked well, and I could enjoy spending time crafting org-capture templates, like everyone else does when not capturing things.
Then, it occurred to me that instead of
- calling Spotlight,
- calling >Capture.app,
- hitting the template key in Emacs,
- capture my things,
I could create specialized little apps that would call a given template that I often use. Like the "fait" (done) template that I use a number of times during the day to write down what I've accomplished so far.
I started investigating in that direction, trying to use various org-capture options, but I could not make it work. Then I found a number of interesting articles that helped me figure out my new solution:
- How to use org-capture from system tray (2016)
- Some Uses for Org-Protocol (2022)
- The capture protocol (the org-mode manual)
When I read that I could use the org-protocol to set the template key, I immediately thought that I could implement a little interactive template selector in AppleScript that would automatically put me in the right capture buffer.
And that's what I've been doing today, and I'll properly document that in my daily log, with that application, when I'm done with this article...
So, first, here is the app. It's just out of the oven, it works, I've not tested it extensively, so there might be glitches here and there, but it looks OK.
The main difference is that I'm using emacsclient to work with org-protocol here, instead of having it evaluate a call to (org-capture).
When I call it, it displays the following list item selector:
If I have copied something from a document on my machine, the copied contents will be used in the headline for "dictionnaire":
Here, I had copied "統一教会" from an article I was reading, to gather notes about the organisation.And the copied contents will be used in the body of the capture buffer if I selected "notes":
Here, it is a short article about 伊藤詩織 and her recent win in court. Now I just have to put a title and eventually write more about the item.
As usual, the explanations come as comments in the AppleScript code so that you can just copy-paste the thing and test it. Although in this case, there are many things that depend on my setup that I doubt it would work out of the box.
####################################
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use framework "Foundation"
#########
# First, I'll be using the Foundation framework because otherwise it
# would be non-trivial to find "the index of a given item in my list".
# And for that I'll just need to use NSArrays. I did not figure that out
# by myself, I found the hint on stackoverflow.
# reference: https://stackoverflow.com/a/65690139/5511978
#########
property NSArray : class "NSArray"
#########
# Then, I'm setting my paths to Emacs and emacsclient.
# Change the values to something that works for you.
#########
set myEmacs to "/path/to/Emacs.app"
set myEmacsclient to "/path/Emacs.app/Contents/MacOS/bin/emacsclient"
#########
# Here, I create the list of template items I want to quickly access. I
# have many more, but the others are more about long-form writing, so I
# figured I'd be facing Emacs when I need to access them.
#
# This list is basically "todo", "done", "dictionary", "notes" and "drafts".
#
# Then I transform that into a Foundation array
#########
set myList to {"à faire", "fait", "dictionnaire", "notes", "brouillon"}
set myArray to NSArray's arrayWithArray:myList
#########
# Here are the template keys that I use in my Emacs interactive capture
# template.
# Their order corresponds to the order of the above list items.
#########
set myKeys to {"a", "f", "d", "n", "b"}
#########
# Now comes the interactive item selection window. That's the thing
# that's apparently called when I call the app. All the rest takes place
# behind the scenes.
#
# That's the thing that you see above in the first screenshot.
#
# I can hit the first letter of any item for it to be selected, then I
# hit Enter to simulate clicking on OK.
#########
set captureChoice to item 1 of (choose from list myList with title "Capture" default items "fait" with prompt "Choix")
#########
# Here comes the place where I need Foundation's arrays.
#
# Basically, the line says "You just selected something, find its index in the
# list where it belongs, and get me the letter that has the same index
# in the myKeys list."
#########
set myTemplateKey to item ((myArray's indexOfObject:captureChoice) + 1) of myKeys
#########
# For 2 items, "dictionnaire" and "notes", I'm thinking that I'll
# probably have found something in a document that I'm reading, I'll need
# to copy it for use later on in the capture buffer.
#
# In the case of "dictionary", it's a word that I want to check, and I'll
# use that as the title of the captured item.
#
# In the case of "notes", it's probably a sentence, or a paragraph that
# I want to keep in the body of a captured item.
#
# I won't use the selection for the other items.
#
# For that, I'll put the selection (the clipboard) into the &body part of
# the org-protocol command.
#########
set myTemplateBody to ""
if myTemplateKey is in {"d", "n"} then
set myTemplateBody to "&body=" & (the clipboard)
end if
#########
# The &body part, though, is not super clearly explained in the
# org-mode manual:
#
# %i The selected text
#
# Basically, the value of &body is the contents of %i in the capture template.
# I don't know why the manual mentions "the selected text" since you can put
# whatever you want in &body.
#
# In my templates, I've used it this way:
#
# ("n" "choses à noter
# → notes | à lire | code | inspiration" entry (file "~/org/memo.org")
# "* [%u] %?\n%i" :empty-lines 0 :unnarrowed nil)
#
# Here, %i, the contents of the clipboard, will be inserted in the body
# of the capture buffer, under the headline (notice the "\n"). And the cursor
# (%?) is waiting for me inside the headline.
#
# or:
#
# ("d" "Dictionnaire" entry (file+datetree "/Users/suzume/org/dico.org")
# "* [%<%H:%M>] %i\n%?" :empty-lines 0 :unnarrowed nil)
#
# where %i will be inside the headline and the body of the capture
# (supposedly the term definition) will come after the line-break, where
# the cursor is waiting for my input (%?).
#########
#########
# Et voilà !
# The org-protocol command can be built, and sent to emacsclient:
#########
set myOrgProtocolCommand to myEmacsclient & " \"org-protocol://capture?template=" & myTemplateKey & myTemplateBody & "\""
#########
# Here, I make sure that Emacs is in front of me.
#
# If Emacs is not launched yet, it is now launched, which is important,
# because my setting also starts emacs-server, without which I could not
# use emacsclient.
#########
try
tell application "System Events" to tell process "Emacs" to set frontmost to true
on error
tell application myEmacs
activate
end tell
end try
#########
# And last but not least, the org-protocol command is sent to the
# shell, and with Emacs at the front with the cursor waiting for me,
# I can start working right away in my capture buffer.
#########
try
do shell script myOrgProtocolCommand
on error
display alert "ooops"
end try
#############################################