Unless you're working in an environment where gnu make is not available, or poorly configured (mingw), you won't need any Makefile
for mono-file projects. They are not mandatory in that case, and IMO more of an hassle than anything else.
Compilation is best done with:
:make %<
This way any errors directly go to the quickfix windows. This will improve your productivity to locate errors (:h quickfix
). Also, whether your current file is in C, C++, Fortran... or any language recognized by default gnumake configuration, you don't have to specify the compiler you wish to use depending on the current filetype. If you really want to select another compiler for C++ for instance, you can use
:let $CXX ='clang++'
" $CC for C, and so on
And if you want to change your compilation options
:let $CXXFLAGS = '-std=c++17 -Wall -Werror'
" $CFLAGS for C, $LDLIBS, $LDFLAGS for the linker, and so on
Note also that if you have a Makefile
, it'll be automatically used.
Chaining with the execution
:!make %< && ./%<
indeed is simple enough to chain both steps. Alas, we don't have the direct equivalent with :make
. We have to analyse the quickfix list to see if there were any issue
If filter(getqflist(), 'v:val.valid != 0')
is not empty we can know whether problems have been detected. But it doesn't tell whether they are warnings or errors. We can have the complete information with the following
" From my build-tools-wrapper plugin
function! lh#btw#build#_get_metrics() abort
let qf = getqflist()
let recognized = filter(qf, 'get(v:val, "valid", 1)')
" TODO: support other locales, see lh#po#context().tranlate()
let errors = filter(copy(recognized), 'v:val.type == "E" || v:val.text =~ "\v^ *(error|erreur)"')
let warnings = filter(copy(recognized), 'v:val.type == "W" || v:val.text =~ "\v^ *(warning|attention)"')
let res = { 'all': len(qf), 'errors': len(errors), 'warnings': len(warnings) }
return res
endfunction
From this we can decide to stop just on errors, or on errors and warnings.
Optional inputs
With filereadable()
we can know whether the input file is here.
It thus becomes:
let exec_line = '!./' . expand('%<') " we could also use the complete path instead
let input = expand('%:p:h')/.'input.txt'
if filereadable(input)
let exec_line .= ' < ' . input
endif
exe exec_line
If you want to redirect the result in a :terminal, this time unfortunately, redirection cannot be used with Vim (it works with nvim though)
TL;DR
The final code (given the previous function to detect errors & warnings) becomes.
function s:build_and_run(file) abort
let tgt = fnamemodify(a:file, ':r')
" to make sure the buffer is saved
exe 'update ' . a:file
exe 'make ' . tgt
if lh#btw#build#_get_metrics().errors
echom "Error detected, execution aborted"
copen
return
endif
let path = fnamemodify(a:file, ':p:h')
let exec_line = '!./' . tgt
let input = path.'/input.txt'
if filereadable(input)
let exec_line .= ' < ' . input
endif
exe exec_line
endfunction
nnoremap μ :<C-U>call <sid>build_and_run(expand('%'))<cr>