Emacsclient and TRAMP
For quite a while now, I’ve done a large amount of my text editing in emacs over ssh using TRAMP. It’s extremely convenient to be able to run one instance of emacs locally instead of spawning an editor remotely and interacting with it over ssh. I’ve tried using sshfs in the past, but it’s never worked well for me in the general case. But, this isn’t a post about justifying my choices.
The other crucial part of my local emacs setup has been emacsclient. Setting
my local $EDITOR
to emacsclient means that I could, for example, run git
commit
in a terminal and edit the commit message without having to spawn a
new emacs instance. Instead, emacsclient tells the existing emacs what files
to edit, and waits until emacs says it’s done with them.
Until just yesterday, there was a major flaw in my setup: I couldn’t activate
my local emacs instance through emacsclient on a remote host. This meant that
$EDITOR
had to be set to something that spawned an editor on the remote host,
which was becoming increasingly frustrating.
In the past I’d tried doing things like using an ssh tunnel to forward the emacsclient connections from the remote host to my local machine, but emacsclient doesn’t anticipate this case. The emacsclient protocol is very simple: ASCII commands delimited with line feeds. So, for example:
1 2 3 4 |
|
The problem is that this is being sent to your local emacs, which then tries
to load the local /foo/bar
. A TRAMP path is in the form of (approximately)
/ssh:example.com:/foo/bar
, so if only this could be prepended, emacs could
deal with it!
In my most recent investigation of the problem, I found Ryan Barrett’s implementation, which was most of what I wanted. Except,
It wasn’t automatic. It’s really convenient to be able to have TRAMP dump the appropriate authentication file onto the server as soon as a connection is made without having to connect using a special function.
It was relying on emacsclient sending a
-tty
argument in a specific format, and then using the TRAMP prefix of the current buffer. In practice this means you have to have your frontmost buffer a file open on the remote server, and only then can you use emacsclient on that remote server.
I started to hack on it, but I was still affected by the same original problem: emacsclient doesn’t know about TRAMP. The default implementation doesn’t even have a way of specifying additional data to be sent over the wire. With the simplicity of the emacsclient protocol, it started looking like writing my own would be the simplest choice.
So, I put together some elisp to write out a special client authentication file that would also include the TRAMP prefix and a shell script that would parse it and write commands using nc. My elisp implementation borrows code from Ryan Barrett’s (and thank you for getting me started with this!) but goes a bit farther. The full workflow is like this:
Update your
.emacs
to load and configure the elisp.Update your
.profile
to set$EDITOR
to theemacsclient.sh
script and tell it where the authentication file is.Open a TRAMP connection from your local emacs to write out the authentication file.
Go hog wild.
Repeat only steps 3-5.
Here are the versions of emacsclient.sh
and remote-emacsclient.el
as of
2013-06-25:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 |
|
The most up-to-date versions of each will be in my dotfiles repository. For convenience, here are links to the files within the repository: emacsclient.sh and remote-emacsclient.el.
Configuration is pretty straightforward. In my local .emacs
file,
(server-start)
got changed to:
1 2 |
|
The update-tramp-emacs-server-port-forward
function takes a TRAMP method
and updates the ssh arguments to that method to include a -R
flag forwarding
remote TCP connections to the local server. It’s probably not necessary to call
it for any TRAMP method other than your default.
By default every new server connection will leave an authentication file.
This can be tuned with the tramp-default-remote-emacsclient-auth-file
and
tramp-remote-emacsclient-auth-file-alist
variables. The former can be set to
nil
to disable auth file creation by default. The latter is in the same form
as the tramp-default-method-alist
variable for specifying the location of the
auth file by host and/or by user.
And in my .zshrc
:
1 2 3 |
|
$remote_emacs_auth
’s default mirrors remote-emacsclient.el
’s default of
writing the auth file to ~/.emacs.d/remote-server
. Since emacsclient.sh
is
(for me, by default) checked out in ~/.dotfiles
and set executable, this is
all that’s required to use it as an editor. As an added bonus, the emacs
alias lets me type emacs somefile
over ssh to open somefile
in my local
emacs.
There’s only two caveats (that I’m aware of (I’ve only tried the scp
, ssh
,
and scpc
tramp methods with this so far)):
Since the authentication key changes every time emacs is started, you must open a new tramp connection to a server before emacsclient will be able to speak to your local emacs. I haven’t found a good solution to this other than forcing the authentication key to always be the same (bad) or making emacs reconnect to everything whenever you start it (worse).
There’s no detection of when the server is restarted. Restarting the server in a running emacs also changes the authentication key. Making a new tramp connection isn’t required in this case; there’s a
tramp-save-remote-emacsclient-auth-file
function that can be run interactively. Running it will write a new auth file for the connection corresponding to the current buffer.