OS X, Emacs, and gnuserv

As part of my final migration to all things Macintosh, I needed to get my text editing situation under control. After developing on various X windows based platforms for the vast bulk of my career, I’m very comfortable with using the command line to navigate around a programming project. My work pattern is typically to move around on the command line until I need to edit a file. Then I have a command line tool that will bring that file up in my chosen editor.

Users of BBEdit on Mac OS X might be familiar with this, as BBEdit includes a nice command line utility that you can use for this. If push came to shove, I could probably live with using BBEdit as my main editor. However, for many years, I’ve been using a shell script wrapper around “gnuclient”, which would bring up an (X)Emacs frame with my file. Very very convenient. This was a practice started in the distant past when I used both XEmacs instead of (GNU) Emacs, and it took forever to launch a new instance of XEmacs (as well as each instance sucking up a lot of memory).

In any case, I wanted to stick with Emacs because 15 years of using it has so deeply ingrained the key combinations that I try and turn on emacs-like key bindings wherever I go. Not the BBEdit is a bad editor, but when you’ve invested in Emacs as much as I have, you tend to want to stick with it for most things. (I do find BBEdit to be a much better option when dealing with HTML and CSS, however).

So, first off was to get a build of Carbon Emacs. I had played with Aquamacs, which is essentially a different build of Carbon Emacs, but it want’s to use ESC as meta by default, and that doesn’t work for me. I had originally tried Aquamacs because my original copy of Carbon Emacs was locked into a particular, weird install location. The current version of Carbon Emacs doesn’t seem to have that restriction, and seems to be quite nice, actually.

Next, was to get gnuserv/gnuclient working. Interestingly enough, OS X 10.4 (and possibly earlier? I haven’t checked) comes with gnuserv and gnuclient already installed, presumably for use with the terminal-based emacs that it ships with (and it works!). Getting it to work with Carbon Emacs was just the trick I was looking for.

Based on previous work, I had downloaded an external gnuserv implementation. Version 3.12.6 didn’t compile cleanly on 10.4, but 3.12.7 did. I don’t think that you need to do this at all, however. Mostly what you need is to give Carbon Emacs access to three elisp files from that gnuserv distribution — which are already in your Tiger system in /usr/share/emacs/21.2/lisp directory. The three files are gnuserv.el, gnuserv-compat.el, and devices.el. You could probably just throw them into your Carbon Emacs’s internal lisp directory, or you can do what I did–put them somewhere in your home directory and get .emacs to point to them. Something like:

(setq load-path (cons (expand-file-name “~/.fsfemacs/pkg/gnuserv”) load-path ))

which assumes that you put them into the ~/.fsfemacs/pkg/gnuserv directory. Modify that to match wherever you put them. Next, put

(gnuserv-start)

somewhere in you .emacs. This will actually cause your Carbon Emacs to run gnuserv in a sub-process. One potential problem remains, however. If you have DISPLAY set in the environment that Carbon Emacs in running it (like I do), using gnuclient will generate an error message, rather than actually work. What is going on is that Carbon Emacs cannot actually connect to an X display (not actually being an X application). There are probably a number of different ways to fix this. How I did it was to patch devices.el:

--- devices.el  2000-01-31 18:12:32.000000000 -0500
+++ devices.el.davidb   2005-06-03 11:09:03.000000000 -0400
@@ -69,7 +69,8 @@
have no effect."
(cond
((and (eq type 'x) connection)
-    (make-frame-on-display connection props))
+    ; (make-frame-on-display connection props))
+    (make-frame props))
((eq type 'x)
(make-frame props))
((eq type 'tty)

Oh, and a helpful hint for any folks (like me) who traditionally defined a lot of binding to function keys: in Carbon Emacs, option+function key seems to register as the function key, and bypasses any OS-level bindings.

Update: for convenience, I’ve put up local copies of the gnuserv-3.12.7 package (unpatched), the dtemacs script, and my gc script.

3 Comments

  1. Joe Hildebrand:

    Actually, emacsclient works just fine, as well. Just add Emacs.app/Contents/MacOS/bin to your path. I use this script, for extra credit:

    #!/bin/sh
    
    ( emacsclient -n $@ >&/dev/null && \
          osascript -e 'tell application "Emacs" to activate' ) || \
        ( open /Applications/Emacs.app && sleep 2 && emacsclient -n $@ )
    

    Adjust the sleep timer for your box…

  2. davidb:

    When I switch from XEmacs to Emacs some time ago (on Linux, for the express purpose of using nxml-mode), I tried emacsclient for a while, but was ultimately unsatisfied. What made gnuclient/gnuserv better for me was that invoking gnuclient always created a new frame, whereas emacsclient did not.

    I really didn’t want the new file to load in the same frame as the old file, although that might work for you. I frequently have a bunch of emacs frames open when I work, and would rather the new file be opened in a new frame. But different folks work differently, and emacsclient/emacsserver is actually easier to get running than gnuclient/gnuserv.

    I still find it kind of weird that Tiger ships with gnuserv.

  3. davidb:

    Oh, and my script (adapted from the dtemacs script) is this:

    #!/bin/sh
    
    GNUCLIENT=gnuclient
    GNUCLIENTPINGOPTIONS="-batch -eval t"
    EMACS=/Applications/Emacs.app
    TIMEOUT=20
    
    # this will either bring Emacs to the foreground or launch it.
    open ${EMACS}
    
    # Try for TIMEOUT seconds to talk to the Emacs process.
    
    count=0
    until ${GNUCLIENT} ${GNUCLIENTPINGOPTIONS} >/dev/null 2>&1 ; do
        if [ ${count} -gt ${TIMEOUT} ] ; then
            echo "gc: error starting Emacs" 1>&2
            exit 1
        fi
        sleep 1
        count=`expr ${count} + 1`
    done
    
    exec ${GNUCLIENT} -q ${1+"$@"}
    

    I was using a similar ‘osascript’ command to Joe’s, but using the open command is faster and does the same thing.

Leave a comment