Skip to main content

Emacs 26 and macOS Mojave

In preparation for moving to Mojave, I'm updating my GNU Emacs installation process.

Server Mode?

Using emacs in server mode reduces startup time dramatically. It also makes sharing content between various frames much nicer (e.g., M-w in one frame can be yanked via C-y in another).

Create an Emacs Server Application

Previously, I would tell emacsclient to start a server if one was not present via the -a "" option, but having macOS start a server at login is actually nicer, I think.

Open the AppleScript editor and paste in the following:

tell application "Terminal"
   do shell script "/Applications/Emacs.app/Contents/MacOS/Emacs --daemon"
end tell

Compile it and save it as an application (Emacs Server.app) in ~/Applications/Development.

You can add the GNU Emacs icon to your new application by:

  • Select /Applications/Emacs.app and press ⌘i.
  • Click the icon in the top left, then press ⌘c to copy it.
  • Select /Applications/Development/Emacs Server.app and press ⌘i.
  • Click the icon in the top left, then press ⌘v to paste it.

Start the Emacs Server at Login

For your user id:

  • Access System -> User & Groups -> Login Items
  • Press + and choose /Applications/Development/Emacs Server.app

You may, of course, double click on Emacs Server.app to start it now, rather than logging out/in.

Create an Emacs Client Application

We now need the ability to open a new frame on the server. Use the AppleScript Editor again to create a script containing:

tell application "Terminal"
    try
        do shell script "/Applications/Emacs.app/Contents/MacOS/bin/emacsclient -c -n &"
        tell application "Emacs" to activate
    on error
        do shell script "/Applications/Emacs.app/Contents/MacOS/Emacs"
    end try
end tell

Compile it and save it as /Applicaitons/Development/Emacs Client.app.

This ensures that Emacs Client.app will appear as the first item in spotlight because 'Development' sorts before 'Emacs', and 'Client' sorts before 'Server'.

File Associations

I also configure macOS to use Emacs Client as the default application for a number of file types (e.g., .py, .txt, .md, ...) so that double-clicking on those files opens them for editing with Emacs.

Add Emacs Client to the Dock

Drag the app to the dock.

The Dock

With the above complete, Emacs Server will always appear in the dock. Clicking on it in the dock will raise any frames, if they exist. If no frames exist, clicking on it does nothing.

Clicking on Emacs Client in the dock will create a new frame on the server and raise the window.

This isn't a perfect scenario, but it's about the best that can be done with macOS.

bash Scripts

Create a few bash scripts to complement what's been done so far. I put these in my ~/bin, which is at the front of my $PATH.

ec

When run from the command line, this uses emacsclient to open a GUI new window (frame) containing a buffer for the file specified as the argument. The script returns immediately, making the command line available for continued use.

#!/usr/bin/env bash

# Open a new frame GUI using emacsclient.  Return immediately.

# -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

/Applications/Emacs.app/Contents/MacOS/bin/emacsclient -n -a "" -c "$@"

ecw

This is similar to ec, except that the script does not exit until the buffer being edited is closed via C-x #.

#!/usr/bin/env bash

# Open a new frame GUI using emacsclient.  Return when buffer is exited.

# -a "" --> If emacs is not running in server mode, start it
# -c    --> create a new frame instead of using existing frame

/Applications/Emacs.app/Contents/MacOS/bin/emacsclient -a "" -c "$@"

emacs

This runs a new character-based Emacs instance, independent of any emacs server, in the current terminal session. The script does not return until Emacs is exited (e.g., via C-x C-s or C-x C-c).

#!/usr/bin/env bash

# Open a new character-based emacs process in the current terminal.
# This does not connect to an emacs server.

/Applications/Emacs.app/Contents/MacOS/bin/Emacs "$@"

Usage

When working at the command line, I usually edit files via ec foo, which opens the file foo in a new window for editing. Because emacsclient returns immediately, I can keep issuing cli commands.

Some commands use the $EDITOR environment variable to determine the editor to run. Generally, these commands assume that when the editor exits, the edit is complete. Clearly, ~/bin/ec is not suitable for this purpose; I use ~/bin/emacs in this situation.

Emacs Anywhere

Ever need to edit text within another application but you'd much prefer to edit the text with Emacs? Now you can. This is based on the Vim Anywhere post on HN. There are two components.

Automator Workflow

Create an Automator Workflow comprised of two parts and saved as ~/Library/Services/EmacsOnSelection.workflow.

Applescript

on run {input, parameters}  
    tell application "EmacsMac" to activate
    tell application "System Events"
        repeat until visible of process "Emacs" is true
            delay 0.5
        end repeat
    end tell
    return input
end run

Bash Script

#!/usr/bin/env bash
tmp=$(mktemp -t EmacsSelection)
ec=/Applications/Emacs.app/Contents/MacOS/bin/emacsclient
cat > "$tmp"
until [ $($ec -q -e '(server-running-p)' 2>/dev/null) == t ]; do
    sleep 1
done
$ec -q -c "$tmp"
rv="$?"
cat "$tmp"
echo 'khe'
if [ $rv -ne 0 ]; then
    echo "Something went wrong.  Please find your text in"
    echo "$tmp"
else
    rm "$tmp"
fi

Resulting Workflow

Within the Applescript Editor, the workflow should end up looking like this:

MacOS Keybinding

Once the workflow is complete, assign it to a key sequence (I chose Ctl-⌘-E) here: SystemPreferences -> Keyboard -> Shortcuts -> Services -> Emacs Anywhere.

References

https://news.ycombinator.com/item?id=16395379

https://github.com/d1egoaz/vim-anywhere

https://github.com/cknadler/vim-anywhere

https://korewanetadesu.com/emacs-on-os-x.html