Vim and the terminal
I have always missed terminal emulation integration in vim. Yes, I know, it’s never going to happen, Bram Moolenaar has his good reasons, so stop whining. But who knows, maybe one day someone will come up with a vim-like editor that allows for something like that. Until then, there’s workarounds.
Since we can’t run a shell inside of vim, we have to make do with the inverse scenario: running vim inside a shell. This does away with gvim, alas, which takes away quite some gui features, but using a terminal like urxvt with 256 color support, xft and anti-aliasing, we can get quite close (we even get italic text!)
So, what I ended up with, is a setup that lets me easily jump out of vim using
the CTRL+Z shortcut, which effectively pauses the vim process and puts me back
into the shell from which I started it. Now, I can do whatever shell work I
need to do, and then I can jump back into vim with fg. So far,
nothing new. But what if the shell work I do, results in me wanting to open
another file, possibly elsewhere in the file tree?
Changing working directory after suspending vim won’t do us any good, as the
working directory of vim will stay unchanged. So, after issuing the
fg command, we still have to tell vim where to find the new file.
Also, starting another vim process would be ludicrous, and switching between
the two would be a pain. Instead, it would be great if we could tell the
sleeping vim process to wake up and to edit the new file, which might
be just at our finger tips thanks to above mentioned shell work, the very reason
we left vim in the first place.
Enter the clientserver feature of vim. It must be explicitly built into vim, and is usually part of most distributions (not all of them, though, yes MacPorts, I am looking at you). This feature allows us to send instructions to a vim process from another process. Before you think that this might be the solution to all your asynchronous needs in vim, think again. The commands that can be exchanged are limited, not so much in what is “allowed”, but in what will “work” as opposed to what will cause vim to misbehave (I speak out of personal experience). Nevertheless, in this particular scenario, it is just what the doctor ordered.
Picture this: we are happily hacking along on the keyboard, joyfully keeping
our digits close to that beloved home row, when — suddenly! — the need arises
to copy something from an obscure little file we know we left somewhere on
our file system. Off we go, out of vim, hitting the magic CTRL+Z shortcut.
Back in our favourite shell, we navigate around, using find and
grep and other such implements to jug our memory or otherwise
find said file. Now, with the file cornered, we resume our vim process, and
tell it through the clientserver feature to edit said file.
The tricky part, I found eventually, lay in timing things right. What I ended up doing, was to fire off the client-server instruction first, and putting that call into the background. It would pause until the vim server process would wake up again, so putting it in the background would get it out of my way, while still resting assured that, once resumed, the vim process would be properly instructed to edit the newly found file.
The other tricky bit, was supporting many different instances of vim. Being a busy fellow, I have had many days in which I’d have quite a collection of terminal emulators open, each of which sporting a different instance of vim, aiding me in tackling various different tasks more or less concurrently. As such, it was important to create a setup, that would allow for this mechanism to work with the proper vim server, i.e. I needed to make sure that once I’d instruct a vim server to open a file, it would be the correct one.
I settled on using an environment variable inside the parent shell process of the vim process. This variable contains the job number of the vim process, which allows me to wake the correct vim process.
Here is the code of the bash function I wrote to support this work flow.
Basically, whenever I want to edit a file, I use this v bash
function instead of the vim command. This saves on the typing,
and works completely transparently: if there is a vim server sleeping in the
background, it will awaken it and instruct it to edit the given file,
otherwise it will simply start a new vim process.
function v() {
if [[ -z "$_V" ]] ; then
# No vim in the background.
vim --servername vim$$ $@ &
# Get the JOB number of the vim server!
_V=$(jobs -l | grep $! | sed -e "s/^\[\(.*\)\].*/\1/")
fg $_V && unset _V
else
# There is a v in the background, so we'll resume that and load the new
# file.
bash -c "sleep .1 ; vim --servername vim$$ --remote-silent $@ ; \
vim --servername vim$$ --remote-send ':redraw<CR>'" &
fg $_V && unset _V
fi
}
A few remarks about the above code. First off, I use vim$$ as
server name. That is the name of the vim server, and $$ is
replaced by bash with the process number, therefore giving me a unique name
for every vim instance I have running.
Getting the job number of a freshly started vim process turned out a bit
trickier, I had to resort to piping the output of jobs -l (which
gives me a listing of all running jobs) through grep and
sed, but it works.
When a vim process is found in the background, the function will start a new
bash process in the background, which will send instructions to the vim
process using the clientserver feature. Note that the first thing the
bash subprocess does is to sleep a moment. This ensures things don’t go crazy
with race conditions. Next, you will see that the bash subprocess actually
sends two commands to the vim process. The first command is
--remote-silent $@, which instructs the specified vim process to
open all the files that the v function receives as arguments.
The second command is --remote-send ':redraw, which causes the vim process to redraw the screen. This makes sure vim updates properly.
I find this a bit of a wart, but I think it has to do with the fact that, deep
inside, vim was never really written for much asynchronous operation. As a
result, opening a file this way (through remote instructions) is a bit fiddly,
and therefore we need to help vim by explicitly making it update the screen.
It’s almost as if vim has not noticed that the remote command it just executed
requires a redraw.
I keep the above code in a file inside my .vim directory, and
source it from my .bashrc. This way, I have the v function at hands all the time, while still keeping vim stuff together in one place.
It’s worth pointing out, that when I want to jump back into vim without
actually editing a new file, I use fg and not v.
Sure, the v function could be expanded to notice the absence of
any arguments, and thus act just like fg in that context, but I
find it more intuitive this way: v is for editing files,
fg is for resuming a background job.
in Vim
How did you build Vim with
+clientserver? I can’t figure out what to pass to./configure.I usually configure vim with the flag
--with-features=huge, but if you want more fine-grained control, you will have to edit the Makefile or you can even editfeatures.h. For more info, check:help install.Genius! Exactly what I was looking for. Thank you very much.