.@ Tony Finch – blog


One of the features I like about Firefox is when I run firefox (from the command line or from my window manager) it will start it if it isn't running, and it'll open a new window if it is. I have a lot of virtual desktops, and it is much more convenient to just run firefox than to find a desktop with an existing firefox window, create a new window, then move it to the target desktop. Sadly emacs is not so accommodating.

Emacs has a feature which allows you to tell a running emacs to start editing a file. However it does not open a new frame to display the file, so you have to go searching through your virtual desktops to find where it ended up.

I run emacs version 21, but version 22's emacsclient has a new -e (eval) option which allows you to tell emacs to run arbitrary lisp. This can solve the niggles I have described: e.g. to open a new emacs frame on the current desktop I could just run emacsclient -e '(make-frame)'. It would be nice to have this feature in version 21, so I have come up with an evil hack to do the job.

My original idea was that I could set up a file with a local variables section, including an "eval" specifier that tells emacs to open a new frame. Then opening the file would create a new frame. However emacs is sensibly paranoid and won't run lisp in arbitrary files without manual confirmation.

Plan B was to add a function to the find file hook, so that when I opened a specific file (that need not exist) the necessary magic would occur. Then running emacsclient --no-wait ~/.emacs-newframe would create a new frame. After some fiddling I made this work. The code in my .emacs is as follows. As well as creating a new frame, it kills the buffer containing the dummy file and switches the new frame to the canonical dummy buffer, to keep things tidy. It uses the server-visit-hook which is a bit more specific for my purpose than the file-find-hook.

(defun fanf-newframe ()
  (cond ((string-equal buffer-file-name "/home/fanf2/.emacs-newframe")
	 (kill-buffer nil)
	 (select-frame (make-frame))
	 (switch-to-buffer "*scratch*"))))

(add-hook 'server-visit-hook 'fanf-newframe)

A little wrapper script can start emacs or run emacsclient as necessary, to get behaviour like firefox. Instead of running bare emacsclient, it can now also create a new frame before asking emacs to visit a file, which is a bit nicer. However doing so may be racy because I can't make emacsclient ~/.emacs-newframe (without --no-wait) work sensibly: the hook runs before the buffer is registered as being visited by emacsclient, so the hook can't tell the server that the buffer is finished with after it is sure the frame exists. However emacs is not internally multi-threaded and the server handles requests in-order, so it's probably OK.