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!
 

Conversation
  • Jake Robb says:

    Rename in IntelliJ is not Shift-Function-F6. It’s Shift-F6. But you’ve left your Mac in the default Fkey mode, which is a silly choice for a developer. You need to toggle the setting (I think it’s in the Keyboard prefpane; posting from my iPhone at the moment) so that you hold Function when you want to use the non-Fkey variants. Now you’ll have to hold Function to change volume, brightness, etc, and the keys will act as Fkeys by default.

    Give it a shot. I think you’ll like it.

    • Ken Fox Ken Fox says:

      Yeah, I reversed the meaning of the fn key soon after starting to use IntelliJ. It still annoys me. I really like Intellij, but it should respect the defaults on MacOS. UI just doesn’t seem high on JetBrain’s priority list–better than Eclipse is a low bar. Putting a common feature like rename on shift-F6 is one example, but non-standard command keys, such as command-O, are worse.

      I switch back and forth between the built-in keyboard and an external keyboard (a Das Keyboard model S which I adore). Function keys are difficult to touch type, so I’ve starting rebinding refactoring commands to a control-C prefix, e.g.:

      control-C r = rename
      control-C i = inline
      control-C m = extract method

      I would really like to use “control-C r” for the prefix and then a third key for the refactor function, but IntelliJ only allows 2 key commands.

  • Jake Robb says:

    Fair enough. I got my start with IntelliJ when my work computer was a Windows PC. Their default key mapping for Windows is pretty consistent with what a Windows user would expect, and their Mac keymap is a Mac-ish translation from there. It wasn’t hard for me to make the transition when my work replaced my HP desktop with a Mac.

    Coming from any other background, I can see the transition being harder.

    Have you looked into IntelliJ’s plugin API at all? Maybe you can extend the key mapping abilities to support a third keystroke.

    • Ken Fox Ken Fox says:

      Two keystrokes per action is hard-wired into IdeKeyEventDispatcher; I don’t think that’s easy to change. Maybe it’d be possible to use something like AceJump’s input popup to collect a third keystroke and then invoke a synthetic action with a control-alt-shift variant of the second keystroke.

      The keymap would look like this:

      control-C r = extended-keymap-plugin
      control-alt-shift-R r = refactor rename

      I’d hit “control-C r r” to do a rename. Which would be sweet. Thanks for the idea!

  • Thank you very much!

    I recently purchased PyCharm because I absolutely had to debug something quickly, on the server, and ipdb was not enough. Debugging experience was indeed very pleasant – especially having access to symbol definitions, docstrings and usages while debugging.

    But as a tool for text manipulation PyCharm felt so poor compared to my Emacs with years of customizing and many plugins, that I almost regretted spending my money on it. Many useful features are missing entirely, some are available as plugins, some are poorly approximated.

    Your post made me believe that not all is lost. While all your tips are useful, mentioning LivePlugin is by far the most important thing for me. I’m so used to “just write a few lines of Elisp” as a response to any irritating thing (with being able to evaluate a defun and immediately use it; also C-h f, etc. but I guess I’m asking too much) that the thought of having to write *Java* and *develop plugins* for things like delete-horizontal-space was simply frightening. Now, with LivePlugin and its support for Clojure (although it says its experimental, I need to check how well it works) I can probably do the same in PyCharm.

    Anyway, thank you very much, thanks to you I think I will be able to use my PyCharm on a daily basis, instead of running it once in a while for debugging or refactorings.

  • You might be interested in the IdeaVim plugin. It fulfills a similar purpose for Vim users, and its source code might be a treasure in case you want to create a plugin for Emacs users.

  • Adam Almo says:

    Ken, I’ve tried replicating your solution to spawn an emacs window in Intellij. Since I’m using an Unbutu machine I tweaked your script a little bit, ending up with:

    #!/bin/bash

    emacs_full_path=/usr/bin/emacs
    emacsclient_full_path=/usr/bin/emacsclient
    server_dir=$HOME/.emacs.d/server/
    server_name=”IntelliJServer”
    server_full_path=$server_dir$server_name

    if [ ! -e $server_full_path ]; then
    $emacs_full_path –daemon=”$server_name” &
    while [ ! -e $server_full_path ]; do
    sleep 1
    done
    fi

    $emacsclient_full_path \
    -nw \
    –socket-name=$server_full_path “$@”

    And I configured the external tool in IntelliJ per your specifications (except I checked the “output console” box so I could see error outputs).
    When I try to run the external tool, I get the following error message:
    /usr/bin/emacsclient: could not get terminal name

    I haven’t had much luck googling around. Any ideas why I’m having this problem? Thanks a bunch!

  • Mahesh Venkat says:

    Very nice article to integrate emacs with Intellij running on Mac OS.
    Steps 1-3 worked perfectly for me with no issues.
    Thanks for writing very nice practical article on improving source code editing in IntelliJ.

  • Great tip on running Emacs as an external tool!

    You can not only specify the line number, but the column number as well with

    +$LineNumber$:$ColumnNumber$ “$FilePath$”

    See `man emacs` for more on these options.

  • Comments are closed.