Using single Emacs instance to edit files
emacs emacs-lisp linux tools ~4 minutes read

Modern text editors usually operate in one instance. When I select some advanced text editor as my preferred editor in the system, I expect this to happen:

  • I click on some file in the file manager;
  • If there’s no editor instance opened, a new instance opens with the file ready to edit;
  • If there is an instance of editor opened somewhere, file is being opened in it, and the editor is brought to me via some focus event.

This is what happens when you associate most of your files to be opened in VSCode for example. But this is not the case for Emacs, which is unfortunate. But, this is Emacs, of course we can change it!

Usually we don’t think about it much, but Emacs comes in two parts. First one is Emacs server, and the other one is Emacs client. However, every time when someone mentions Emacs server, it happens in the context of startup time issue solution – e.g. launch Emacs once, then only connect to the process. And mostly the solution is to run Emacs when the system starts, and only restart it if you’ve updated configuration in a way, when Emacs has to be restarted.

This is not what I want, since I don’t have startup speed issues – my Emacs configuration starts under 1.4 seconds on my machine. However I want Emacs to use one instance unless I explicitly request additional one. I did a bit of searching for a solution, but unfortunately didn’t find any that would satisfy my needs.

Configuring server-mode

To achieve our goal we need to configure server-mode in Emacs. Surprisingly there’s not so much of a configuration needed. I’m using use-package so I’ve added this into my init.el:

(use-package server
  :straight nil
  :config
  (unless (server-running-p)
    (server-start)))

You can see, that we’re starting the server only when there is no other server running. This is important for us, so we still could use multiple sessions without confusion.

Unfortunately other sessions will not use their own servers, but I personally don’t care. I’ve though about maintaining persistent variable that holds server PID, but dropped the idea, since I’ve never actually used two different sessions of Emacs where I would want to connect sessions to different servers. This is definitively the point that can be improved, but it works fine for my needs in current form, but if someone has an idea, feel free to open an issue or pull request at my dotfiles repository so we could discuss it.

Although the server part is not the only configuration we need to perform, otherwise this would not really be enough to make a post :)

System configurations

Another big part is the desktop file, that we will be using to launch Emacs from the desktop environment menus, as well as the function for when we’re launching from the shell.

First the shell function, as it is not so convoluted:

emacs() { emacsclient -a 'emacs' -n "$@" 2>/dev/null || command emacs; }

If you add this function to your .bashrc it will replace emacs command in your shell session. What this command does, is tries to open emacsclient, and connect to the server. We may fail, because there’s no server running yet, and in this case we fall back to command emacs which will launch Emacs, which in turn will start the missing server.

In case server is already running, emacsclient will be able to connect to it, but it will not spawn another window, instead it will use running instance and file will be opened in it. Sweet!

This pretty much defines the behavior I want, but I’m not launching Emacs from shell very often, so I want this behavior to happen when I click on files in my file manager, or when I’ve downloaded something in web browser. For this we will need a very similar thing as our shell function, except it will be the .desktop file.

XDG specification allows us to create emacs.desktop under ~/.local/share/applications/ with these contents:

[Desktop Entry]
Name=Emacs
GenericName=Text Editor
Comment=Edit text
MimeType=text/english;text/plain;text/x-makefile;text/x-c++hdr;text/x-c++src;text/x-chdr;text/x-csrc;text/x-java;text/x-moc;text/x-pascal;text/x-tcl;text/x-tex;application/x-shellscript;text/x-c;text/x-c++;
Exec=sh -c "emacsclient -a emacs -n \"\$@\" || command emacs" dummy %F
Icon=emacs
Type=Application
Terminal=false
Categories=Development;TextEditor;Utility;
StartupWMClass=Emacs

Note, you’ll also need emacs.png icon under ~/.local/share/icons/, so this .desktop file could be displayed with it in the menus.

You can see that we’re using exactly the same function, as in shell, expect we pass the dummy argument, because that’s how it works for some reason. And we have all this other noise around, to correctly display menu entries.

Now we can associate files we want to be opened with Emacs with this .desktop file, and just as with shell function, Emacs will be opened if no instance is running, and existing instance will be used if you click on another file without spawning new windows. With tabs this is really handy, and works exactly how I would expect from advanced text editor.

I think the same trick should be possible to do on other operating systems, but I don’t know their equivalent of .desktop files, so I’ll left this part for those who would want to try ;)