Ok, that's a slight misquote from Galaxy Quest ("Never give up, never surrender"), but I want to talk about quitting vim today, so I'm taking creative license. There's a lot of jokes about quitting vim, like this and this. That's because quitting vim is a little different than quitting other applications. I'm not sure what kind of things people try when they want to quit vim. Probably ctrl-c ranks highly, but when you do that vim actually tells you how to quit vim. As with all vim things, there's a few ways. Here's some of them:

  • :quit - In vim : puts you in command mode where things you type show up in the bottom left corner. The command quit means quit. Yep.
  • :q - A lot of vim commands have shorter aliases. q is such an alias.
  • :wq - Write and quit (analogous to "save and close" in other programs).
  • :quit! or :q! - Force quit. If vim detects that you have unsaved changes, it won't let you exit with :q. You have to quit like you mean it. Which in this case means using ! (which vim calls "bang"). A command with a bang doesn't necessarily have one universal meaning like it does in (for example) Ruby. In fact custom commands can allow a bang and handle it however they like. But with :quit it means abandon changes.
  • ZZ - Save the file (if there are changes) and exit vim.
  • ZQ - Exit without checking if there are changes.
  • :x, :xit, :exi, :exit - Save (if there are changes) and exit.

And there's more. Seriously. I didn't list :qall, :confirm qall, :wqa, :xa . . .

But . . .

I'm actually writing this post to tell you not to quit vim. How can you do that? Well, most of the things you'd probably quit vim for are things you want to do on the command line. Maybe you like exiting vim before changing branches or committing and pushing changes. Maybe you move, copy, or delete files. The point is, for some reason you've decided whatever you need to do next can't be done (or can't be done gracefully) in vim. This is a notion of which I would like disabuse you. Vim is pretty strongly integrated with the terminal, and you can do a lot without quitting. Probably a strong indicator of vim experience level is how frequently you quit to get back to the terminal. I used to quit vim a lot to go do something in the terminal, but now I try to do as much as possible without leaving vim. To the point that I even wrote a small plugin for myself called "neverquit" that prevented me from existing with :q and :quit (as if quitting vim wasn't hard enough), although I recently abandoned that as I've gotten better at not leaving vim, and commands like :q! and :wq still let me quit, so I was working around the plugin's original intent.

For a long time, vim's terminal support was via !, e.g. :!cp foo.js bar.js. Translated, that means:

  • : - Run a command
  • ! - Shell out
  • cp - Run the cp binary
  • foo.js bar.js - To copy foo.js to bar.js

This is ok but it's a bit ichy because vim suspends it's own interface to run the command, so you get a weird flash of the terminal before vim comes back, and it happens synchronously, so you can't do anything else. It's possible to work around the first shortcoming via silent, but then you have to :redraw when you come back to vim. And the synchronous nature of it is still a problem. There's also :call system('command'), but once again, this is synchronous, and it's a lot to type, and if the command has any output, you have to parse it yourself. So what can you do? The intent of this post (yes, now, after 600+ words) is to you introduce you to the two plugins that most helped me stop quitting vim: Determined and Fugitive.

Vim 8.0 introduced asynchronous terminal support via the term_start() function, which is much better than the synchronous :!, but it's overwhelmingly annoying to type :call term_start('cp', { 'curwin': 1, 'term_finish': 'close', 'vertical': 1 }) every time. The first of the two plugins is actually one that I wrote as a wrapper for term_start and since then, I use it all the time because it's so versatile. Vim-determined let's you create commands that call term_start with preset arguments. It also adds some nifty configurability. Here's all the commands in my vim files that I'm creating with Determined (it's not that many . . . it really could be a lot more).

call determined#command('Npm', 'npm', { 'vertical': 0, 'autoclose': 1, 'rows': '5', 'cols': '40%' })  
call determined#command('Repl', 'node', { 'background': 0 })  
call determined#command('Node', 'node', { 'background': 0, 'vertical': 0, 'rows': '10', 'cols': '40%' })  
call determined#command('Rg', 'rg', { 'background': 0, 'reuse': 1, 'complete': replete#byArg('Rg', ['replete#complete#bufwords', 'file']) })  
call determined#command('Grunt', 'grunt', { 'reuse': 1 })  
call determined#command('Gulp', 'gulp', { 'reuse': 1 })  

To create a command with vim-determined, you call the autoload function determined#command. The first argument is the command name to create, the second argument is the base command line binary, and the third argument is a dictionary of options. You can read the README on the vim-determined repo for the full list of options, but I'll go over a few of the more interesting ones here.

First, let's talk about what these do and how to use them. Determined accepts additional command line arguments after the vim command, so I typically use the first command like this: :Npm i -S chalk or :Npm r -S grunt-contrib-watch. You'll notice in the options for this command, that vertical is 0, so the terminal pane for this command opens along the bottom (for me, because I have splitbelow set). autoclose sets term_finish to 'close' in the arguments to term_start, which means that the terminal will close as soon as it completes. For npm installing, that's perfect. rows and cols specify how big the terminal window should be. I make it very small so it stays out of the way. You might be wondering why I have both rows and cols when I just explained that vertical: 0 makes this a horizontal window. What do I need cols for? Determined commands accept a !, which means "invert the default orientation." So I could still install something in a vertical split but running :Npm! i -S express. Note that determined allows you to use literal values or percentages for height and width.

The second command is :Repl which opens a node repl in a vertical split. I write lots of javascript at work, and I use this maybe once or twice a day to make sure some snippet of code I'm working on does what I think it should. Here we see a new option background. Because the intent of this command is for me type in the newly opened repl, I don't want to send control back to the original window (which is the default behavior . . . this let's you keep working on things while the command runs). background: 0 let's you tell determined to leave you in the terminal window once it opens.

Next we have :Node. :Node and :Repl actually both just run node so I wouldn't have to have both, but I use them semantically differently. I always use :Repl by itself to launch a node repl, but I use :Node for anything else I might need to do, like run a script (:Node foo.js) or evaluate something (:Node -e "console.log('banana')").

The :Rg command is one of the most useful commands I have. I use this probably half a dozen times an hour. It's similar to running :grep, but it doesn't populate the quickfix list. Instead it gives me standard looking grep results in a vertical split, and I can still use things like gf on a file name there to open a file and inspect things more closely. The :Rg command introduces us to the reuse option, which means, if a terminal is already open that has the exact same arguments, use that window to rerun the command. This is helpful with rg, for example, when I'm trying to remove all references to something. I can grep for references, open one of the files and make changes, and then essentially refresh the grep window by running the same :Rg command. :Rg also introduces the complete option, which creates custom command completion. This is both a good and bad example of completion because the completion for :Rg is awesome and uses another plugin of mine called replete, which let's you create separate completion types according to which argument you're on (i.e. you can complete the first word differently, maybe via buffer names, than the second word, maybe by files in path). It also provides a custom completion function for words in the current buffer, which is almost always what I want to grep for. The bad news is that replete is not yet published, so you'll have to roll it yourself for now.

There's also a expand flag to make Determined handle certain vim built-ins for you, so you can do things like :Rg <cword> to search for references to the word under the cursor or :Node % to execute the current file as a node script.

I'll say less about Fugitive because it's well-documented (and well-known within the vim community). Fugitive is a vim plugin for git and per the Fugitive README, it is "A Git wrapper so awesome, it should be illegal." And that really is an excellent description. With fugitive, there's virtually no reason you'd ever need to leave vim to do something with git. And it's not just a wrapper for git commands; it integrates with core vim features too. So when you run :Glog (git log) using fugitive, the results populate the quickfix window so you can bounce backward through the history of a file very quickly to see it change over time. :Gblame (git blame) opens a side panel with the blame for the current file, but it also sets scrollbind to keep the two panes in sync, and hitting <Enter> on a sha in the blame will open that commit, from where you can open files changed in that commit or continue to walk the parent shas backward in history.

Other fugitive commands I use frequently include the following. :Gbrowse (which requires the hub plugin for Github) opens the remote version of the current file in a browser. :Gtabe, :Gvsp, etc. opens any commit-ish file. I typically use it to open the current file in master (or another branch). For instance, to compare my current changes to master, I use :Gvsp master:%. If I need to pull in or look at changes from another branch, I use :Gvsp some-branch:some/file.js. :Gedit (a same-window version of the previously mentioned :Gtabe and :Gvsp) is useful, when used with no arguments, for restoring the current index version of a file (essentially undoing all changes to that file). :Gstatus and :Gdiff are really useful for comparing, reverting, and staging changes. Plus, it's really easy to extend fugitive to work with aliases you've created because of the generic catch-all :Git. I have a bunch of git aliases wrapped into commands with this simple function:

function! s:GitWrapper(action)  
  silent exec ":silent Git" a:action

which lets me do things like this

command! -nargs=0 Gnuke call <SID>GitWrapper('nuke')  

to invoke my git alias nuke.

There are other useful vim plugins for staying in vim as well, like vim-eunuch, basically any good buffer explorer (I use my own called vim-rebuff), live markdown renderers such as vim-xmark, vim-instant-markdown, or livedown, and many, many others. And if these things aren't enough, creating new tmux panes, windows, or sessions can also solve many problems by giving you access to multiple terminals at once so that you can leave one dedicated to vim.

The point is, you really don't ever need to leave vim. I think a worthwhile exercise is to look at the things that make you type :q then search for a vim plugin that might do those things from within vim (or in the unlikely case that one doesn't exist, roll your own, as this is a great way to help others and learn some vimscript on the way!). According to stack overflow and common internet lore, it is difficult to quit vim. So stop trying and embrace the convenience of doing it from within vim!