Defining custom handlers for use with org-protocol
org-protocol intercepts calls from emacsclient to trigger custom actions without external dependencies. Please refer to this file for the basic setup required.
You might want to watch the screencast on youTube.
Defining custom handlers
org-protocol
scans the list of filenames passed to the emacs-server for
"org-protocol:/sub-protocol:/
" and triggers actions associated with
sub-protocol
through the custom variable org-protocol-protocol-alist
.
To defun a custom org-protocol handler basically means to define two basic elements:
- a sub-protocol that triggers the action
- a function that consumes the data (i.e. the part of an URL that follows
"
org-protocol://sub-protocol://
")
To install the custom handler's protocol, we add an entry to
org-protocol-protocol-alist
:
(add-to-list 'org-protocol-protocol-alist '("Hello World" :protocol "hello-world" :function my-hello-world))
The :protocol
property is the sub-protocol, that triggers the action. Note,
that names of protocols (or URL schemes) are only allowed to consist of a
restricted set of characters. See rfc1738, section 2.1.
The :function
is an arbitrary function that takes exactly one argument: the
string that follows our protocol, found in a filename passed to emacs through
emacsclient. All the three standard handlers split and decode that string
using a helper function in org-protocol.el
:
org-protocol-split-data (data &optional unhexify separator)
You may use different separators for your custom handlers and pass them to
org-protocol-split-data
.
Here is a simple definition:
(defun hello-world (data) "Say hello to the world." (message data) nil)
Now the URL org-protocol://hello-world://encoded-data
will call our function
with the string "encoded-data
". Hence an
emacsclient org-protocol://hello-world://encoded-data
will put "encoded-data
" into the minibuffer.
Killing the client
If your handler uses interactive functions that could be canceled by the user
by typing 'C-g
', consider to supply the ':kill-client
' property when you
define the protocol.
This is what we did for the capture handler:
(defconst org-protocol-protocol-alist-default '(("org-capture" :protocol "capture" :function org-protocol-capture :kill-client t) ;; ... ))
Otherwise, if the user has an interactive property defined in her capture
template, discarding it through 'C-g
' would lead to emacsclient waiting for
ever, thus to the appropriate questions when exiting emacs.
All filenames passing from emacsclient to the emacs will be ignored if you
set :kill-client
to a non-nil value.
Return values
Note, that our hello-world
handler explicitly returns nil
. This tells
org-protocol
to remove the filename from the list of files passed to the
emacs-server. If more than one filename was supplied, all those filenames are
searched for protocols. Only filenames without protocols are passed to the
emacs-server as usual.
Another possible return value is a string. If the string is a valid filename,
and if that file can be read, org-protocol
replaces the original filename with
the one returned from the handler.
Using more than one value
Passing one argument to our custom handler is nice, but sometimes more parameters are needed. We would have to encode the the data and split it into parts using a separator.
This is where org-protocol-split-data
comes into play. It takes a string as
its first argument, an optional parameter to tell if the string should be
considered URL-encoded UTF-8 text and finally an optional separator. By
default, no URL-encoding is assumed and '/
' is used as the separator.
The return value is a list of strings. If a non-nil value is supplied as the
second argument, each elements of the returned list will be URL-decoded using
org-protocol-unhex-string
. If the second argument is a function, that function
is used to decode each element of the list. The function should take a string
as its only parameter, and return the decoded value 1.
This is a rewrite of our handler:
(defun hello-world (data) "Say hello to the world." (let* ((parts (org-protocol-split-data data nil '::my-separator::')) (one (car parts)) (two (cadr parts)) (three (caddr parts))) ;; ... do something with one, two and three ) nil)
Using more than one value the greedy way
Finally, it is possible to define a greedy handler. Basically it will discard all the filenames from the servers list of files that follow the filename that triggered the handler.
A handler is greedy, if you add the :greedy
property to
org-protocol-protocol-alist
, regardless of its return value:
(add-to-list 'org-protocol-protocol-alist '("Greedy" :protocol "greedy" :function my-greedy-handler :greedy t))
The one argument to greedy handlers is the rest of the list of filenames, the one that triggered the handler included. But read on, please.
The list of filenames
Here I have to admit, that I was lying all the time. emacsclient does not pass a list of filenames to the emacs-server. It's a list of lists. And the list is the list of emacsclient's arguments reversed.
As an example, the following commandline:
emacsclient org-protocol:/greedy:/one two three +15:42
is passed as
((/dir/three (15 . 42)) (/dir/two) (/dir/org-protocol:/greedy:/one))
to the emacs-server, where org-protocol
grabs it and reverses it to make it
look like this:
((/dir/org-protocol:/greedy:/one) (/dir/two) (/dir/three (15 . 42)))
This is now, what our greedy handler will receive as its only parameter.
The "/dir/
" prefix is added by emacsclient. It's the absolute path to its
working directory.
You may set org-protocol-reverse-list-of-files
to nil
to inhibit the
reversion. But that leads to unexpected results. In this example, the only
filename left would be the one that triggered the actions. That seems not
very greedy, and reversing the arguments on the commandline seems
unnatural. Note though, that the sequence is not changed for the server.
Flatten the list of arguments
org-protocol.el
provides a function to flatten the list of arguments for
greedy handlers:
org-protocol-flatten-greedy (param-list &optional strip-path replacement)
This function takes the list of lists your greedy handler gets as its only parameter, and turns it into a flat list. Also, all prefixes and protocols are stripped from the element that triggered your handler.
This is, what the first parameter might look like:
(("/dir/org-protocol:/greedy:/one") ("/dir/two") ("/dir/three" (15 . 42)))
If only the first parameter is supplied, org-protocol-flatten-greedy
will
return this list:
("/dir/one" "/dir/two" "/dir/three" 15 42)
If you supply a non-nil value as the second parameter for the function:
("one" "two" "three" 15 42)
And, last not least, if you supply a replacement "REPL-
" (must be a string):
("REPL-one" "REPL-two" "REPL-three" 15 42)
Note, that this works exactly this way regardless of your setting of
"org-protocol-reverse-list-of-files
". The sequence of the returned list will
always reflect the sequence of arguments on the command line.
General remarks
emacsclient compresses double and triple slashes to one. That's why it doesn't really matter how many slashes succeed the scheme part of the URL, also known as protocol.
This behavior is the main reasons, why the slash was chosen as the default separator for data fields. Keeping the slashes is insecure, since some of the data fields could contain double or triple slashes themselves.
Footnotes:
The function feature was added with the Org-mode 6.26 release (commit 6a9acfa9a3ec4ad889951d02c9809f55ac7491fb).