Andrey Listopadov

Using a single Emacs instance to edit files

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 files 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 an editor opened somewhere, a 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. The first one is the Emacs server, and the other one is the Emacs client. However, every time when someone mentions the Emacs server, it happens in the context of a 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 the 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 an 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 thought about maintaining a 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 its current form, but if someone has an idea, feel free to open an issue or pull a 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, which 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 is 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 the server is already running, emacsclient will be able to connect to it, but it will not spawn another window, instead it will use a running instance and a 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 a 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 the shell, except 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 the shell function, Emacs will be opened if no instance is running, and the 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 an 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 lave this part for those who would want to try ;)