46

I often use TRAMP to manage several remote servers, both for editing files and running remote shells in shell-mode. However, this does not work when a command uses the EDITOR variable to edit a file, such as crontab -e especially because shell-mode is a "dumb" terminal that does not support running another editor inside of it.

Locally, I do this with an appropriate call to emacsclient which opens up a new buffer and makes life very easy. Unfortunately, this does not work from the remote machine.

I guess I could use ed. (Hah!)

How can I set up a remote EDITOR that would let me edit files with my local Emacs instance?

Tikhon Jelvis
  • 6,152
  • 2
  • 27
  • 40

7 Answers7

25

[NOTE] this answer was heavily edited to follow the updates of with-editor developments. Most comments likely won't make much sense anymore. There are some new comments which do make sense.


Magit contains a library named with-editor available at https://github.com/magit/with-editor which allows you to use your local Emacs as an $EDITOR on remote machines over TRAMP.

Another alternative is https://github.com/habnabit/remote-emacsclient, but it seems more complicated & less generic.

The simplest way to install with-editor is through MELPA:

M-x package-install with-editor

Otherwise, just grab https://github.com/magit/with-editor/blob/master/with-editor.el somewhere to your load path and require it (it also depends on dash).

Then, simply start shell, eshell or ansi-term and do the following:

M-x with-editor-export-editor

It'll ask you which $EDITOR you are interested in, just press enter for the default EDITOR variable. Then inside the shell you can type crontab -e and edit your crontab within emacs. Press C-c C-c to save the crontab or C-c C-k to cancel editing.

If you want a more permanent setup:

(add-hook 'shell-mode-hook  'with-editor-export-editor)
(add-hook 'term-mode-hook   'with-editor-export-editor)
(add-hook 'eshell-mode-hook 'with-editor-export-editor)

Alternatively, you can use M-x with-editor-async-shell-command crontab -e RET for quick commands.

Silex
  • 801
  • 6
  • 6
  • Could you elaborate on how this `with-editor` library relates to the question? Sounds useful – Malabarba Oct 08 '14 at 16:18
  • @Silex: Would I need to set the `$EDITOR` variable to something on the remote machine for this to work? Does it just hook into `emacsclient`? – Tikhon Jelvis Oct 08 '14 at 17:40
  • But yeah, this looks like exactly what I want. I'll have to give it a try somehow—I assume it can be installed by itself, without the rest of the branch? – Tikhon Jelvis Oct 08 '14 at 17:40
  • @Malabarba: as said in the original post, you can call `M-x async-shell-command crontab -e RET` over TRAMP and it just opens the crontab buffer for editing in your local emacs instance. You then save with `C-c C-c` – Silex Oct 09 '14 at 08:26
  • 1
    @TikhonJelvi: it certainly *will* be installable by itself, but for now it's just part of `git-modes`. I'm following this lib closely and it's just that its author (@tarsius) is busy with releasing magit, but eventually it'd be a package of its own. About $EDITOR, you don't need to set it to anything yourself, it's done when needed when you run whatever commands uses it. Magit uses this lib with $GIT_EDITOR. – Silex Oct 09 '14 at 08:36
  • @Silex: I'm still a bit confused about that last point. Do you mean `$EDITOR` is set when I run an *emacs* command that uses it? But what if I just run `crontab -e` from a shell buffer—how would `crontab` know what to call if not from `$EDITOR`? – Tikhon Jelvis Oct 09 '14 at 17:33
  • @TikhonJelvis: I gave more details [in this discussion in chat](http://chat.stackexchange.com/rooms/17753/discussion-between-silex-and-tikhon-jelvis). (Also I updated the answer to give steps to try with-editor out). – Silex Oct 09 '14 at 18:59
  • 1
    Note this will stop working after sshing to a remote from within an existing `shell-mode`/`term-mode` buffer. This can be addressed with some additional configuration, see http://emacs.stackexchange.com/questions/5589/automatically-update-default-directory-when-pwd-changes-in-shell-mode-and-term-m. If you do get it to work, then please report your findings at https://github.com/magit/magit/issues/1638. – tarsius Dec 20 '14 at 17:47
  • Using `with-editor` for something like "`crontab -e`" works for me, but how do I start editing an arbitrary file from an `M-x shell`? I tried "`$EDITOR file.txt`", but get the error message "`"WITH-EDITOR:: 1: "WITH-EDITOR:: Syntax error: Unterminated quoted string`" – asjo Sep 02 '17 at 11:23
  • If I follow the directions in the post, and run `ansi-term` and then `M-x with-editor-export-editor` and then ssh (from this ansi-term session) on a different computer (hoping to use my local emacs as the EDITOR) and run `crontab -e`, I get `emacsclient: can't find socket; have you started the server?`. In short it looks like nothing changed and my local machine is still trying to use its default editor. Is there something else to configure? – Startec Sep 05 '17 at 04:05
  • @Startec: this won't work. Use TRAMP instead and spawn a remote shell with `M-x shell`, there `with-editor` would work. See @tarsius comment above. – Silex Sep 10 '17 at 11:53
1

I have a tiny script in my path on the remote host at ~/bin/ec, shorthand for emacsclient.

#!/bin/bash

params=()
for p in "$@"; do
  if [ "$p" == "-n" ]; then
    params+=( "$p" )
  elif [ "${p:0:1}" == "+" ]; then
    params+=( "$p" )
  else
    params+=( "/ssh:z:"$(readlink -f $p) )
  fi
done
emacsclient --server-file=$HOME/.emacs.d/server/server "${params[@]}"

This script passes -n and + args unchanged to emacsclient, otherwise args are treated as files for your local Emacs to open. Each file is prefixed with the TRAMP protocol and host so Emacs knows how to open it. You might be able to change ssh: to a different TRAMP protocol if you prefer.

You must replace z with the hostname of your remote machine. This is used by local Emacs to connect via TRAMP. (You might be able to use hostname here for generality. I prefer to use tiny entries like z in my local ssh_config for brevity, and the remote has no idea I'm doing this. Try it!)

Usage:

  • ec file in the remote shell opens file in local Emacs and waits
  • ec -n file in the remote shell opens file in local Emacs and returns
  • export EDITOR=~/bin/ec in remote .bashrc makes the magic happen

To ensure my server file is good I have this in my local .emacs, again using the tiny hostname z:

(setq server-use-tcp t
      server-port    9999)
(defun server-start-and-copy ()
  "Start server and copy server file to remote box."
  (interactive)
  (server-start)
  (copy-file "~/.emacs.d/server/server" "/z:.emacs.d/server/server" t)
  (chmod "/z:.emacs.d/server/server" (string-to-number "644" 8))
  )
(add-hook 'emacs-startup-hook 'server-start-and-copy)

Port 9999 is a RemoteForward. I put this in my local ~/.ssh/ssh_config to automate the forwarding, plus the ControlMaster stuff for speed.

Host z
HostName dev.example.com
User dev
ControlMaster auto
ControlPath ~/.ssh/z.sock
RemoteForward 9999 localhost:9999

Finally, make sure TRAMP knows about your ssh_config if you use it:

(require 'tramp)
(tramp-set-completion-function "ssh"
  '((tramp-parse-sconfig "~/.ssh/config")))
Andy
  • 111
  • 3
1

https://stackoverflow.com/questions/2231902/originate-edit-of-remote-file-using-emacs-tramp-from-ssh-session has a fairly simple accepted answer which amounts to

(setq server-use-tcp t)
(setq server-host "name_of_local_machine")
(server-start)
;; Maybe also muck with `server-auth-dir`

and then use

emacsclient -f ~/.emacs.d/server/server /`hostname`:/path/to/local/file

There is also https://stackoverflow.com/questions/12546722/using-emacs-server-and-emacsclient-on-other-machines-as-other-users which is more complex but where the answers also (roughly) touch similar bases.

tripleee
  • 251
  • 5
  • 15
1

Disclaimer: I have not tried this.

You could get part of the way by having a function watch command output for a command to open the file using TRAMP. Security hole? Yes. Functional? Probably.

Use a shell-mode hook to add a hook to after-change-hook in shell-mode. This hook watches for a specific sequence. Example:

;; RemoteTRAMP token: <authentication token maybe?>
;; RemoteTRAMP edit: /path/to/my/file

It then uses tramp-find-file to open the file. This is relatively secure because the ONLY THING that the remote can do is trigger a tramp-find-file. A confirmation first would be good but optional.

When editing is finished, another hook can trigger the exit of the dummy program (eg by sending C-c).

The worst case (for security) is that they find a way to run arbitrary code. If you have buffer-variables set to always evaluate, a malicious attacker could overwrite important settings without your knowledge. They could also launch a denial of service attack by causing many buffers to open. Confirmations could likely prevent all of the above.

The program on the remote could be trivially implemented in C (or any other language).

J David Smith
  • 2,635
  • 1
  • 16
  • 27
0

Really surprised no one has mentioned sshfs yet. This is what I usually do when I go remote:

1) multi-term; sshfs user@host:/dir/i/want/ /mnt/point/on/my/machine
2) open whatever I want to edit in my local emacs
3) get-term; ssh user@host to launch executables etc

While multi-term doesn't sync the local working dir with the remote dir, it beats all the other solutions I've tried by a long shot. Although dir tracking would be a welcome feature for sure.

0

Harold Abnabit came up with this approach1:

http://blog.habnab.it/blog/2013/06/25/emacsclient-and-tramp/

1 based on an earlier article by Ryan Barrett (to which he links).

FWIW there's also https://stackoverflow.com/q/5154224/324105 in which Petri Lehtinen came up with https://gist.github.com/akheron/850795. I imagine the more complex solutions are better, but a very simple approach might still be of interest.

phils
  • 48,657
  • 3
  • 76
  • 115
0

Might require a bit of tweaking, but here's the idea:

EDITOR="ssh artagnon@luneth \"emacsclient -n /`hostname`:$1\""
dgtized
  • 4,169
  • 20
  • 41
artagnon
  • 2,237
  • 1
  • 15
  • 17
  • 1
    Do you mean ssh'ing back from the remote server to the client where Emacs is running? That's often not possible. – Gilles 'SO- stop being evil' Sep 23 '14 at 21:49
  • Well, if you can't reach the client where Emacs is running, it's impossible to do anything. – artagnon Sep 23 '14 at 21:53
  • 2
    You can ask ssh, via it's ~/.ssh/config to always forward the local ssh listener to a port on the remote machine. You'd probably want to forward your ssh-agent as well, though opinions vary as to how secure that is. This will let you can get back by doing EDITOR="ssh $user@localhost:1234 ..." – Ben Hyde Sep 24 '14 at 00:51
  • Another option perhaps would be writing a small wrapper to call edit-server.el – stsquad Sep 24 '14 at 13:09