Editing Remote Files With Your Local Vim Using the SCP Protocol

I love Vim!
It is so easy enough to edit a remote file with my local Vim through the Secure Copy protocol:

    $ vi scp://user@remote.host.com/projects/foo/bar.py

However, I often find myself wishing that bash completion was available to expand/complete paths on the remote system.
Furthermore, when editing files outside of my home directory hierarchy, I have to remember to add an extra slash after the host name, e.g.:

    $ vi scp://user@remote.host.com//var/www/html/index.htm

A solution is to write a custom wrapper function that takes scp-style remote file paths, converts them into Vim-style remote file paths, and then invokes Vim on them.
Then we can make use of the existing scp
bash completion for our wrapper function, and we get the side-benefit of not needing to juggle two different remote file path syntaxes.

The following code implements this solution. The function “vscp” is the wrapper function, while the line “complete -F _scp $nospace vscp” applies the “scp” completion function to the wrapper function. The code below needs to be sourced into your shell or placed into “~/.bashrc“.The “scp” completion function, “_scp() should already be defined if you have bash completion installed and sourced (and you should!), but just in case, it is conditionally defined here.

#! /bin/bash

function vscp() {
    if [[ -z $1 ]]
    then
        echo "usage: vscp [[user@]host1:]file1 ... [[user@]host2:]file2"
        return
    fi
    declare -a targs=()
    for iarg in $@
    do
        targ="scp://$(echo $iarg | sed -e 's@:/@//@' | sed -e 's@:@/@')"
        targs=("${targs[@]}" $targ)
    done
    vi ${targs[@]}
}

## Following function is from ".bash_completion". If it
## is not already defined/sourced, we define it here.
if [[ !$(declare -F _scp) ]]
then
    _scp()
    {
        local cur userhost path

        local IFS=$'\t\n'
        COMPREPLY=()
        cur=${COMP_WORDS[COMP_CWORD]}

        _expand || return 0

        if [[ "$cur" == *:* ]]; then
            # remove backslash escape from :
            cur=${cur/\\:/:}
            userhost=${cur%%?(\\):*}
            path=${cur#*:}
            # unescape spaces
            path=${path//\\\\\\\\ / }
            if [ -z "$path" ]; then
                # default to home dir of specified user on remote host
                path=$(ssh -o 'Batchmode yes' $userhost pwd 2>/dev/null)
            fi
            # escape spaces; remove executables, aliases, pipes and sockets;
            # add space at end of file names
            COMPREPLY=( $( ssh -o 'Batchmode yes' $userhost \
                    command ls -aF1d "$path*" 2>/dev/null | \
                    sed -e 's/[][(){}<>",:;^&!$&=?`|\ ]/\\\\\\&/g' \
                    -e 's/[*@|=]$//g' -e 's/[^\/]$/& /g' ) )
            return 0
        fi

        [[ "$cur" == */* ]] || _known_hosts -c -a
            COMPREPLY=( ${COMPREPLY[@]} $( command ls -aF1d $cur* \
                    2>/dev/null | sed \
                    -e 's/[][(){}<>",:;^&!$&=?`|\ ]/\\&/g' \
                    -e 's/[*@|=]$//g' -e 's/[^\/]$/& /g' ) )
        return 0
    }
fi
complete -F _scp -o nospace vscp


The code is obviously pretty basic, and can be elaborated further (e.g., by passing custom options/arguments to Vim).

<

p>Once you have a remote file open in Vim, the command “:E” or “:Explorer” will open up Vim’s native remote filesystem browser. Once you have set up the function presented above, you can also invoke Vim’s native remote filesystem browser by passing in a directory path as an argument, being sure to include the trailing backslash:

    $ vscp user@remote.host.com:/var/html/

Of course, the obvious alternative to all of this is to login to the remote system and just edit the files through the ssh connection. Vim is, after all, ubiquitious and installed by default on most (decent) operating systems (except for the one that shall not be named). And, to be honest, this is what I do most of the time when making minor edits or tweaks.
However, there still are some benefits to using my local Vim versus a remote one over an ssh connection on a terminal emulator, especially when I have a major editing/debugging session: speed/responsiveness, and much nicer syntax coloration (16 million colors vs. the 16 colors to which I am limited using OS X’s Terminal). These may seem like minor advantages, but both make a huge difference when trawling through hundreds of lines of code!

Leave a Reply

Your email address will not be published. Required fields are marked *