9 Comments

Customizing IntelliJ for Emacs Compatibility

xkcd.comAtomic Object has a great tradition of making and customizing tools. It’s a habit I’ve gained and lost several times over my career — keeping tools sharp requires time and effort. Fortunately, one of the best things about becoming an Atom is that all of my colleagues care deeply about software, so it’s easy to find inspiration to start making tools again.

Recently, I’ve been working on a modern Java web app, and I ended up going all-in on IntelliJ 13. It’s a great development environment for mixed language web development (and Android!). The biggest down-side for me is its poor Emacs compatibility out of the box. The pain of hitting keys that don’t work or do something unexpected sent me looking for a cure. As a life-long Emacs user, I just wasn’t willing to down-grade to the Vim plugin, so what started out as key binding tweaks became full-blown plugin development to customize IntelliJ.

My fingers are happy with what IntelliJ can do now, and there’s a clear elisp-style path to add features that I miss.

Simple Keymap Tweaks

I started by changing the key bindings to the built-in Emacs mode via the menu setting IntelliJ IDEA > Preferences > Keymap. My enthusiasm was short lived. These bindings are archaic and don’t support normal Mac OS commands such as copy and undo; Emacs 24 and Aquamacs both support standard OS key commands, and I use them frequently. The built-in Mac OS X 10.5+ bindings feel better, but aren’t complete. I raided the Emacs keymap frequently as I stumbled over missing keys.

There are a few issues that must be solved according to personal taste:

  • IntelliJ has an Escape command, similar to Emacs’ keyboard-quit, but IntelliJ doesn’t use it consistently. I tried changing it to Control-G to free up room for Emacs ESC commands, but it doesn’t work well — many places in the UI hardcode the ESC key. I left the Escape command alone and only use the Option key for Emacs ESC commands.
  • IntelliJ only supports one and two keystroke commands. If you use Emacs packages with deep bindings, this will be a real compatibility problem. I kept Contol-C as a standard command prefix and flattened out the commands I used.
  • The Mac Option key is not consistently recognized. For example, Option-F is detected, but Option-V isn’t and can’t be bound to a command. I solved this by creating a new Mac keyboard layout with John Brownie’s Ukelele utility. The trick is to define all the dead Option key combinations. I simply redefined all Option key combinations to be the same as the plain key. You can grab a copy of my layout from github.com/kenfox/intellij-emacs.
  • IntelliJ is key-command-heavy, which doesn’t leave a lot of room for Emacs commands. Contrary to popular belief, mouse commands are not slow, so I removed many key bindings for specialized, lesser-used commands. Others are ridiculous (Shift-Function-F6 to rename a symbol, for example), but I’m leaving them for now. I found a third party plugin named “Key Promoter” which proved very helpful in learning IntelliJ commands.

Here are some key bindings I added to the built-in Mac OS X 10.5+ keymap:

Edit > Paste C-y
Edit > Delete C-d
Editor Actions > Toggle Sticky Selection C-SPC
Editor Actions > Page Down C-v
Editor Actions > Page Up M-v
Editor Actions > Move Caret to Text Start M-<
Editor Actions > Move Caret to Text End M->
Editor Actions > Scroll to Center C-l
Edit > Find > Find… C-s
Edit > Find > Find Next C-s
Edit > Find > Find… C-r
Edit > Find > Find Previous C-r
Window > Editor Tabs > Split Horizontally C-x 2
Window > Editor Tabs > Goto Next Splitter C-x o
Window > Editor Tabs > Close C-x 0
Window > Editor Tabs > Unsplit All C-x 1
Edit > Macros > Play Back Last Macro C-x e
Edit > Macros > Start/Stop Macro Recording C-x (
Edit > Macros > Start/Stop Macro Recording C-x )
View > Quick Documentation C-h i
Refactor > Rename… C-c r
External Tools > Emacs C-c e

IntelliJ Plugins

Changing key bindings only got me so far — it still wasn’t enough. Fortunately, JetBrains has a very impressive extension API for customizing IntelliJ, and the source to the Community Edition is available under an Open Source license. Less fortunately, the documentation is sparse, and examples are not easy to find. Reading the source is the best way to understand the extension API. I highly recommend checking the code out and opening it as a project.

IntelliJ also has a third party plugin repository that is available via the menu IntelliJ IDEA > Preferences > Plugins > Browse repositories. I couldn’t find an Emacs plugin like Emacs+ for Eclipse. In general the plugin repository doesn’t seem as well-stocked as the Eclipse marketplace nor the Emacs package repos such as MELPA and Marmalade.

However, there is a great plugin called LivePlugin that allows scripting IntelliJ with Groovy and Clojure. It gives the user a similar development process to using elisp: write an extension at any time without needing to compile, package or install it.

LivePlugin Scripting with Groovy

Traditional IntelliJ plugin development is fairly heavy weight. You need to create a plugin project using an IntelliJ template and build it similar to a Java app. LivePlugin makes this much easier by allowing you to script a plugin with Groovy directly in any project you are working on. If you need a small utility, you can just pop open an editor and write the code. The debugging capabilities are limited, but you have full access to the entire IntelliJ extension API.

Here’s a simple command to introduce LivePlugin. It deletes the character under the cursor (“at point,” in Emacs terminology). This is the full code. Build or packaging files are not used. Just open the LivePlugin tool window, hit “+” to create a new plugin, and paste in the code. When you run this plugin, it binds delete-char to the keystroke C-d so it works just like Emacs.

Here’s a clone of Emacs’ delete-horizontal-space function, again showing all the code:

That can be made much simpler by using a Groovy regex to find the matching space at point, but I wanted to see how similar I could get to the Emacs elisp version:

Here’s a command I made on-the-fly while doing some work with Javascript and RequireJS. My problem was the imports in the project were inconsistent and I was manually reformatting them. Now I can cleanup the imports by just hitting Option-=.

LivePlugin is available in the IntelliJ plugin repository and is very easy to install. There’s no additional setup needed to start writing plugins, but I found it very helpful to checkout the IntelliJ community edition so I could browse the source to the API. The source to LivePlugin is also available at github.com/dkandalov/live-plugin.

I’ve written several small plugins using LivePlugin to make IntelliJ more friendly to Emacs users. Some of these have reasonable built-in IntelliJ implementations, but it was useful to reimplement them to see how well the extension API works. The code is up on github at github.com/kenfox/intellij-emacs.

Running Emacs from IntelliJ

Even with the impressive customization IntelliJ allows, I still miss features and packages in Emacs. The best solution I’ve found is to use IntelliJ IDEA > Preferences > External Tools to add a command that runs emacsclient. I already hinted at this by showing C-c e as my key binding to run Emacs.

The trick to making this work well is to set the external tool’s parameters to +$LineNumber$ "$FilePath$". When I hit Control-C E, an Emacs window appears with the same file and cursor position as it was in IntelliJ. After I’m done editing, I save the file in Emacs, Command-TAB back to IntelliJ, and my changes load automatically.

Setting this up requires more configuration than writing LivePlugin code! If you haven’t used emacsclient before, try following my setup:

1. Add some code to your ~/.emacs startup file so Emacs will listen for commands:

2. Wrap emacsclient in a small shell script named e to make it easier to use:

3. Use IntelliJ IDEA > Preferences > External Tools to add a new command:

IntelliJ External Tool dialog

Now Go Write Some Code

You now have a nice, comfortable IntelliJ environment with a quick escape hatch to real Emacs. I hope your fingers are as happy as mine.

Enjoy!