DEV Community

woland
woland

Posted on

How to write vim9script or How to Compile and Run C code from Vim

Using a simple vimscript, we can easily compile and run any C code without leaving Vim! We will use vim9script for this.

CompileAndRun Function

Functions in vim9script are defined with the def keyword. Since Vim uses an algol-like syntax, enddef will denote the end of the functions.

We will define several variables with the var keyword since vim9script doesn't use the let keyword.

vim9script

def CompileAndRun()
    var current_file = expand('%')
    var file_name = fnamemodify(current_file, ':t:r')
Enter fullscreen mode Exit fullscreen mode

As with every vim9script, we start by telling vim that this is in fact vim9script!

Then we get the name of the current file using expand(), then using the fnamemodifty() and :t:t we will strip away the path and the extension, keeping only the basename.

  • :t: Takes only the "tail" (or basename) of the file path, removing the directories.
  • :r: Removes the extension from the filename.

Typically expand('%') returns filename.extension if you directly open a file with vim filename.extension but if the file was opened from a relative path, such as vim ~/some/path/filename.extension it would also return the path, so we have to clean it up.

We need another variable to store our compile command, for starters, a simple gcc compile command should do:

var compile_cmd = 'gcc ' .. current_file .. ' -o ' .. file_name
Enter fullscreen mode Exit fullscreen mode

The .. is how we concatenate strings, in vim9script (it used to be one dot in legacy vimscript similar to PHP). You may have also noticed that we are using the extracted file name for our output binary.

After that we can simply compile and execute the output binary.

var compile_result = systemlist(compile_cmd)
execute 'terminal ./' .. file_name
Enter fullscreen mode Exit fullscreen mode

So together:

def CompileAndRun()
    var current_file = expand('%')
    var file_name = fnamemodify(current_file, ':t:r')
    var compile_cmd = 'gcc ' .. current_file .. ' -o ' .. file_name 
    var compile_result = systemlist(compile_cmd)

    execute 'terminal ./' .. file_name
enddef

defcompile
Enter fullscreen mode Exit fullscreen mode

The defcompile command in the end does exactly what it says, which is compiling our functions into bytecode.

Since vim9script functions are scoped differently than legacy vimscript, we need to define a command to access them.

command! CompileAndRun call CompileAndRun()
Enter fullscreen mode Exit fullscreen mode

Now we can run :CompileAndRun.

demo_gif_1

Ok that's fine but what about errors? Warnings? WE NEED THOSE! So let's improve our script!

Let us add a condition that checks for v:shell_error, see :h v:shell_error.

    if v:shell_error != 0 || !empty(compile_result)
        botright new +setlocal\ buftype=nofile\ noswapfile\ bufhidden=wipe
        call setline(1, compile_result)
        return
    endif
Enter fullscreen mode Exit fullscreen mode

Here is a summary, botright new creates a horizontal split, the +setlocal options ensure that it's a scratch buffer, systemlist() executes the binary while handling newlines properly, setline() puts the results at the first line of the split buffer.

Let's also add -Wall to our compile command and create an unused variable in our simple test C code.

var compile_cmd = 'gcc -Wall ' .. current_file .. ' -o ' .. file_name
Enter fullscreen mode Exit fullscreen mode

demo_gif_2

Not bad ey? We can still make it better!

Another perk of using systemlist() is that it already captured 2>&1 aka stdout and stderr for us so we don't need to perform a redirection.

  • Can we add syntax highlighting to the warnings and errors? Yes! Just add set filetype=c before call setline().

  • Can we auto-close this split? Yes! But it needs a bit more code.

We will give a name to our split buffer, then using an augroup auto close it on leave.

if v:shell_error != 0 || !empty(compile_result)
    botright new +setlocal\ buftype=nofile\ noswapfile\ bufhidden=wipe
    file CompileErrors
    set ft=c
    call setline(1, compile_result)
    return
endif

augroup AutoCloseCompileErrors
    autocmd!
    autocmd! BufEnter * if bufexists('CompileErrors') && bufname('CompileErrors') != '' | silent! execute 'bdelete! CompileErrors' | endif
augroup END
Enter fullscreen mode Exit fullscreen mode

This ensures that when we change focus from a buffer with the name CompileErrors, the buffer with be deleted and therefor the split will be closed.

However, there may be times when we'd want to keep the split open as we work, so we can create a boolean to set it to 1 or 0 at runtime.

g:close_err_split = true
Enter fullscreen mode Exit fullscreen mode

And we'll modify the augroup and format it nicely:

augroup AutoCloseCompileErrors
    autocmd!
    autocmd BufEnter * if g:close_err_split &&
                \ bufexists('CompileErrors') &&
                \ bufname('CompileErrors') != ''
                \ | silent! bdelete! CompileErrors
                \ | endif
augroup END
Enter fullscreen mode Exit fullscreen mode

We will be applying the same checks and niceties to the run command as well. So instead of simply running the binary, we can do the following:

g:close_run_split = true

if v:shell_error == 0 
    botright new +setlocal\ buftype=nofile\ noswapfile\ bufhidden=wipe
    resize -10
    file RunBin
    var results = systemlist('./' .. file_name)
    call setline(1, results )
endif

augroup AutoCloseRunBin
    autocmd!
    autocmd BufEnter * if g:close_run_split &&
                \ bufexists('RunBin') &&
                \ bufname('RunBin') != ''
                \ | silent! bdelete! RunBin
                \ | endif
augroup END
Enter fullscreen mode Exit fullscreen mode

You may have noticed that this time we are also determining the size of the split.

demo_gif_3

Let us add the last touch, which is the ability to define the compile flags at runtime. For that we will have to add a condition to check for a g:custom_compile_flag and initiate it to be empty at first since vim9script is picky with empty variables.

if !exists('g:custom_compile_flag')
    g:custom_compile_flag = ''
endif
Enter fullscreen mode Exit fullscreen mode

And of course, we have to also modify our compile_cmd:

var compile_cmd = 'gcc ' .. g:custom_compile_flag .. ' ' .. current_file .. ' -o ' .. file_name
Enter fullscreen mode Exit fullscreen mode

Now we can change our compile flags at run time, just don't forget to add a space in the end!

Let's test:

demo_gif_4

As you can see, we can change our compile flags at runtime without any issues!

The whole script is as follows:

vim9script

def CompileAndRun()
    var current_file = expand('%')
    var file_name = fnamemodify(current_file, ':t:r')

    if !exists('g:custom_compile_flag')
        g:custom_compile_flag = ''
    endif

    var compile_cmd = 'gcc ' .. g:custom_compile_flag .. ' ' .. current_file .. ' -o ' .. file_name
    var compile_result = systemlist(compile_cmd)

    if v:shell_error != 0 || !empty(compile_result)   
        botright new +setlocal\ buftype=nofile\ noswapfile\ bufhidden=wipe
        file CompileErrors
        set ft=c
        call setline(1, compile_result)
        return
    endif

    if v:shell_error == 0 
        botright new +setlocal\ buftype=nofile\ noswapfile\ bufhidden=wipe
        resize -10
        file RunBin
        var results = systemlist('./' .. file_name)
        call setline(1, results )
    endif
enddef

g:close_err_split = true
g:close_run_split = true

augroup AutoCloseCompileErrors
    autocmd!
    autocmd BufEnter * if g:close_err_split &&
                \ bufexists('CompileErrors') &&
                \ bufname('CompileErrors') != ''
                \ | silent! bdelete! CompileErrors
                \ | endif
augroup END

augroup AutoCloseRunBin
    autocmd!
    autocmd BufEnter * if g:close_run_split &&
                \ bufexists('RunBin') &&
                \ bufname('RunBin') != ''
                \ | silent! bdelete! RunBin
                \ | endif
augroup END

command! CompileAndRun call CompileAndRun()
nnoremap <F8> :CompileAndRun<CR>

defcompile
Enter fullscreen mode Exit fullscreen mode

We've also added the F8 keybinding to access our command more easily.

Note

Vim already has the compiler and make commands that you should add to your arsenal. This article only tries to teach some vim9script and some of the things that you can very easily achieve with it.

I hope that you enjoyed this article and if you did, leave a comment or a reaction.

Top comments (0)