#+OPTIONS: H:3 num:nil toc:t \n:nil @:t ::t |:t ^:t -:t f:t *:t TeX:t LaTeX:t skip:nil d:(HIDE) tags:not-in-toc #+STARTUP: align fold nodlcheck hidestars oddeven lognotestate #+SEQ_TODO: TODO(t) INPROGRESS(i) WAITING(w@) | DONE(d) CANCELED(c@) #+TAGS: Write(w) Update(u) Fix(f) Check(c) #+TITLE: Defining custom handlers for use with org-protocol #+AUTHOR: Sebastian Rose #+EMAIL: sebastian_rose gmx de #+LANGUAGE: en #+PRIORITIES: A C B #+CATEGORY: worg-tutorial [[file:index.org][{Back to Worg's tutorial index}]] org-protocol intercepts calls from emacsclient to trigger custom actions without external dependencies. Please refer to [[file:../org-contrib/org-protocol.org][this file]] for the basic setup required. You might want to watch the [[http://www.youtube.com/watch?v=h7Z2PiAcgh8][screencast]] on youTube. * Defining custom handlers =org-protocol= scanns the list of filenames passed to the emacs-server for "=org-protocol:/sub-protocol:/=" and triggers actions assossiated 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: 1. a sub-protocol that triggers the action 2. 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=: #+begin_src emacs-lisp (add-to-list 'org-protocol-protocol-alist '("Hello World" :protocol "hello-world" :function my-hello-world)) #+end_src The =:protocol= property is the sub-protocol, that triggers the action. Note, that names of protcols (or URL schemas) are only allowed to consist of a restricted set of characters. See [[http://www.ietf.org/rfc/rfc1738.txt][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=: #+begin_src emacs-lisp org-protocol-split-data (data &optional unhexify separator) #+end_src You may use different separators for your custom handlers and pass them to =org-protocol-split-data=. Here is a simple definition: #+begin_src emacs-lisp (defun hello-world (data) "Say hello to the world." (message data) nil) #+end_src Now the URL =org-protocol://hello-world://encoded-data= will call our fuction 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 canceld 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: #+begin_src emacs-lisp (defconst org-protocol-protocol-alist-default '(("org-capture" :protocol "capture" :function org-protocol-capture :kill-client t) ;; ... )) #+end_src Otherwise, if the user has an interactive property defined in her capture template, discarding it through '=C-g=' would leed 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 protocolls 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 [fn:1]. This is a rewrite of our handler: #+begin_src emacs-lisp (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) #+end_src * 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: #+begin_src emacs-lisp (add-to-list 'org-protocol-protocol-alist '("Greedy" :protocol "greedy" :function my-greedy-handler :greedy t)) #+end_src 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 tripple 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 behaviour is the main reasons, why the slash was choosen as the default separator for data fields. Keeping the slashes is insecure, since some of the data fields could contain double or tripple slashes themselves. * Footnotes [fn:1] The function feature was added with the Org-mode 6.26 release (commit 6a9acfa9a3ec4ad889951d02c9809f55ac7491fb).