Update (2013-01-23): The new org-mode removed (org-make-link), so I replaced it with (concat) and uploaded a new example-file: org-custom-link-completion.el.
I recently set up custom completion for two of my custom link types in Emacs org-mode. When I wrote on identi.ca about that, Greg Tucker-Kellog said that he’d like to see that. So I decided, I’d publish my code.
The link types I regularly need are papers (PDFs of research papers I take notes about) and bib (the bibtex entries for the papers). The following are my custom link definitions :
(setq org-link-abbrev-alist '(("bib" . "~/Dokumente/Uni/Doktorarbeit-inverse-co2-ch4/aufschriebe/ref.bib::%s") ("notes" . "~/Dokumente/Uni/Doktorarbeit-inverse-co2-ch4/aufschriebe/papers.org::#%s") ("papers" . "~/Dokumente/Uni/Doktorarbeit-inverse-co2-ch4/aufschriebe/papers/%s.pdf")))
For some weeks I had copied the info into the links by hand. Thus an entry about a paper looks like the following.
* Title [[bib:identifier]] [[papers:name_without_suffix]]
This already suffices to be able to click the links for opening the PDF or showing the bibtex entry. Entering the links was quite inconvenient, though.
The trick to completion in org-mode is to create the function org-LINKTYPE-complete-link.
Let’s begin with the papers-links, because their completion is more basic than the completion of the bib-link.
First I created a helper function to replace all occurrences of a substring in a string1.
(defun string-replace (this withthat in) "replace THIS with WITHTHAT' in the string IN" (with-temp-buffer (insert in) (goto-char (point-min)) (replace-string this withthat) (buffer-substring (point-min) (point-max))))
As you can see, it’s quite simple: Just create a temporary buffer and and use the default replace-string function I’m using daily while editing. Don’t assume I had figured out that elegant way myself. I just searched for it in the net and adapted the nicest code I found :)
Now we get to the real completion:
<<string-replace>> (defun org-papers-complete-link (&optional arg) "Create a papers link using completion." (let (file link) (setq file (read-file-name "papers: " "papers/")) <<cleanup-link>> link))
The real magic is in read-file-name. That just uses the file-completion with a custom command prefix.
cleanup-link is only a small list of setq’s which removes parts of the filepath to make it compatible with the syntax for paper-links:
(let ((pwd (file-name-as-directory (expand-file-name "."))) (pwd1 (file-name-as-directory (abbreviate-file-name (expand-file-name "."))))) (setq file (string-replace "papers/" "" file)) (setq file (string-replace pwd "" (string-replace pwd1 "" file))) (setq file (string-replace ".pdf" "" file)) (setq link (concat "papers:" file)))
And that’s it. A few lines of simple elisp and I have working completion for a custom link-type which points to research papers - and can easily be adapted when I change the location of the papers.
Now don’t think I would have come up with all that elegant code myself. My favorite language is Python and I don’t think that I should have to know emacs lisp as well as Python. So I copied and adapted most of it from existing functions in emacs. Just use C-h C-f <function-name> and then follow the link to the code :)
Remember: This is free software. Reuse and learning from existing code is not just allowed but encouraged.
For the bib-links, I chose an even easier way. I just reused reftex-do-citation from reftex-mode:
<<reftex-setup>> (defun org-bib-complete-link (&optional arg) "Create a bibtex link using reftex autocompletion." (concat "bib:" (reftex-do-citation nil t nil)))
For reftex-do-citation to allow using the bib-style link, I needed some setup, but I already had that in place for explicit citation inserting (not generalized as link-type), so I don’t count following as part of the actual implementation. Also I likely copied most of it from emacs-wiki :)
(defun org-mode-reftex-setup () (interactive) (and (buffer-file-name) (file-exists-p (buffer-file-name)) (progn ; Reftex should use the org file as master file. See C-h v TeX-master for infos. (setq TeX-master t) (turn-on-reftex) ; don’t ask for the tex master on every start. (reftex-parse-all) ;add a custom reftex cite format to insert links (reftex-set-cite-format '((?b . "[[bib:%l][%l-bib]]") (?n . "[[notes:%l][%l-notes]]") (?p . "[[papers:%l][%l-paper]]") (?t . "%t") (?h . "** %t\n:PROPERTIES:\n:Custom_ID: %l\n:END:\n[[papers:%l][%l-paper]]"))))) (define-key org-mode-map (kbd "C-c )") 'reftex-citation) (define-key org-mode-map (kbd "C-c (") 'org-mode-reftex-search)) (add-hook 'org-mode-hook 'org-mode-reftex-setup)
And that’s it. My custom link types now support useful completion.
For papers, I get an interactive file-prompt to just select the file. It directly starts in the papers folder, so I can simply enter a few letters which appear in the paper filename and hit enter (thanks to ido-mode).
For bibtex entries, a reftex-window opens in a lower split-screen and asks me for some letters which appear somewhere in the bibtex entry. It then shows all fitting entries in brief but nice format and lets me select the entry to enter. I simply move with the arrow-keys, C-n/C-p, n/p or even C-s/C-r for searching, till the correct entry is highlighted. Then I hit enter to insert it.
And that’s it. I hope you liked my short excursion into the world of extending emacs to stay focussed while connecting seperate data sets.
I never saw a level of (possible) integration and consistency anywhere else which even came close to the possibilities of emacs.
And by the way: This article was also written in org-mode, using its literate programming features for code-samples which can actually be executed and extracted at will.
To put it all together I just need the following:
Now I use M-x org-babel-tangle to write the code to the file org-custom-link-completion.el. I attached that file for easier reference: org-custom-link-completion.el :)
Have fun with Emacs!
PS: Should something be missing here, feel free to get it from my public .emacs.d. I only extracted what seemed important, but I did not check if it runs in a pristine Emacs. My at-home branch is “fluss”.
1 : Creating a custom function for string replace might not have been necessary, because some function might already exist for that. But writing it myself was faster than searching for it.