Vim tiny IDE: edit-compile-edit cycle for fast error browsing



This is continuation of previous post: VIM and Tmux as a tiny quasi IDE

Intro

Here in this post I will show you how I use a bit of scripting together with vim’s quickfix window to speed up my workflow of finding compilation errors. There reason to use bash instead of makefiles is because it is easier for me to reason what is happening in bash script than in makefile and it is just easier to debug when compared to makefiles.

Here is a short video where I used this method to do simple c++ compilation, a a little dart AOT compilation and java build process. Then I run on a bit small software renderer project where I put errors before hand then then found them with compilation script. At the very end I show how searching works with using and .

Disclaimer: This is just one of the ways to approach it. I do not endorse it or say that it is better than anyther way. Whatever way you use is the best but this one pretty good for me and maybe someone else might like it.

The process

First of all in my case for every project or task I prefer to create a build.sh script which does the building. The process to launching it is the same across all projects but it might contain a lot of different things. For C++ it will compile it (and sometimes might launch it), for dart it will use dart toolchain, for android I might build JNI code and push it to remote phone and etc. But in order not be abstract let’s focus on simpole C++ project.

For this I will need two files main.cpp and build.sh. I will put main.cpp and build.cpp in “project1/src” location. Then cd into ‘project1/src’ and fill in contents for those files.

main.cpp

int main(int argc, char **argv) {
    printf("Hello world!\n");

    action();
}

build.sh

mkdir -p ../build/
pushd ../build/

gcc ../src/main.cpp -o main

popd

This version of main.cpp won’t work because it has invocation of a function action() and is missing include of “stdio.h> (or ) but it is what we need. Also we need to make our build.sh file executable.

chmod +x build.sh

Maybe for this setup you might argue that build.sh is not needed but usually projects are a bit more involved and always using this script just unifies everything. The the build script is creating a build directory inside root directory and then “cd” into it. It is good as all intermediary files will be created there and not mess our source directory. All this is regular stuff and everybody does this, some might use make, some cmake but steps are quite similar. What makes it better is special function that we add in our vimrc.

Vim setup

The trick is already built in into vim and is called quickfix window. You can read more about it with “:h quickfix”. It is used to populate with a list of file locations and can be later used to quickly jump between those. There is a way to tell vim compile code within vim but using a build script give a more flexibility for pre and post processing. So in the vimrc file we will add this function:

function! CompileProgram()
    if !exists("g:MyQuickFixIsOpen")
	let g:MyQuickFixIsOpen = 0
    endif

    if g:MyQuickFixIsOpen == 0

	let current_filename = expand("%:p")
	let current_folder = current_filename
	let script_name="build.sh"
	let foundFile = 0

	while(len(current_folder) > 1)
	    let current_folder = fnamemodify(current_folder, ":h")
	    let build_file = current_folder . "/" . script_name

	    if current_folder == "/"
            let build_file = current_folder . script_name
	    else
            let build_file = current_folder . "/" . script_name
	    endif

	    if filereadable(build_file)
            let foundFile = 1
            let result = setqflist([], " ", {'lines' : systemlist(build_file)})
            botright copen
            let g:MyQuickFixIsOpen = 1
            break
	    endif
	endwhile

	if !foundFile
	    echom "This is no build.sh up till /"
	endif
    else
	let g:MyQuickFixIsOpen = 0
	cclose
    endif
endfunction

It is simple function that will start searching for a file name build.sh starting from current directory all the way up to the / (you might say that it is a security issue but it is not a problem for me since if there is anybody that can put build.sh in those directory already has all the access). When it finds, it executse the files and load result into quicklist window on the bottom. And that is almost it you got yourself a good enought edit-compile-edit cycle.

But let’s take it a little further to make it more easy to use. First of all we bind a shortcut so that we will not have to call it by typing function name. Then I bind ctrl-j and ctrl-k keys to move up and down of the quick list (which is also has a neet side effect).

nnoremap <Space> :call CompileProgram()<enter>

nnoremap <C-j> :cnext<CR>
nnoremap <C-k> :cprevious<CR>

I bound space key to launch compilation but I also map , and m to the same function. I used to test all these combination but space one is the most comfortable for me these days.

We are fully done now and can use this sytem and if we cd into “project1/src” and from there launch “vim main.cpp” we can start editing. Then in normal mode just press space and will compile it with errors and show this screen:

Result of running build.sh step1

It opened ’error window’ and put cursor on the first error. Now if we press enter it will jump to correct location. Then you can jump up and down error list by pressing and . When done you can press space again and it will close quickfix window. So the process is edit text, press space, edit text, press space and so on.

Result of running build.sh step2

And this is how the quick windows look once we fixed all the errors. So in my case when I am done with some small portion of code I begin this edit-compile-edit cycle until I see this window and then I can go safely launch the program. Of course it does not mean that there are not bugs but for that ther is a different process and it is totally different story.

Extra helpful side effect

One additinal feature is that these introduced shortcuts for move up and down the error list could be used for searching. It works because when you grep (or any other searching tool) for something it will populate this exact same quickfix window with a list of file locations. It is totally accidental found for me and I already got so used to that I cannot imagine going back to using :cn and :cp.

Speed

This method is ofcourse pretty slow when you try to build a usual C++ project because I use a synchronous method to compile and will block ui for the time of compilation. There is a way to do it asynchronously but I didn’t do it as I was a bit lazy. If I ever decide to add async way I will add it here.

What about lua

I tried moving to lua and now moved back vimscript as my vim needs are pretty minimal and I don’t have time to fully utilize lua but I have a function I wrote for nvim/lua version and if you are lazy to write you own you can copy this one:

GetContainingDir = function(Path)
    return vim.fn.fnamemodify(Path, ":h")
end

function CompileProgram()
    local windowinfo = vim.call("getwininfo")
    for i, window in pairs(windowinfo) do 
        local is_quickfix_open = window['quickfix']
        if is_quickfix_open == 1  then
            vim.cmd('cclose')
            return
        end
    end

    local BuildFileName = "build.sh"
    local CurrentFilename = vim.api.nvim_buf_get_name(0)
    local WorkingDir = CurrentFilename

    while string.len(WorkingDir) > 1 do
        WorkingDir = GetContainingDir(WorkingDir)

        if WorkingDir == "/" then
            BuildFile = WorkingDir .. BuildFileName
            WorkingDir = ""
        else
            BuildFile = WorkingDir .. "/" .. BuildFileName
        end

        local IsFileReadable = vim.fn.filereadable(BuildFile)

        if IsFileReadable == 1 then
            local ListOfLines = vim.fn.systemlist(BuildFile)
            vim.fn.setqflist({}, " ", {lines = ListOfLines})
            vim.cmd('botright copen')
            return 
        end
    end
    print("There is not build.sh found.")
end

My knowledge of lua is few hours so it is probably bad and inefficent so use with caution.