Anxious Mo-Fo

An anxious m*********** from Seattle

Opening files in an already running instance of Emacs

with one comment

Emacs comes with Emacs server, which allows you to open a file in an already running instance of Emacs. To start Emacs server, you add a line like this to your ~/.emacs file:

(server-start)

From there, you can open a file using the command emacsclient FILE, where FILE is the path to a file. You can also run arbitrary lisp forms using the command emacsclient --eval (lisp-code-here). What emacsclient does not do is start emacs if it isn’t already running.

If you tend to have a lot of windows open, and you don’t want to scan them all to verify whether one of them is Emacs or not, you might prefer to type a single command, or drag and drop onto a single icon, which is smart enough to open a file in an already running instance of Emacs, or start a new instance if Emacs isn’t running. For how to do this on various platforms, read on.

Linux and cousins:

It’s pretty easy. Save this script as a file:

EMACS="/usr/bin/emacs"
EMACSCLIENT="/usr/bin/emacsclient"

if test -n "$1"; then
    $EMACSCLIENT --no-wait "$@" 2> /dev/null || $EMACS "$@" &
else
    $EMACSCLIENT --eval "(raise-frame (selected-frame))" 2> /dev/null || $EMACS &
fi

You may need to edit the paths to emacs and emacsclient at the beginning of the script. When done, run that script with or without filenames as arguments. If run without any arguments, emacs will attempt to raise the active frame above other windows if it’s already running, or a new instance will be started if not. Associating files with such a script in your desktop environment is left as an exercise for the reader.

Mac OS X:

Boy howdy, it’s easy: drag and drop a file onto the Emacs icon in the dock or the Finder. From Terminal.app, if you’re using GNU Emacs, type this command: open -a Emacs, followed by zero or more filenames. If you’re using Aquamacs Emacs, type this command: open -a Aquamacs\ Emacs.

Windows:

Not easy. Step 1: copy and paste this script and save it as a text file with the extension .wsf:

<job id="main">
   <script language="JScript">
       // Globals
       var EMACS_PATH = "C:\\GNUEmacs\\emacs-22.2\\bin\\runemacs.exe";
       var EMACSCLIENT_PATH = "C:\\GNUEmacs\\emacs-22.2\\bin\\emacsclient.exe";
       var WAIT = 100; // number of milliseconds to wait for return
                       // value from command un the runCommand()
                       // function
       var shell = new ActiveXObject("WScript.Shell");

       main();

       function main() {
           if(WScript.Arguments.length == 0) {
               return activateEmacs();
           }
           if(WScript.Arguments.length > 0) {
               var result = 0;
               var fileArgs = "";
               for(var i = 0; i < WScript.Arguments.length; i++) {
                   fileArgs += "\"" + WScript.Arguments(i) + "\" "
               }
               result = openFiles(fileArgs);
               return result;
           }
       }

       // openFiles(FILES)
       //
       // FILES: A string containing the names of one or more files,
       // each file enclosed in quotes, and separated by spaces
       // (e.g., '"C:\foo.txt" "C:\bar.txt"')
       //
       // Opens the files in GNU Emacs
       function openFiles(args) {
           var r = runCommand(EMACSCLIENT_PATH + " --no-wait " + args);
           if(r != 0) {
               r = runCommand(EMACS_PATH + " " + args, false);
           }
           return r;
       }


       // activateEmacs()
       //
       // Launches Emacs, or brings the active frame to the front
       function activateEmacs() {
           var r = runCommand(EMACSCLIENT_PATH +
                           " --eval \"(raise-frame (selected-frame))\"");
           if(r != 0) {
               r = runCommand(EMACS_PATH, false);
           }
           return r;
       }

       // runCommand COMMAND, [WAIT]
       //
       // COMMAND: a string containing a shell command to execute
       // WAIT: Optional. If WAIT evaluates to true, wait for the
       // process to finish execution; if WAIT evaluates to false,
       // do not. If the WAIT argument is not supplied, defaults to
       // true.
       //
       // Executes the command COMMAND, returning the exit code.
       function runCommand(command, wait) {
           var result = shell.Exec(command);
           if(wait == undefined || wait) {
               while(result.Status == 0) {
                   WScript.Sleep(WAIT);
               }
           }
           return result.ExitCode;
       }
   </script>
</job>

Step 2: Edit, if necessary, the EMACS and EMACSCLIENT variables so they point to their actual location.
Step 3: Save this line as a batch file:

cscript /NoLogo "%USERPROFILE%\Batch\Launch Emacs.wsf" %*

Replace %USERPROFILE%\Batch\Launch Emacs.wsf with the actual path you saved the script in. After saving that as a batch file, create a shortcut to the batch file, right click on the shortcut’s properties, and, on the Shortcut bat of the Properties dialog, select Minimized window from the Run: menu. You can then drag and drop files onto the shortcut’s icon, or you can associate files with the batch file.

If, for some reason, you like to use the Windows Command Prompt, you can open files in Emacs from the Command Prompt by typing the same command in the batch file above, but with %* replaced with zero or more filenames. If you would prefer to use a functional shell when you have to use Windows, I would highly recommend cygwin, and see the cygwin section below for a handy shell script.

In case you’re curious as to why it’s a .wsf file which is much more complicated than the UNIX script, (a) I couldn’t figure out how to start a process asynchronously from a batch file, which meant I needed to use a more general scripting language, (b) I coulda used Perl or Python but not all of my PCs have Perl or Python installed, (c) which leaves Windows Script Host JavaScript and Windows Script Host VBScript, (d) I loathe VBScript, (e) if I just saved it as a JavaScript file without the .wsf wrapper stuff, it’s entirely possible that you, reader, have associated JavaScript files with some other application which is not the Windows Script Host.

Cygwin:

If you are running cygwin with a cygin version of GNU Emacs, presumably you can use the UNIX script above. However, if, like me, you use cygwin’s bash shell but you use a non-cygwin Emacs, things are a little more complicated. The non-cygwin Emacs can’t use cygwin’s paths, so they need to be converted back to Windows paths. Luckily, cygwin supplies a command, cygpath, which does just that. Save this as a script:

#!/bin/bash

EMACS="C:/GNUEmacs/emacs-22.2/bin/runemacs.exe"
EMACSCLIENT="C:/GNUEmacs/emacs-22.2/bin/emacsclient.exe"
CYGPATH='/usr/bin/cygpath'

function main() {
    if test -n "$1"; then
        argcount=0
        args=""
        emacs_exec=""
        emacsclient_exec=""

        # For each argument, get the file's absolute Windows path with
        # forward slashes rather than backslashes. Collect them into a
        # string in which each path is enclosed in double quotes and
        # separated by spaces.
        for arg in "$@"; do
            cygpath=`cygpath -ma "$arg"`
            args="$args \"$cygpath\" "
            argcount=`expr $argcount + 1`
        done

        # First, try opening each argument using emacsclient; if that
        # fails, open in a new instance of Emacs
        emacsclient_exec="\"$EMACSCLIENT\" --no-wait $args 2> /dev/null"
        emacs_exec="\"$EMACS\" $args"

        (echo $emacsclient_exec | sh ) || (echo $emacs_exec | sh)
    else
        # Try running emacsclient with an argument which raises the
        # selected frame to the front; if that fails, start a new
        # instance of Emacs
        emacsclient_exec="\"$EMACSCLIENT\" --eval \"(raise-frame (selected-frame))\" 2> /dev/null"
        emacs_exec="\"$EMACS\""

        (echo $emacsclient_exec | sh) || (echo $emacs_exec | sh)
    fi
}

main "$@"

Advertisements

Written by JPP

August 4, 2008 at 9:33 pm

Posted in Emacs

One Response

Subscribe to comments with RSS.

  1. Try not to become a man of success but rather to become a man of value.

    Schuyler

    November 23, 2009 at 3:17 am


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: