Skip to content

Instantly share code, notes, and snippets.

@sr105
Last active June 16, 2017 14:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save sr105/f9e7de73643607af166d855a0093f925 to your computer and use it in GitHub Desktop.
Save sr105/f9e7de73643607af166d855a0093f925 to your computer and use it in GitHub Desktop.
Instructions and script for telling a local emacs to open a remote file via ssh while at the remote prompt using iTerm2 triggers
#!/bin/bash
# Don't forget to read the SECURITY WARNING below, and then don't
# say you didn't know.
#######
## Description
#
# tl;dr -- Type `e filename` on a remote host via ssh and your local
# emacs will open the remote file. Don't have the `e` function
# installed on the remote machine? No problem, it will
# automatically detect that, install the function, and then
# open the file.
#
# This script contains instructions for telling a local
# Emacs to edit a remote file over an ssh connection from
# the remote host. It requires iTerm2. Setup involves creating
# two iTerm2 triggers that execute this script. You may need to
# edit the path to emacsclient here:
EMACSCLIENT=/usr/local/bin/emacsclient
## Quick overview of the commands run by this script:
#
# Example output for `e filename` on remote host (split over two lines for readability)
#
# EMACS_EDIT r_user="hchapman" ssh_conn="192.168.56.1 65406 192.168.56.101 22" \
# pwd="/home/hchapman" ARGS filename
#
# Format: EMACS_EDIT shell_variables ARGS filename(s)
#
# This runs the following command on your local machine:
#
# $ shell_variables /path/to/remote_emacs.sh filename(s)
#
# which builds this command (for each filename):
#
# $ emacsclient -n /ssh:hchapman@192.168.56.101#22:/home/hchapman/filename
## SECURITY WARNING!!! (and thoughts of a fix)
#
# Hyperbole aside, I don't even worry about this, but it exists.
#
# This will pass some text containing shell variables given from a
# remote machine to your local shell without any safety checks. We
# should find a way to make this safe. All we really want is to
# pass some variable data. This was the fastest and easiest method
# at the time.
#
# Example of the local command:
# $ shell_variables this_script.sh filename(s)
#
# Worst-case(?) scenario: your angry, just-fired co-worker
# manages to install/change the `e` function on a remote machine
# to output this:
# EDIT_EMACS rm -rf / ; ARGS filename
#
# note: EMACS_EDIT above is intentionally transposed to prevent
# an incorrect iTerm2 regex plus catting this script from
# executing that command.
#
# The best fix would be to pass the entire remote text as an
# unevaluated string to the script for safe processing. I
# don't see an easy way to do this today with iTerm2. Perhaps we
# could patch iTerm2 to give it the ability to pass text to
# a command via standard input.
## Future improvements:
#
# If we could figure out how to auto-add the `e` function to the
# current environment after sshing anywhere, then it would
# be automatic and awesome. (UPDATE: it works!)
#
# Integrate this into a script that starts emacs if not already running.
#
# Handle line numbers like `+10 file` which tells emacsclient to open file
# at line 10.
#
# Handle the way grep outputs filename and line numbers, `file:10`. If you
# have iTerm2 set to auto-copy mouse selections, you can double click the
# filename:line_number, then type `e [CMD-V]` to take you right to that
# line.
#
# Can we make this work for multi-hop connections? (use tunneling?)
#
# Use xargs and a function to open files rather than a for which may
# not handle filenames with spaces.
#
# The `e` function doesn't work with vagrant VMs because they forward a port on the
# host machine to the guest. Until we can detect and handle that, replace
# `$SSH_CONNECTION` above with `_ _ localhost 2222` or whatever port vagrant
# used. I usually put this in a .bash_aliases file on the VM which all new
# Ubuntu machines seem to automatically load if it exists.
#
# Work with sudo. Added some code to work below, but it requires users to run
# sudo with the '-E' preserve environment option. Idea: use the parent process
# pid to get the SSH variables from /proc/parent_pid/environ.
#
#######
## Instructions:
## Install the `e` function
#
# This is no longer required unless you have a problem like the vagrant one
# mentioned above. The iTerm2 triggers below will now automatically do this
# for you.
#
# Put this function on the remote host. This function prints a single easily
# parseable line to the terminal with all of the information needed to tell
# Emacs + tramp how to open your remote file.
#
e() { printf 'EMACS_EDIT r_user="%s" ssh_conn="%s" sudo_user="%s" pwd="%s" ARGS %s\n' $(id -un) "$SSH_CONNECTION" "$SUDO_USER" "$(pwd)" "$@"; }
## Add these triggers to iTerm2 (Profiles -> Advanced -> Edit Triggers):
#
# regex: ^EMACS_EDIT (.*) ARGS (.*)$
# action: Run Command
# parameters: \1 /path/to/__this_script__.sh \2
#
# regex: [ ]*e: command not found$
# action: Send Text...
# parameters: [Paste everything between the horizontal lines below
# especially the trailing newline! Remove the leading
# comment characters. (explained below)]
#----------------------------------------------------------------
#e() { printf 'EMACS_EDIT r_user="%s" ssh_conn="%s" sudo_user="%s" pwd="%s" ARGS %s\n' $(id -un) "$SSH_CONNECTION" "$SUDO_USER" "$(pwd)" "$@"; }
#!-2
#
#----------------------------------------------------------------
#
# The second trigger detects the missing `e` command and auto-sends it for you.
# It adds `!-2` which makes bash re-run the `e filename` command again after
# setting the function.
## Algorithm:
#
# Parse the remote ssh host and port
# For each filename (assuming each argument is a separate file):
# open it using tramp-mode in an already running emacs
# URL pattern /ssh:user@host#port:/path/to/file
read my_ip my_port their_ip their_port <<< "${ssh_conn}"
for filename in "$@"; do
# only add pwd to relative filenames
if ! [[ "$filename" =~ ^/ ]]; then
filename="${pwd}/${filename}"
fi
sudo_part=""
if [ "$sudo_user" != "" ]; then
sudo_part="|sudo:${their_ip}"
r_user=$sudo_user
fi
# echo "$EMACSCLIENT" -n "/ssh:${r_user}@${their_ip}#${their_port}${sudo_part}:${filename}" >> ~/remote_emacs_output.txt
"$EMACSCLIENT" -n "/ssh:${r_user}@${their_ip}#${their_port}${sudo_part}:${filename}"
done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment