Skip to main content

Gnu Emacs With Server Mode on OS-X

I have used the Emacs for Mac OS-X distribution of Gnu Emacs for years and have been quite pleased with it. Emacs' server-mode is great in that it makes for very fast load times and it allows sharing buffer contents, kill rings, etc., between windows. However, I found it a little tricky to get it working perfectly on OS-X. These are my notes on how to install and configure it so that server-mode works correctly on OS-X.

Under server-mode, the emacsclient program is used to edit files. I have another note about how to configure emacsclient as an OS-X application. This may be worth reading as well.

Configuration

  1. Install Emacs for Mac OS-X.

  2. Update your path:

    # Emacs.app binaries must come before /usr/bin
    # Using the apple provided emacsclient with a different
    # emacs in server mode is a common source of problems.
    PATH=/Applications/Emacs.app/Contents/MacOS/bin:$PATH
    
  3. Create a script named ec that invokes emacsclient with desired options. Note that this returns immediately; it does not wait for the edit to complete.

    #!/usr/bin/env bash
    # -n    --> Return immediately, don't wait for edit to complete
    # -a "" --> If emacs is not running in server mode, start it
    # -c    --> create a new frame instead of using existing frame
    emacsclient -n -a "" -c "$@"
    
  4. Create a script named ecw that invokes emacsclient with desired options. Note that this command will not exit until the edit is complete.

    #!/usr/bin/env bash
    # -a "" --> If emacs is not running in server mode, start it
    # -c    --> create a new frame instead of using existing frame
    emacsclient -a "" -c "$@"
    
  5. Many programs use the EDITOR and VISUAL environment variables to determine which editor to use. Set these up to use ecw.

    export EDITOR=ecw
    export VISUAL=ecw
    

Whenever you run ec or ecw, an emacs daemon in server-mode will be started, if one doesn't already exist.

This is the simplest emacs setup that I've used and it works well.

Lagniappe

While not specific to OS-X, this is germane to server-mode emacs.

I'm used to killing buffers with C-x k. When first starting to use server-mode, I started seeing warnings about a client still using the buffer that I was killing. For my (simple) use case, the following allows me to keep using my old muscle-memory to kill the buffer yet avoid the warning:

(add-hook 'server-switch-hook
       (lambda ()
         (when (current-local-map)
           (use-local-map (copy-keymap (current-local-map))))
         (when server-buffer-clients
           (local-set-key (kbd "C-x k") 'server-edit))))

Alternate Configuration

Before I settled on the above approach, I configured OS-X to automatically start emacs in server-mode at boot time. This does work, but it isn't as clean as the above. Here's how to do it:

  1. Create the plist ~/Library/LaunchAgents/org.fsf.gnu.emacs.plist using an editor:

    <?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>org.fsf.gnu.emacs</string>
          <key>ProgramArguments</key>
          <array>
             <string>/Applications/Emacs.app/Contents/MacOS/Emacs</string>
             <string>--daemon</string>
          </array>
          <key>RunAtLoad</key>
          <true/>
          <key>KeepAlive</key>
          <true/> <!-- restart if it terminates -->
          <key>ServiceDescription</key>
          <string>Gnu Emacs Daemon</string>
       </dict>
    </plist>
    
  2. Install the plist:

    $ launchctl load -w  ~/Library/LaunchAgents/org.fsf.gnu.emacs.plist
    

Note: I made up org.fsf.gnu.emacs. There's nothing magic about it; it could be any unique value.

Now OS-X will start emacs in daemon mode and keep it running for you. I have had weird things happen with this approach, which is why I converted to the first method presented.

'Open-with' and emacsclient

After creating an emacsclient app via Platypus, I was disappointed to notice that the app did not appear in the 'Open With' menu when a supported file was control-clicked on.

Here is how I took care of this issue.

/Applications/emacsclient.app/Contents/Info.plist contains an array of file extensions that it knows how to open; they are stored under the CFBundleDocumentTypes key. The array was empty, containing only a *.

I concluded that a suitable array could be copied from Emacs.app and set about trying to insert it into emacsclient's Info.plist. After a few hours of PlistBuddy and plutil, I was unable to get what I inserted as the array to be properly recognized.

This is why I don't like binary / non-editable configuration files. There's no reason that the plist shouldn't be editable text: YAML, XML, JSON, or whatever. A binary format for configuration files that can only be modified with dedicated tools is insane.

I finally dropped back to Python, having bypassed it initially, assuming that programming be the long road to get it done. I was surprised to see that Python includes plistlib in its standard distribution. It made things ridiculously easy.

The following code grabs the array from Emacs.app, and inserts it into a copy of Info.plist taken from emacsclient.capp. It saves the modified result as Info.plist.new in the current directory.

#!/usr/bin/env python

# Run this script from the command line with the path to the Emacs.app
# Info.plist as the first argument and the path to the emacsclient.app
# Info.plist as the second argument.  'Info.plist.new' will be created
# in current direcotory.

import sys, plistlib, os

(pl_from, pl_to) = sys.argv[1:]

plf = plistlib.readPlist(pl_from)
plt = plistlib.readPlist(pl_to)

plt['CFBundleDocumentTypes'] = plf['CFBundleDocumentTypes']

plistlib.writePlist(plt, os.path.basename(pl_to) + '.new')

When this completes, copy the resulting Info.plist.new to emacsclient.app/Contents/Info.plist (after making a backup copy).

So that OS-X picks up the change, it is necessary to run lsregister, which causes Launch Services to rebuild its database. Here's how:

# Valid for OS-X 10.11.2.  If the path is invalid on yours,
# run 'locate lsregister' to find it.

% F=/System/Library/Frameworks/CoreServices.framework
% F+=/Versions/A/Frameworks/LaunchServices.framework
% F+=/Versions/A/Support/lsregister

% $F -lint -kill -r -domain local -domain system -domain user

This may take 15-30 seconds to run. After that, emacsclient will appear in your 'Open With' menu the next time you use it; it's worth the effort.