Showing posts with label bash. Show all posts
Showing posts with label bash. Show all posts

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.

Putting the current directory in your terminal's titlebar: an optimization

Some time ago, I blogged about putting the current directory in your terminal's titlebar to improve your productivity. In the interest of dogfooding I used this myself for some time and I got annoyed by how much my terminal got slower.

The old (slow) code:

PROMPT_COMMAND="echo -ne \"\033]0;\$(pwd | sed "s#^$HOME#~#g")\007\""
Notice how the $(pwd | sed "s#^$HOME#~#g") part has to spawn at least two processes? One for pwd and another one for sed. Suffice it to say that spawning processes on Cygwin (Windows) isn't something you should take lightly.

Therefore, I propose to you the new and improved (fast) code:

PROMPT_COMMAND="echo -ne \"\033]0;\${PWD/\$HOME/~}\007\""
We replaced $(pwd | sed "s#^$HOME#~#g") with ${PWD/$HOME/~}. Instead of spawning 2 different process, this new command uses only bash built ins to accomplish the same thing. The bash syntax I'm using is quite obscure so let me quote the bash man pages (under Parameter Expansion):

${parameter/pattern/string}
The pattern is expanded to produce a pattern just as in pathname expansion. Parameter is expanded and the longest match of pat- tern against its value is replaced with string. If Ipattern begins with /, all matches of pattern are replaced with string. Normally only the first match is replaced. If pattern begins with #, it must match at the beginning of the expanded value of parameter. If pattern begins with %, it must match at the end of the expanded value of parameter. If string is null, matches of pattern are deleted and the / following pattern may be omit- ted. If parameter is @ or *, the substitution operation is applied to each positional parameter in turn, and the expansion is the resultant list. If parameter is an array variable sub- scripted with @ or *, the substitution operation is applied to each member of the array in turn, and the expansion is the resultant list.

Usage of this new syntax sped everything up a lot.