Vim Tip #33: Vim :compiler and :make

2020-10-01(Thu)

tags: Vim

Vim Tips

It's possible from within (Neo)Vim to build the program you're working on, and not just by running an external program with :!make clean or !javac %. Vim supports doing builds - quite well, in fact. But, like so many Vim things, it takes a bit of work to get it set up. I'll be talking about setting this process up for Java, as that's the language I'm using the most at the moment, but this applies to almost any other language as well.

The first thing you should do to see the extent of the support available, is to run :compiler. This will show you all the compiler plugins available. On my Fedora 32 system using NeoVim, this shows 65 compiler files in the /usr/share/nvim/runtime/compiler/ folder. For working with Java, I run :compiler javac (you're choosing the Vim file in that folder called javac.vim, not the command javac - although it will ultimately be used), and after that I can type :make % to compile my current Java file. % is essentially "the current file" to Vim. You might think "so what, I can run :!javac % and the same thing happens! Or I can do it in tmux or a terminal or ..." You're right - and you're wrong. Because, using Vim's :make % does one more very important thing: it parses any errors, and turns them into a QuickFix list that allows you to jump to the right line number with the error easily accessible. See :help quickfix for more on that.

The above setup didn't do exactly what I wanted it to: but like everything in Vim, it's user-configurable. I wanted to not only compile the file, but compile it without naming it and also run it. I created a new folder ~/.vim/compiler/. I copied /usr/share/nvim/runtime/compiler/javac.vim to ~/.vim/compiler/gjavac.vim and modified it:

if exists('current_compiler')
  finish
endif
let current_compiler = 'javac'

if exists(':CompilerSet') != 2              " older Vim always used :setlocal
  command -nargs=* CompilerSet setlocal <args>
endif

CompilerSet makeprg=javac\ %\ &&\ java\ %:r

CompilerSet errorformat=%E%f:%l:\ %m,%-Z%p^,%-C%.%#,%-G%.%#

I've included the entire file as I haven't shown one yet and I want to discuss some other parts of it. I named it gjavac.vim so the name is different from the default javac.vim and I can easily choose the one that works best at the time. Let's look at the change I made to this file: CompilerSet makeprg=javac; became CompilerSet makeprg=javac\ %\ &&\ java\ %:r. I had to look this up online, because putting the makeprg command in quotes didn't work, I had to search to find that you need to backslash-escape your spaces. (And %:r is the current filename without its extension.) That done, it works.

I'm not sure it's a good idea in the long term for a couple reasons. It works well now because I'm working with simple command line Java programs that output a few lines of text and exit: it might not be so well behaved with complex projects, requested input, or graphical programs, and wouldn't work at all with something that used Enterprise Beans. Likewise, the errorformat setting is for the compiler javac, it is NOT for java. So this should catch compiler errors, but won't know what to do with runtime errors. But, for me, right now ... it's great. And I can change it really fast.

As presented above, you'd still need to set :compiler ... every time you opened the editor. A good way to fix this is to go into your ~/.vim/ftplugin/ folder (NeoVim users remember that mentions of ~/.vim/ are the equivalent of ~/.config/nvim/) and add a line to the file java.vim (or you can create the folder and file if they don't exist) and add the line compiler javac (or compiler gjavac in my case). From then on, whenever you open a Java file the :make command will be set.

I should point out that Vim has an incredibly flexible attitude about what constitutes a make program. In fact, it doesn't care at all: if you set up a :make command for text files that actually toggles the lights in your living room ... it'll work and Vim won't care. A saner option would be to run something like a linter. I covered setting up linters inside Vim in Lint, Linting, and Linters (and Vim), but that's a hassle and may not work for you. This method is simpler - although unless you rewrite the errorformat variable, you won't get a Quickfix list. That's the last thing I'm going to cover today: errorformat looks like a pain to work with, but this video ("Testing compiler plugins") by YouTube user wincent will give you a good start.

Further Reading

To better understand all of this, Vim's help is - as usual - a bizarre combination of totally opaque and brilliantly enlightening. Try the following:

  • :help make
  • :help makeprg
  • :help CompilerSet
  • :help compiler