8

The emacsclient program allows a flag --no-wait (abbreviated as -n) which will cause the emacs server to visit the specified file, and return immediately.

emacsclient -n ~/.bashrc

If I provide an alternate editor, then this will still work in cases where there is no running Emacs server

emacsclient -n -a "/usr/local/bin/emacs" ~/.bashrc

However, this gives me inconsistent behavior because in cases where the server is running, this call will return right away. In cases where there is no server running, and the alternate editor is used, the call becomes a blocking call, and will not return until I exit Emacs.

Is there a way to tell emacs (as opposed to emacsclient) to create a new frame and then return?

  • 1
    It looks like you're comfortable providing Emacs as your alternate editor. Is there a reason you're choosing not to use the -a '', "start the Emacs daemon and retry emacsclient" option? – purple_arrows Oct 17 '14 at 7:07
  • @purple_arrows In my experience, using -a '' will start a daemon instead of a server. Then it tries to open a terminal Emacs, but because I have provided the -n option, it does not stay open. It just bounces right back out to the shell. – nispio Oct 17 '14 at 13:28
6

Description

The default behavior when invoking emacsclient is a little conservative. Check out this comment from emacsclient.c:

  /* Unless we are certain we don't want to occupy the tty, send our
     tty information to Emacs.  For example, in daemon mode Emacs may
     need to occupy this tty if no other frame is available.  */

From your description and comments, it sounds like you're trying to start the Emacs server on demand while also using the -n flag. The "for example" comment here is why emacsclient -n -a '' FILE doesn't satisfy what you're looking for when no server is running.

  1. The -a '' logic starts up a daemon.
  2. Then emacsclient tells it to create a new terminal frame, because that's the default unless you're evaluating elisp.
  3. The -n logic immediately kills that new terminal frame.

If you could change Step 2 to create a new graphical frame by default, then emacsclient -n -a '' FILE would do what you want.

Elisp Solution

You can cause Emacs to create a new graphical frame by default if you advise the function server-process-filter like so:

(defadvice server-process-filter (before prefer-graphical activate)
  ;; STRING is a sequence of commands sent from emacsclient to the server.
  (when (and
         ;; Check that we're editing a file, as opposed to evaluating elisp.
         (string-match "-file" string)
         ;; Check that there are no frames beyond the Emacs daemon's terminal.
         (daemonp)
         (null (cdr (frame-list)))
         (eq (selected-frame) terminal-frame)
         ;; Check that we have a graphical display.
         ;; `display-graphic-p' doesn't work here.
         (getenv "DISPLAY"))
    (setq string (concat
                  ;; STRING must be all one line, but comes to us
                  ;; newline-terminated.  Strip off the trailing newline.
                  (replace-regexp-in-string "\n$" "" string)
                  ;; Add the commands to create a graphical frame.
                  "-window-system "
                  "-display " (getenv "DISPLAY")
                  ;; Add back the newline.
                  "\n"))))

Put that in your init-file, then, as said, emacsclient -n -a '' FILE and Bob's your uncle.

Compare to Shell Solution

On the one hand, I can point to a few advantages to using this defadvice approach as compared to using the script suggested by Archenoth

#!/bin/bash
emacs --eval '(server-start)' $* &

as the alternate editor. With the defadvice:

  1. save-buffers-kill-terminal (C-x C-c by default) behaves consistently across all frames. It never kills the Emacs process, because every frame is always a client frame.
  2. The daemon's terminal frame hangs around. Commands like find-grep that shell out to external processes behave better when the dumb terminal is there. At least, I experience fewer shell-escaping related headaches.

On the other hand ... yeah.

  1. That shell script is beautifully simple.
  2. Advising Emacs' communication protocol is not.

Conclusion

Maybe there's a compromise? This is the best I could come up with. You set it as your $EDITOR.

#!/bin/sh

emacsclient -e "(frames-on-display-list \"${DISPLAY}\")" 1>/dev/null 2>/dev/null
if [ "$?" = "1" ]; then
    emacsclient -c -n -a "" "$@"
else
    emacsclient -n "$@"
fi
  • So how do I advise a function when emacs is not running? – nispio Oct 18 '14 at 14:41
  • You should be able to just drop it in the init file. The daemon reads that file when it starts up and finishes reading before emacsclient starts sending commands. That's how I'm doing it, at least. – purple_arrows Oct 18 '14 at 16:07
4

I am unsure of how to do that strictly within Emacs, but luckily there are other ways to get what you describe.

If you don't have something in your .emacs to start a server, you could always make a small script that starts up Emacs with the file you want to edit and start the server forked.

Something like:

#!/bin/bash
emacs --eval '(server-start)' $* &

And then pass that to -a.


However...
If you have the server start in your .emacs, you don't really need to create a script; you have a slightly more terse option:

emacsclient -n -a "/usr/local/bin/emacs" ~/.bashrc &

The ampersand will background the process and instantly give you back your shell, even when Emacs starts for the first time.

You might have to disown the process if bash kills jobs on exit. If that is the case, you simply add a disown to the end:

emacsclient -n -a "/usr/local/bin/emacs" ~/.bashrc & disown

On Windows, the closest equivalent would be the "start" command...

So, like the script suggestion above, you would likely have to create a batch file that had something in it like:

start /b C:\path\to\emacs %*

And then point the -a argument at it.
That should run the batch file and return immediately after starting up Emacs with the appropriate file.

  • I am on Linux (RHEL 6.5), and & was the first thing I though to try. Unfortunately, the following text gets sent to stderr: and then the terminal blocks waiting for the process to end: emacsclient: can't find socket; have you started the server? To start the server in Emacs, type "M-x server-start". I am guessing that the emacsclient forks into the background, but then it invokes emacs which gets opened in the foreground. – nispio Oct 17 '14 at 4:50
  • In that case, maybe a little script may be the best route..? Something like emacs --eval '(server-start)' $* & maybe? That way the server starts, you get your terminal, and emacsclient has a server it can connect to. – Archenoth Oct 17 '14 at 4:56
  • I've tested it and added it to my answer... Sorry about that! I had figured you had a mechanism for starting the server when you ran emacs the normal way. It dawns on me most people likely wouldn't have that configuration, so that's now the first part of the answer. Hope it helps..! – Archenoth Oct 17 '14 at 5:14
  • @nispio set your alternate editor parameter to have an & at the end. – Malabarba Oct 17 '14 at 7:22
  • 2
    @Malabarba You cannot pass command line arguments or shell directives to the alternate editor, which is why I suggested a small script. – Archenoth Oct 17 '14 at 11:35

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.