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.
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.
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.
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.
Normally, you keep track of your files through their names, such as
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
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
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.
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.
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 feel 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: