March 26, 2022
I use Emacs as my daily planner and for a few other things (I might write a separate blog post about this – don’t worry, I still use Vim, I actually use both).
However, one of the problems with Emacs is that it’s a bit slow to start up, especially since I have it refresh packages on startup, which is synchronous.
It gets annoying to have to wait through that startup sequence whenever you want to use Emacs.
A common way to solve this is to have Emacs run automatically when you log in, keep it running in the background as a server, and then connect to the running session with emacsclient
.
But this is not a solution for me, because I don’t want it always running in the background, since there are times when I don’t use Emacs.
Read on to see what I did.
Emacs actually knows it can be slow, and provides a way to run it in the background,
Basically, you run through the init sequence once in an Emacs session, keep that session running, and attach to it or detach from it with emacsclient
.
One way is to run the command:
emacs --daemon
This starts a server in the background, to which you can connect with emacsclient
.
An alternative is to run (server-start)
from inside an Emacs session.
It’s also possible to start a server ‘smartly’ with just the emacsclient
command, as such:
emacsclient -a '' -c
The -a EDITOR
flag specifies an “alternate editor”, and if EDITOR
is an empty string, it runs emacs --daemon
to start Emacs in daemon mode, and tries to connect to it (from man 1 emacsclient
).
So that command either connects to an existing Emacs server, or if there is none, it starts a new one and connects to it.
The flag -c
means to create a GUI frame (you can use -nw
or -t
, which are equivalent to each other, to connect using a terminal session instead of the GUI).
Now that we know about these features, how do we actually use them in practice?
For people that live in Emacs, it’s often preferred to have Emacs run at start-up and keep it always running in the background.
On Linux, you’d create e.g. a systemd service (or the equivalent in whatever init/job scheduling system you use).
On macOS, this would involve creating a plist file in ~/Library/LaunchAgents/
, with contents like this:
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.mine.StartNcmpcpp</string>
<!-- Run as a shell program -->
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/local/opt/util-linux/bin:/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin</string>
</dict>
<key>StandardOutPath</key>
<string>/tmp/emacs.stdout</string>
<key>StandardErrorPath</key>
<string>/tmp/emacs.stderr</string>
<key>ProgramArguments</key>
<array>
<string>emacs</string>
<string>--daemon</string>
</array>
<key>KeepAlive</key>
<false/>
<!-- Run immediately when loaded (on login) -->
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
That would run Emacs on login as a daemon, and then you could use emacsclient
to connect to it.
You can also customize it however you like, launchd.info has good descriptions of the LaunchAgent/LaunchDaemon plist format.
A full explanation of the format and options is out of the scope of this post; I suggest you check out launchd.info (I might also write a post in the future covering the format in more detail).
However, I didn’t go this route, because I only want Emacs running when I actually use it.
There’s two parts to this: starting Emacs, and stopping Emacs.
I launch Emacs using my own “Emacs.app” (see below), which connects to an Emacs server, or starts a new one when it’s not running. That means on first launch, Emacs will take a bit longer to open, but after that it’ll be instant. If I don’t open it at all, Emacs won’t run and consume resources in the background.
To stop Emacs, I use the Emacs keybinding C-x C-c
to close just the current emacsclient
, and C-u C-x C-c
to kill the whole server.
That lets me quickly close any Emacs frames and potentially save buffers, while also allowing me to keep the server running when needed (e.g. to clock time in my agenda).
The first step is launching Emacs, which is quite similar to how it’s done in Linux: essentially just an emacsclient
call.
To do it “the macOS way” (i.e. using an application that I can run from Spotlight), I created an Automator application that just wraps the following shell script:
export PATH=/usr/local/bin:/usr/local/opt/util-linux/bin:$PATH
setsid -f -- emacsclient -c -a '' >/dev/null 2>&1
First, I set PATH to include other locations for binaries – you might need to adjust this depending on where stuff is installed on your system.
Then, with setsid -f
I create a new session with a forked process for the program, which means that Automator doesn’t keep running in the background.
I run emacsclient
with the flags -c
(create a GUI frame) and -a ''
(if an Emacs server isn’t running, start it and then connect to it).
I discard standard output/error, but you could redirect this to log files if you want.
Then I saved this as an application in /Applications, and I blacklisted the original Emacs.app from Spotlight.
Here’s what the application looks like:
When I close Emacs, I want to have the option to either close the current emacsclient
(and keep the server running), or to also terminate the server.
I already have muscle memory for Emacs’ C-x C-c
binding so I want to use that, but I don’t want to override that if I’m not using Emacs as a daemon.
I came up with the following snippet of elisp code, which I have in my config file:
(defun za/emacsclient-c-x-c-c (&optional arg)
"If running in emacsclient, make C-x C-c exit frame, and C-u C-x C-c exit Emacs."
(interactive "P") ; prefix arg in raw form
(if arg
(save-buffers-kill-emacs)
(save-buffers-kill-terminal)))
(if (daemonp)
(global-set-key (kbd "C-x C-c") #'za/emacsclient-c-x-c-c))
First, I define my own function (za/emacsclient-c-x-c-c
), which accepts an optional argument (in practice, it’ll be a single universal argument, C-u
).
The (interactive "P")
means that it’s an interactive function (can be called from a keybinding or via M-x), and that the prefix will be in raw form (that’s not particularly important here, it doesn’t really matter whether the prefix is raw or numeric in this case).
Then there’s a single if-else statement: if an argument is present, ask to save buffers and quit Emacs including the server; if not, ask to save buffers and close the terminal (or the GUI frame).
The actual value of the argument doesn’t matter; it only matters whether there is an argument.
Finally, if Emacs is running in server mode (which can thankfully be checked with the daemonp
function), I rebind C-x C-c
, which normally quits Emacs, to that custom function.
Otherwise, I leave the keybindings as-is.