This is a newer version of what I wrote on Sunday.
I figured a number of things.
- I’m not going to call this app at random, so I could just as well action something in Emacs when I dismiss the dialog.
- My default does not have to be selected. If it is not selected, I can use the fact that nothing is selected as the default.
So, I decided that dismissing the script would bring Emacs to the front, so that I can just write something without anything captured.
Also, I added an option to display the Capture interactive selector in Emacs, in case I want to capture something that’s not in the list (i.e., something that I would not capture that often).
I also put handlers, to kind of prettify the code.
The next step, as written in the script, is to combine that code with the “Open with Emacs” code that I have and check whether Finder is front, in which case I either open the selection or the enclosing folder.
I’ll try to not scratch that itch before I’m done with urgent work. . .
####################################
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.
#########
set myEmacs to "/.../Emacs.app"
set myEmacsclient to "/.../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 "capture", "todo", "done", "dictionary", "notes" and "drafts".
#
# Then I transform that into a Foundation array
#
# "capture" allows me to display the interactive template chooser.
#########
set myList to {"capture", "à 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 {"c", "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.
#
# I can just hit Enter without selecting anything.
# I'll use that to call the default template, which is "fait".
#
# I can hit the first letter of any item for it to be selected, then I
# hit Enter to simulate clicking on OK.
#
# The "capture" (c) key is special since it allows me to display the
# Emacs interactive template selection dialog.
#
# I can dismiss the dialog with either hitting Escape or clicking on
# Cancel. But since I'm not calling the dialog by mistake, I use that
# action to just bring Emacs to the front.
#
# I'll probably use that in a future version to call Emacs on selected
# file and folders to open them or open dired.
#
# Then I'll need to move all that into a macOS service that I call with
# a simple shortcut so that I don't have to:
# - call Spotlight (Cmd+Space)
# - call the app (>C)
#########
try
set captureChoice to (choose from list myList with title "Emacs" with prompt "Défaut = \"fait\"" with empty selection allowed)
set captureChoice to item 1 of captureChoice
on error
if captureChoice is false then
myEmacsComesForward()
return
end if
if length of captureChoice is 0 then
set captureChoice to "fait"
end if
end try
#########
# 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."
#
# I'm adding a few parameters to see whether I'll use the contents of
# the clipboard or not, and if I want to automatically send the template
# key or let Emacs propose me the interactive selection dialog.
#########
set myTemplateKey to item ((myArray's indexOfObject:captureChoice) + 1) of myKeys
if myTemplateKey is in {"d", "n"} then
set useBody to true
else
set useBody to false
end if
if myTemplateKey is in {"c"} then
set interactiveCapture to true
else
set interactiveCapture to false
end if
#########
# 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.
#########
if useBody is true then
set myTemplateBody to "&body=" & (the clipboard)
else if useBody is false then
set myTemplateBody to ""
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:
#########
if interactiveCapture is true then
set myCaptureCommand to myEmacsclient & " -e \"(org-capture)\" -n"
else if interactiveCapture is false then
set myCaptureCommand to myEmacsclient & " \"org-protocol://capture?template=" & myTemplateKey & myTemplateBody & "\""
end if
#########
# Here, I make sure that Emacs is in front of me.
#
# If Emacs is not launched, it is now launched, which is important,
# because my setting also starts emacs-server, without which I could not
# use emacsclient.
#########
myEmacsComesForward()
#########
# 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.
#########
myEmacsDoesSomething(myCaptureCommand)
#############################################
on myEmacsComesForward()
try
tell application "System Events" to tell process "Emacs" to set frontmost to true
on error
tell application myEmacs
activate
end tell
end try
end myEmacsComesForward
on myEmacsDoesSomething(myCaptureCommand)
try
do shell script myCaptureCommand
on error
log "Emacs au premier plan"
end try
end myEmacsDoesSomething