11

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?

nispio
  • 8,175
  • 2
  • 35
  • 73
  • 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 07: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

2 Answers2

7

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
purple_arrows
  • 2,373
  • 10
  • 19
  • 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.

Archenoth
  • 2,106
  • 16
  • 17
  • 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 04: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 04: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 05:14
  • @nispio set your alternate editor parameter to have an & at the end. – Malabarba Oct 17 '14 at 07: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
  • I am able to get this working with a script like you suggest, but it does not work without the script. I already have a `(server-start)` line in my init file, so my script is just `emacs-24.3 $* &`. But if I try to bypass the script and just call `emacsclient -n -a emacs-24.3 &` it does not work because the `&` is forking `emacsclient` rather than forking the alternate editor. – nispio Oct 17 '14 at 16:44
  • Well, `emacsclient` doesn't return immediately when it runs its alternate editor, that's why I suggested forking `emacsclient`, and not `emacs` itself. That way the behavior is consistent with the `-n` switch. As for the `(server-start)`, I have no idea why `emacs` won't start a server when run as the alternate editor. It appears that when I run `M-x server-start` maually from the alternate Emacs, `emacsclient` can see the server. – Archenoth Oct 17 '14 at 18:06
  • Actually, I should ask... @nispio, does manually running `M-x server-start` from the alternate Emacs instance create a server that can be seen by `emacsclient`? If so, there may be an issue loading your init file. – Archenoth Oct 17 '14 at 18:54
  • It does start a server, yes. What I should have said in my comment instead of "does not work" is that it "loads a new instance of emacs, but **does not return** until emacs is closed." In other words, whenever the alternate editor is called, it becomes the foreground process of the terminal. The only way that I can avoid this behavior is by setting the alternate editor to be a script which forks emacs for me, just like your first suggestion. – nispio Oct 17 '14 at 19:03
  • Yes, and that is why forking `emacsclient` was suggested. The downside is that you can't do things with the exit status in that case, but otherwise it would have the same behaviour as if the alternate editor returns instantly from a usage standpoint. – Archenoth Oct 17 '14 at 19:42
  • I feel like we are not understanding each other. Forking `emacsclient` is not working for me. Forking my alternate editor via the shell script is working for me. – nispio Oct 18 '14 at 01:17
  • Ah! I read the above as though you had qualms with the script suggestion even though it worked, so I was trying to troubleshoot what was wrong with the other suggestion. Apologies for the confusion. – Archenoth Oct 18 '14 at 05:39