Quick Tip: Getting Emacs and IntelliJ to play together

March 31st 2015 Steve Smith in Emacs, Intellij, Editors

As I've mentioned before, I'm gradually working towards my grey-beard badge so for most of my programming I tend to use Emacs. However when I moved into the order-systems team I adopted IntelliJ IDEA, which is our weapon of choice for Java development at Atlassian. This is because while Emacs is a great text editor, IntelliJ takes a holistic and semantic view of your project, something that is necessary with Java's verbosity and file-based classes. In particular, its on-the-fly tracking of the project syntax tree enables complex refactoring and clean-ups, either automated or by the more brute method of just changing something and seeing what turns red in the editor.

But like western musical notation enables complex harmonic structures at the cost of rhythmic structure, IntelliJ's structured refactoring come at the cost of a really powerful text editor. Sure, IntelliJ has keybindings to match Emacs and Vim, but those editors have other features that enable complex text processing patterns. And sometimes you need to get down and dirty and hack on some text, be it mangling CSV or conforming to some baroque copyright header formatting requirements. In particular, Emacs' keyboard-macros have helped me turn some annoying data and code transformation problems into a few key-presses in the past, as this screencast from Avdi Grimm shows:

Get to the point

Right, enough rationalisation, let's get on with it. What I want to do is do my coding in IntelliJ on a day-to-day basis, but immediately load a file into Emacs for any tricky text processing I want to do. This is how you do it...

Get Emacs to listen for commands

Rather than constantly starting up Emacs every time we want to use it, we're going to keep an Emacs session running in the background and just tell it to load whatever file we want; most Emacs users tend to have a session open for months anyway. So start up an Emacs session if necessary and then tell it to listen for instructions from emacsclient. This is just a case of invoking M-x server-start in an existing Emacs window.

Emacs server-start info

Teach IntelliJ how to send files to Emacs

IntelliJ has a feature called external tools that allows it to invoke external commands with some pre-defined variables, such as the current file path. To use this to send files to Emacs go to Preferences->External Tools. From there click the plus button to add a new tool. Then add the following details:

  • For Program enter the path to your emacsclient binary. For Linux this is usually /usr/bin/emacsclient; however if you're on OS X and using the Cocoa packages you'll need to add the path to the binary from that; e.g. on my Mac it's /Users/ssmith/Applications/Emacs.app/Contents/MacOS/bin/emacsclient.
  • For parameters add -n to have the client not wait, and the $FilePath$ IntelliJ variable to give it the file to load.
  • Tweak the other parameters as you see fit; personally I disable the console as it doesn't add anything.

Emacs External Tool Config

Add a keyboard binding to invoke this tool

Your new tool should now be available in the Tools main menu and under the context (right-click) menu in the tab for any open files. However we can do better than that and add a keyboard shortcut. To do this go to Preferences->Keymap->External Tools and double-click on your new tool. This will pull up a dialog allowing you to add a new keyboard-shortcut; I use Ctrl-Shift-O under Linux and Cmd-Shift-O under OS X, but whatever works for you.

Emacs Keymap Config

Done (for now)

Now you can just invoke your keyboard shortcut in any file you're working on and it will immediately load in Emacs. Once you're done just save the Emacs buffer and switch to IntelliJ and it will pick up the changes.

Advanced tricks

This is probably enough for most people, however there are a few more features we can add for the programmer who likes to tweak things (and if you're using Emacs that's almost certainly you). In particular, as Emacs is immensely programmable via its built-in Lisp engine we can override a few settings in the loaded buffer to better work with the already open file in IntelliJ. Some things we'd like to do:

  • Jump to the same line and column that we were on in the file.
  • Automatically revert the file if we make any changes in IntelliJ (i.e. auto-revert-mode).
  • Raise/focus the Emacs window.

To do this we need to invoke Emacs lisp from emacsclient. While this is possible in the external tools dialog it would be messy, so we're going to write a quick wrapper to make this neater...

Create a wrapper file

I use ~/bin/openinemacs but you can put it anywhere. You should make this file executable with chmod +x <YOURFILE>.

The contents of the file should look like:

#!/bin/bash

file=$1
line=$2
col=$3
/usr/bin/emacsclient -n -e \
    "(progn

       ;; Load the file
       (find-file \"$file\")

       ;; Jump to the same point as in IntelliJ
       ;; Unfortunately, IntelliJ doesn't always supply the values
       ;; depending on where the open is invoked from; e.g. keyboard 
       ;; works, tab context doesn't
       (when (not (string= \"\" \"$line\"))
         (goto-char (point-min))
         (forward-line (1- $2))
         (forward-char (1- $3)))

       ;; Raise/focus our window; depends on the windowing system
       (if (string-equal system-type \"darwin\")
         (ns-do-applescript \"tell application \\\"Emacs\\\" to activate\")
         (raise-frame))

       ;; Automatically pick up changes made in IntelliJ
       (auto-revert-mode t))"

(It would be nice to define this in a .el file and just invoke it or even put emacsclient on the shebang line, but emacsclient has no way of evaluating elisp from a file.)

Add the file as an external tool

This is much the same as the previous version, except that we invoke the wrapper instead and pass it some extra parameters:

  • Set Program to your wrapper file
  • Send the parameters $FilePath$, $LineNumber$ and $ColumnNumber$ for the lisp wrapper to use.

Emacs Advanced Config

Done

That's it. Of course, there are probably many more tweaks that could be performed in the evaluated lisp; feel free to add suggestions in the comments.

Unfortunately I don't know enough Vim magic to come up with a similar recipe for its users, but I'm sure it's possible. Again, feel free to post tips in the comments below.