The one bad thing about vim
I spend at least 10 hours a day using Vim. I’m not going to explain, here, why it’s such a good text editor. You can look it up. Today I’m going to describe a terrible thing that Vim does in its default configuration. At the end, I’ll show you how to change its settings so that it does not do this terrible thing any more.
Let’s destroy the user’s files without warning
Try this experiment. Make a file like this: echo "some words" > important~
. Why would you name your file like that? “important~”, with that tilde at the end? Who knows? It’s a legal character. Some people even put spaces in their file names. So a little ~
is not a crime.
Now come back the next day and, in the same directory, decide to write some text in a file. We’ll use Vim, because it’s the best text editor, and we’ll call our file important
. Save your opus, and quit the editor.
I have an alias that shows me the most recently changed files in the directory. You probably have one too, if you use the command line a lot, because it’s something you constantly want to see. Mine is alias llr='ll --color=auto -t | head'
. However you do it, after editing your new file, you decide to take a look at the recently modified files. There is important
, as you expected, but also important~
, modified just a few seconds ago. Huh? You haven’t touched that file since yesterday. Looking at what’s in it, you discover that it’s not the “some words” that you stored there yesterday, but something more like today’s important
file, minus the most recent edits. Yesterday’s file has been kidnapped and used for something else.
You restore the original file from version control and settle in to read the Vim manual.
Let’s make sure that no auto-build system works
The scenario above has never actually happened to me. Some might say that it’s a tad far-fetched. But I’m sure it’s bitten someone. This is something that an editor, in its default configuration, simply should not do. What I am going to describe now did happen to me, and many others. It’s a side-effect of the same rude behavior by Vim.
I was looking into the javascript compiler Svelte, and decided that I wanted to try to use this brilliant piece of engineering to make something. To use Svelte you need to use node, which is a standalone javascript interpreter and web server. So I installed that, and some associated machinery, and the Svelte stuff. Node is nice to have, because it gives you a javascript REPL that you can use from the command line (you don’t have to use a browser). Of course, to be taken seriously these days, any project needs to have its own package manager (or, better, more than one, so they can conflict with each other). Node has one (or more, I guess), and I used it to install and run all the pieces that one needs to develop Svelte programs. When you get it going you have a web server running on a local port, and you open the page you are developing in your web browser, and the code in an editor (Vim, of course). Now it’s supposed to work in this convenient fashion: you made an edit, save, and your new code is automatically compiled, and the web page is refreshed to show you the new version. You just need to code away, and watch your creation evolve in your browser, without interrupting your editing to compile and refresh manually. You need to keep one eye on the console to see the error messages when you make mistakes, and when you fix those, everything gets back on track. This part worked well, because the error messages are detailed and clear. But the auto-everything was not very auto. After the first edit in a session, things happened the way they were supposed to, but on subsequent saves, there was no reaction from the compiler. Every time I wanted to see my new version I needed to quit the server and restart. This only took a second, and I developed two applications this way before deciding to get to the bottom of it. Svelte was a joy to use, but I wanted that smooth development experience promised on the side of the box.
Stackoverflow and related sites are full of disappointed people with the same problem. And not only with Svelte: I encountered similar descriptions in questions from people using a variety of auto-reloading tools, such as TeX previewers and various auto-make systems. Somewhere, during my stroll through these fora, I noticed a comment that made the light bulb go on. Someone asked the forlorn programmer if he was using Vim, and getting an affirmative response, suggested that those ~
files might have something to do with it.
Vim is infinitely configurable. I turned off the ~
file behavior, and found that the node/Svelte development machinery now worked exactly as advertised. On the programmers’ fora, people are wondering what is wrong with their build systems, and delving into what versions of everything they have installed, and wondering why the same exact setup works for some people but not for others. But the problem has nothing to do with the build systems. The wildcard is Vim.
What’s happening and what to do about it
Normally, you keep track of your files through their names, such as important
and important~
above. But your computer sees things a little differently. It identifies files though numbers called inodes. Each file has a unique inode. The filenames are just pointers to the data stored under each inode. You can see the inodes by supplying ls
with the -i
flag. If you create a file now, and check its inode, then rename it with the mv
command, you’ll see that, although the name changes, the inode remains the same. It’s the same file with a different name. Make a hard link to it, and now you’ll have two names with the same inode. Rename the file and the link: the inode does not change. There is no difference between the “original” file and the link; they’re both names that point to the same file, permanently identified by its inode.
Let’s follow the life story of a file being edited by Vim in its default configuration. We’re going to have to keep track of two inodes, which we’ll represent by these two symbols:
Now let’s say we have a file called file
, with the initial version of some content, v0
. We want to track what happens when we open this file in Vim and edit it, saving our work four times. After the first save, we’ll call the version of the content v1
, etc. Our original file has inode i1
. We work on it, and save. Vim does not overwrite the file with the new contents. Instead, it renames our file file~
. Then, it creates a new file, which, of course, has its own inode, which we can call i2
. It puts the new content, v1
, into this new file.
Our original file has not changed; Vim has simply renamed it. If we list our files, we will see file
and the “new” file~
, and if we look at the contents of file
, we see v1
: so everything looks the way we expect it to. But if we look at the inodes, we see a very different story.
Edit some more, and save a new version, v2
. Now Vim does modify the original file, putting v2
in there. Notice that our original file never contained v1
; it jumped from v0
to v2
. The content v1
is now stored in file~
, which is the new file, with inode i2
, that Vim created.
Here is a diagram that shows what happens when making four edits. Time goes from left to right, so files atop each other exist simultaneously. The inodes are identified with the symbols in the previous picture, and the file names pointing to those inodes are written inside the symbols, above the notation showing what is contained within the files.
An auto-reloading system will usually use a system utility, such as inotify
, to learn when a file has been modified so that it can recompile, or do whatever needs to be done. It is tracking changes to the content of a file, and doesn’t care whether the file’s name changes, or if links to it are created or destroyed. It’s watching for modifications to a file, and, remember, a file’s identity is contained in its inode.
So Vim’s dance of the inodes is going to completely mess this system up. The system knows nothing of the file with inode i2
, that Vim created. At best, it will trigger every other save, when the file that it is tracking gets modified. In practice, it may stop working altogether, because it may be tying to keep an eye on file names too, and what’s happening in the filesystem will leave it at an impasse.
Vim is doing all this to help you. It’s worried that there might be a crash just as you’re saving your file, and is trying to maximize the chances that you will be able to recover that last edit. Help like this, I don’t need. Vim never crashes, and neither does linux (what I use, although all the foregoing applies to any unix-like system, I’m pretty sure). Linux will freeze, effectively, when it runs out of memory—and programming is one of those activities where you will occasionally DOS your machine by writing some code that eats up all the RAM (if this never happens to you, you’re not trying hard enough). But, for me, saving in my editor is an unconscious tick (remapped to ,,
in Vim), everything is in git, I commit very often, I have a commit hook that copies changes to a remote machine, I back up my drive more than once a day, etc. I don’t like losing work. And I’m guessing that the side effects of Vim’s hand-wringing have caused more inconvenience than provided actual rescue.
You can turn this, and related, behavior off by putting these lines in your .vimrc
file:
set nobackup
set nowritebackup
set noundofile
set noswapfile
This is controversial. People will say the this is “dangerous”. It’s your choice. You can read the built-in Vim help to delve into the details of what each of these things does (enjoy!). To fix the particular problems discussed above, it’s enough to use just the first two lines, eliminating the “backup” procedure. You can keep the swapfile, but its presence can occasionally cause other problems. The undofile persists undo information between Vim sessions and reboots, but that’s why I use git. I don’t really want Vim to litter my filesystem with all of these piles of nervous energy.
An alternative is to tell Vim to use a special-purpose directory for whatever backup and swap files you think you need, so they’re out of the way. The instructions for doing so exist somewhere within the labyrinthine built-in documentation. It’s better, in my opinion, to adopt good data hygiene, on not depend on an editor’s half-baked and incautious attempt to preserve your files.
I believe that package maintainers should include the first two lines in the system-wide Vim configuration. These backup files are more trouble than any possible benefit they can provide, and demonstrably waste a lot of developer time as people try to figure out why things seem to be broken. And a default behavior that trashes a file without permission, even if it’s only a problem if a user chooses an unusual file name, should not be permitted.
Happy ending
I looked into Svelte because I wanted to create some interactive activities for my 10th grade statistics students to do during the COVID-19 lockdown, to supplement their lessons. They are home, with a variety of devices and varying internet reliability. The only way to deliver these activities to them in a form that everyone would be able to use is javascript running in the browser.
I had made a couple of things along these lines using what people are calling these days “vanilla javascript”, but now I needed to get more interactive things out faster. I didn’t have time to get good enough with one of the big libraries, nor did I like the idea of the bloated pages that they create. After reading up on Svelte, that seemed to be ideal.
I’m sure I’m not alone in indulging a certain kind of laziness by, at times, avoiding the documentation in favor of what you might call “optimistic programming”, where I write some code the way I think the library or framework should work, without seriously expecting that it will. And, usually, it does not, because things are harder than they should be. But I’ve had the recurrent experience, using Svelte, that my experimental code actually works, the first time, just the way I was hoping it would. It often feels like things there are easier than they should be.
Here are a few things I’ve made with Svelte this year. Autoloading certainly makes programming easier: