2010/08/14

Using Cygwin Bash as your Win32-Vim shell

Because the standard Vim that comes with Cygwin is kind of weak and nobody likes running an X server on his Windows computer, running the Win32 build of Vim in conjunction with Cygwin is a logical thing to do.

Although not ideal, it is possible to do this with an out-of-the-box Vim, by entering a few lines of code to your .vimrc. The problem with this is that complicated stuff, like for example this line out of my own .vimrc won't work; entering the :make command in Vim will give you errors:

autocmd BufRead *.py set makeprg=python\ -c\ 
       \"
          import\ py_compile,sys;\ sys.stderr=sys.stdout;\ 
          py_compile.compile(r'%')
       \"
(Without the newlines). Other issues with this method include that you can't use them in combination with PuTTYCyg, the best terminal for Cygwin you can find.

Now, I don't know about you, but "most commands" isn't nearly good enough for me. Ladies and Gentlemen, I give you: Vimshell.

Vimshell is a drop-in-place replacement for the vimrun.exe executable in your Vim directory (most of the time that would be C:\program files\Vim\vim72). You can find Vimshell right here.

Installation

First, you replace the standard vimrun.exe with the one you can find here. You then make a new file in the same folder that's called shell.txt. This file will contain exactly 3 lines. An example that works great with PuTTYCyg:

"C:\Program files\PuttyCyg\putty.exe" -cygterm "bash -i '#F;read;#'"
"C:\Program files\PuttyCyg\putty.exe" -cygterm "bash '#F#'"
"C:\Program files\PuttyCyg\putty.exe" -cygterm -
After Vimshell does its magic, the command that's going to be executed will look something like this:

"C:\Program files\PuttyCyg\putty.exe" 
            -cygterm "bash -i 'C:/somefile.txt'"
Where all commands your shell need to execute will be in the file C:/somefile.txt (so do whatever it takes to make sure your shell reads from whatever #F# will be replaced by). If you'd like to see the output of commands (and you probably do) you will need to make your shell pause after all commands have been executed. This is done by typing #F...something...# in your shell.txt instead of #F#. ...something... is the shell command that will be executed after everything else.

The first line in your shell.txt is the command that will be executed when a normal command needs to be executed by Vim (like when you type !echo "Hi"). The second line will be executed when a command is to be executed silently (like when you type :silent execute "!mkdir ~/temp 2>/dev/null" or :make). The third line will be executed when you just type :! (when you need an interactive shell).

Note that the functionality the second line provides is implemented in a much better way than it is by the standard vimrun.exe and the functionality the third line provides just isn't possible in a standard Vim+bash installation.

The installation is almost done, just add the following lines to your .vimrc file (this would be the .vimrc that's in your Cygwin home directory, not your Windows home directory; to check where this file is located enter the Vim command :echo $MYVIMRC):

set shellquote=
set shellslash  
set shellxquote=
set shellpipe=2>&1\|tee
set shellredir=>%s\ 2>&1
And that's it. Just use your Vim like you'd want to and execute command like :make at will.

How does it work?

At first, Vimshell worked by applying some complicated escaping rules that worked by scanning the strings in your shell.txt and then trying to escape your own commands in such a way that would give the correct result. This worked most of the time but unfortunately not always, since Bash does such strange things with arguments you give it and there's no way for me to predict how a string should be escaped. Also, Vimshell should work with any shell, not just bash, so this really wasn't a feasible option.

Thus, Vimshell currently employs another tactic. The command you wish to execute is written to a temporary file and this file is then given to and executed by your shell. That way, everything just works.

7 comments:

  1. I'm having trouble figuring out what my shell.txt should look like if I want to use it with the Git bash that comes on Windows (msysgit). I have this:

    "C:\Program Files (x86)\Git\bin\sh.exe" -c "#F;read;#"
    "C:\Program Files (x86)\Git\bin\sh.exe" -c "#F#"
    "C:\Program Files (x86)\Git\bin\sh.exe" --login -i

    I am running gVim 7.3.

    ReplyDelete
  2. Try

    "C:\Program Files (x86)\Git\bin\sh.exe" -i "#F;read;#"
    "C:\Program Files (x86)\Git\bin\sh.exe" "#F#"
    "C:\Program Files (x86)\Git\bin\sh.exe" --login -i

    and it's probably best to go with bash instead of sh. The problem with your version is that you're handing sh a command, while (for escaping purposes, don't ask) vimshell will produce a filename for "#F". So the command after substitution will be

    "C:\Program Files (x86)\Git\bin\sh.exe" -i "C:/someDir/script.sh;read;" which will cause the shell to interactively (-i) execute C:/someDir/script.sh and then read input until you press a newline, after which the terminal will close.

    ReplyDelete
  3. Well I can't seem to get the top 2 lines to work. The bottom line works though. I also made the changes to point at bash.exe instead.

    ReplyDelete
  4. Can you describe what goes wrong? I just downloaded the latest msysgit, unpacked and used

    "C:\Users\Ives\Downloads\PortableGit-1.7.10-preview20120409\bin\bash.exe" -i "#F;read;#"
    "C:\Users\Ives\Downloads\PortableGit-1.7.10-preview20120409\bin\bash.exe" "#F#"
    "C:\Users\Ives\Downloads\PortableGit-1.7.10-preview20120409\bin\bash.exe" --login -i

    as my shell.txt and everything worked fine. I also have gvim 7.3.

    ReplyDelete
  5. I just get nothing. I try to do :! ls and I get nothing. But I do get a bash window pop-up with just :!

    ReplyDelete
  6. Did you put

    set shellquote=
    set shellslash
    set shellxquote=
    set shellpipe=2>&1\|tee
    set shellredir=>%s\ 2>&1

    in your vimrc?

    ReplyDelete
  7. Yep, I had copied and pasted that directly from your post.

    ReplyDelete