Emacs and tmux

Date: 16 October 2012

Hello. My name is PerlStalker and I’m an emacs user. I love emacs and use it for nearly everything but there are a few things it’s not good at. (“Like editing,” I hear all you warped vi users cry.) Among them, and most important to me, are window management and terminals.

Let’s start with terminals. I use eshell from time to time to do quick and dirty things on the command line but I always run into weird things that don’t work like I expect. For example, there’s a little one liner I run to convert the mp4 videos of my podcast that I get from YouTube to mp3. Eshell chokes on it. The more powerful and better featured term-mode and it’s more friendly cousin multi-term-mode are pretty good but I still run into tools, from time to time, that break it. (To be fair, it seems to be better in emacs 24 but I haven’t played with it as much.)

My main use of terminals, however, is logging into Linux servers and making changes. I can do weird things with eshell and tramp to edit files but it’s kinda slow. If I try to edit a file on the server through an editor in term-mode, all sorts of things break.

The other thing that emacs is bad at is window management. A little terminology before I go further, emacs uses the term “frames” for what X11 and Microsoft call windows. The term windows is used by emacs when it splits a frame to display multiple buffers. Emacs can split frames horizontally and vertically all day and not have a problem. Where things get hairy is if you use something like gnus which feels like it can do whatever it wants to your window layout at anytime. It’s a real pain in the neck when I have multiple buffers opened, looking at different things, then hit M-x gnus to check my email and boom all of my buffers have been hidden in favor of whatever gnus wants to do. Not cool, emacs. Not cool.

However, something that is good at handling window layouts and shells is tmux. Tmux is similar to screen in that it provides an “always-on” session that you can access from multiple places. Where it beats screen is in its scriptability and window management. Those two features make it especially nice for what I’m going to show you. On a side note, you can apply most of this to screen with a little work but it was really easy to do in tmux.

The super secret ingredient to all of this is emacs server and emacsclient. Emacs server allows you to connect to a running instance of emacs to do things (like edit files or run elisp functions) without starting a whole new emacs instance. That makes it really fast. You can start emacs server by running M-x server-start or have it happen automatically when emacs starts by putting (server-start) in $HOME/.emacs. You can even use emacs --daemon to start emacs in the background when you log in or with the @reboot tag in cron to start it when the machine starts. The --daemon option has the fringe benefit of leaving emacs running even if you log out.

Now to how this all works with tmux. First, I need to redefine “window”. (Don’t you just love overloading definitions?) Basically, tmux only lets you see one window at a time. You can switch between them but you can never see more than one. However, you can split them into “panes” and this is where tmux shines. I’m not going to get into here but you can see the man page to see how easy it is to create, resize and navigate between panes.

The first problem I needed to solve was to quickly and easily ssh into servers. Based on the examples in the docs, I added these lines to my $HOME/.tmux.conf.

bind-key S   command-prompt -p "host" "split-window 'ssh %1'"
bind-key C-s command-prompt -p "host" "new-window -n %1 'ssh %1'"

If I hit the prefix (C-b by default, C-z in my case) followed by C-s, tmux prompts me for a host name (which can also be user@host) and then opens a new window for the ssh session. If I do C-z S instead, it opens the ssh session in a new pane in the same window. Using a pane rather than a new window is useful when I’m checking things on multiple servers at the same time. The window or pane closes when the ssh session is finished.

Now for the fun. Here’s where emacsclient comes in. Let’s say that I want to open emacs inside tmux. By adding this magic to .tmux.conf and reloading the config I can open emacs in a new window (C-z y) or new pane (C-z C-y).

bind-key y   new-window -n "emacs"  "emacsclient -nw"
bind-key C-y split-window "emacsclient -nw"

Emacs opens extremely quickly because it’s already running. Even better, because it’s emacsclient, you can switch to any buffer that you already have open in other clients, even if you opened it in the X11 version of emacsclient.

That’s great but there are other things I want to do in emacs besides edit files. For example, suppose I want to jump into gnus to check my email.

bind-key g   new-window -n "gnus" "emacsclient -nw --eval '(gnus)'"
bind-key C-g split-window "emacsclient -nw --eval '(gnus)'"

C-z g opens gnus in a new tmux window and C-z C-g opens gnus in a new pane.

I’m using a personal convention that whatever key I bind, by itself, opens in a new window and control plus that key opens in a new pane.

If you can script it in elisp, you can make it a shortcut in tmux. I have shortcuts to open w3m …

bind-key W   new-window -n "w3m" "emacsclient -nw --eval '(w3m)'"
bind-key C-w split-window "emacsclient -nw --eval '(w3m)'"

… and I have one to open the RT command line with emacsclient set as the editor in a multi-term buffer. (The editor magic is hidden in a separate script (~/bin/rtc) to get around some restrictions with the RT command line and the EDITOR environment variable.)

(defun rtc ()
  (interactive)
  (if (get-buffer "*rtc*")
      (switch-to-buffer "*rtc*")
    (rtc-create)
    )
)

(defun rtc-create ()
  (eshell t)
  (rename-buffer "*rtc*")
  (goto-char (point-max))
  (eshell-kill-input)
  (insert "~/bin/rtc")
  (eshell-send-input)
)

Then in .tmux.conf:

bind-key C-r split-window "emacsclient -nw --eval '(rtc)'"

Now I can open my ticket list and edit tickets in a pane while I actually work on the ticket in another pane.

You could use this as an example for opening any shell app within emacs. Obviously, if you just want to bring up the app outside of emacs, you can do something magical like this …

bind-key C-m command-prompt -p "man" "split-window 'exec man %%'"

… which prompts you for a man page when you hit C-z C-m then opens it in a new pane. It’s super convenient if you want to check the docs for a tool you’re using. (I could have used emacs man- or woman-mode instead of calling man directly but this was simple and easy.)

If, for some strange reason, you would rather use vi to edit a file, you could simply replace man in the previous command with vi and change the key binding.

For even more special sauce, you could use byobu with tmux to display any number of fun widgets at the bottom of the window. I use a custom script combined with gcalcli to display the next thing I have coming up on my calendar.

The combination of tmux (+byobu) and emacsclient gives me a very efficient and very powerful way to get things done at work. If you’re an emacs user, I highly recommend looking into emacsclient even if you don’t need tmux or screen but combining the two makes for much joy and happiness.