Live diff-mode Vim editting

Recently I felt the need of an editor feature that allows me to see while eiditing a file the content differences (in highlight) between the current and the original version. On one hand, the instant feedback keeps me informed of what has changed so far, saving me from any manul invocation of other tools (internal or external) whenever I want to see the updated differences. On the other hand, it could also keep audiences informed in a presentation involving live coding. I call this feature live diff-mode editing. It is unfortunately missing in most editors (or IDEs) that I am aware of. But where there is imagination, there is a way. With some hacking, I managed to have this feature in Vim. 1

Vim has already offered diff-mode editing. It can be started either by running vimdiff file1 file2 from the shell or by calling :diffsplit file2 while editing file1 inside Vim. This will display the contents of file1 and file2 in two windows (the former way vertically, the latter horizontally). However, neither way shows live differences. One has to always run the command :diffupdate to see the updated differences after any editing. Even worse, both cannot show differences of the same file, that is, when file1 and file2 are the same.

The second problem can be easily worked around by making a temprorary copy of the file and then start diff-mode editing on them. I have the following shell script called diffvim to do this automatically.

# diffvim - Differentially Viming

FILEBN=$(basename $FILENM)

if [ ! -d "$TMPDIR" ]; then
  mkdir $TMPDIR

It starts with making a temporary copy of the file (the filename passed as an argument to diffvim in the shell) in the directory /tmp/diffvim (hence all temporary copies created this way will be cleared out in between a shutdown and next reboot of the system), only if it succeeds, then it calls vimdiff on the original file and the temporary copy.

The first problem that Vim in diff-mode does not update instantly the differences of the files under editing is trickier. Actually, Vim can already update one kind of differences instantly. When inserting new lines or deleting old lines, the differences are shown immediately without delay. But inline editing could not trigger the update of differences. Unaware of Vim's event model, my first attempt was to configure Vim with my limited knowledge to approximate the needed behavior. The best approximation I could imagine was when leaving INSERT mode and entering NORMAL mode, trigger the update of differences. The command to leave the INSERT mode and enter NORMAL mode in Vim is associated with the Esc key. So I remapped it as follows:
imap <Esc> <Esc>:diffupdate<CR>
This line tells Vim that, in INSERT mode, remap the command associated with the Esc key to <Esc>:diffupdate<CR>. So when the Esc key is pressed in INSERT mode after any editing, it will first leave INSERT mode and enter NORMAL mode as usual, but this time it will also update the differences of the files under eiditing. This sounds a reasonable solution. Indeed the effect is not bad. Although the updated differences could still not be shown instantly during the editing, it can be shown immediately after the editing is done. However, it only solves a half or less of the problem. Because I soon noticed that if I modify the text directly in NORMAL mode, that is, without a detour of first entering and then leaving INSERT mode, the differences would still not be updated. That is sad since a central philosophy of Vim is to manipulate text as much as possible in NORMAL mode. So I added another remap of the command associated with the Esc key, but this time for NORMAL mode:
nmap <Esc> :diffupdate<CR>
The expected effect was, in NORMAL mode I could trigger the update of the differences by simply pressing the Esc key after any direct manipulation of the text. In a sense, it worked. But it also brought in some unexpected effects: if after running diffvim file from the shell (thus starting my approximate live diff-mode editing), the first thing I do was not pressing the Esc key but issuing some other NORMAL mode commands, like pressing the key j or l to move the cursor around, I was put into INSERT mode. I tried hard to fix this problem but all my attempts failed. Eventually, I turned to the Unix StackExchange for help. There I learned that overloading the Esc is a bad idea and that the proper way to achieve what I want is to let Vim execute the :diffupdate command automatically in an event-driven way. The event names suggested there, InsertEnter and InsertLeave, only covers respectively the events of Entering and Leaving INSERT mode. So associating :diffupdate to them would only update the differences when entering or leaving the INSERT mode, which my ad-hoc solution could do as well. However, it did suggest the right way to a thorough solution. After checking the supported events in Vim's help, I found the right event names to capture any text change in both INSERT and NORMAL mode. The events CursorMoved or CursorMovedI are triggered whenever the cursor is moved in NORMAL or INSERT mode. The following command associates the command :diffupdate to these two events:
autocmd CursorMoved,CursorMovedI * :diffupdate
It tells Vim to update the differences of whatever file (since its name always matches the wildcard filename pattern *) under editing whenever the events CursorMoved or CursorMovedI is triggered. To see why these events do the job perfectly, one only needs to notice that any inline editing, whether in INSERT or NORMAL mode, will involve moving the cursor either forward or backward.

Later, I also learned that it is better to restrict specific settings for Vim in diff mode local by collecting them into the following conditional structure:
if &diff
  autocmd CursorMoved,CursorMovedI * :diffupdate
I could not find an explanation of &diff in the Vim help. But clearly it is a boolean variable set when Vim is in diff mode.

The only drawback I can see of this solution is that, in NORMAL mode, just moving around without changing the text will also trigger the update of the differences even though there is no difference to udpate. But I view this as a trade-off.

That is all to have this interesting feature. Enjoy live diff-mode editing in Vim!

  1. Emacs allows the user to see the differences whenever she wants by invoking the command diff-buffer-with-file, which is already quite close to what I want. But it is still not live. However, given Emacs's great configurability, it should not be difficult to hack out a way to do the same thing.

No comments: