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. 1Vim 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.#!/bin/sh
# diffvim - Differentially Viming
TMPDIR=/tmp/diffvim
FILENM=$1
FILEBN=$(basename $FILENM)
if [ ! -d "$TMPDIR" ]; then
mkdir $TMPDIR
fi
cp $FILENM $TMPDIR/$FILEBN && vimdiff $FILENM $TMPDIR/$FILEBN
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
endif
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!- 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:
Post a Comment