Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

vim mapping for running c++ with optional input file

Tags:

c++

vim

I am configuring vimrc file for competitive programming in c++.

autocmd filetype cpp nnoremap <F5> :w <CR> 
                                   :!g++ -o %:r % <CR> :!./%:r < input.txt <CR>

The following mapping is when I press F5, it saves, compiles (%:r is filename without .cpp), and runs with input.txt file.

However not every cpp file has input.txt, so I want to pipe input.txt only when input.txt exists in the current directory.

To sum up, what is the best way of checking files during the mapping?

like image 602
Sean Avatar asked Apr 25 '26 01:04

Sean


1 Answers

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>
like image 197
Luc Hermitte Avatar answered Apr 26 '26 16:04

Luc Hermitte



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!