Vim quickfix list

Back when I started using vim, I was a C++ developer. Like many, I used vim because it was there. Like many, I found that hjkl to move around made no sense at first. Next thing I knew, not having them felt like missing a finger. Not long after, w, e, b, f, t or / were no-brainers to move faster.

Entered :make. It runs makeprg which defaults to make from within the editor. If the code compiles fine (as it always does), you live in a wonderful world. Else, vim takes the compiler’s output and fills the quickfix list.

The quickfix list is a special file that basically contains places in your codebase you can quickly jump to. It even jumps to the first one by default but you can avoid this with :make! instead.

To be fair, I was satisfied with this for a long time. The daily routine was simple: :make, fix, start again. But there is more. More than make and more than the first item on the list. There is even more than the list.

The other entries

Sometimes the first spotted error is not easy to grasp. Sometimes it’s just the consequence of something else in the code. Fixing that something else might fix other errors. So moving from one entry to the next with :next comes in handy.

If you’re just exploring the quickfix list you may want to go backward at some point. It’s as easy as :cprevious. You can even go back to the first entry with :cfirst or jump to the last with :clast.

Dude, I’m here to save keystrokes, not to have a stroke with too many commands!

Yeah, that’s right. We’re vim users after all. That’s why I’ve been using keymaps for years. I use <Leader>n and <Leader>p for :cnext and :cprevious. And their capital counterparts for :clast and :cfirst.

vim.keymap.set("n", "<Leader>P", function() vim.cmd("cfirst") end, { silent = true, desc = "Jump to first quickfix entry" })
vim.keymap.set("n", "<Leader>p", function() vim.cmd("cprevious") end, { silent = true, desc = "Jump to previous quickfix entry" })
vim.keymap.set("n", "<Leader>n", function() vim.cmd("cnext") end, { silent = true, desc = "Jump to next quickfix entry" })
vim.keymap.set("n", "<Leader>N", function() vim.cmd("clast") end, { silent = true, desc = "Jump to last quickfix entry" })

When done, you can use :cclose to hide the quickfix list away. And :copen to show it again. There again, I’ve a <Leader>o keymap to toggle back and forth.

vim.keymap.set("n", "<Leader>o", function()
    local is_open = false

    for _, window in ipairs(vim.api.nvim_list_wins()) do
        local buffer = vim.api.nvim_win_get_buf(window)
        if vim.bo[buffer].filetype == "qf" then
            is_open = true
            break
        end
    end

    if is_open then
        vim.cmd("cclose")
    else
        vim.cmd("copen")
    end
end, { silent = true, desc = "Toggle quickfix list" })

More than :make

Yep. There are other ways to fill the quickfix list.

I stopped being a dev a while ago. I’m an ops now. I don’t write code that compiles anymore so :make makes no sense. (Note for my later self: set makeprg to a static code validator to see if I can :make something out of it).

But I do need to search and find stuff in Infrastructure as Code repositories. And what’s better to find stuff than :grep? Well, it depends. If you’re not on a system with grep (be brave, I feel for you), you might want to use :vimgrep. Because, yes, vim has its own internal implementation of grep since :grep relies on the external and optional grep program.

Every cool kid uses ripgrep now. You should give it a try, it’s grep on steroids.

And once again, vim got you covered. Just like with :make that calls makeprg, :grep calls grepprg. You can have :grep call rg with the correct options to search for stuff even faster.

if vim.fn.executable("rg") == 1 then
    vim.opt.grepprg = "rg --vimgrep --no-hidden --no-heading"
end

And bonus points for this custom :Grep command I use. It takes arguments if need be, it opens the quickfix list but does not jump to the first occurrence.

vim.api.nvim_create_user_command("Grep", function(opts)
    local args = opts.args
    vim.fn.execute("grep!" .. (args and (" " .. args) or ""))
    vim.schedule(function()
        vim.cmd("copen")
    end)
end, { nargs = "+", complete = "file" })

What about a preview?

If you use nvim and have a couple of plugins in your configuration, chances are that telescope is part of them. You can run :Telescope live_grep to open a modal which allows you to search as you type.

What does it have to do with the quickfix list?

Just press C-q when you’re done searching. Boom, the results in the quickfix list!

Grep is not flexible enough

Whether it’s with the internal :vimgrep, the external :grep or the fancy :Telescope live_grep method, you may find your quickfix list has too many entries. You could engineer a smart :grep command with extra options to search for multiple patterns and exclude some others. Or… You can have a more iterative approach by searching for more than what you need then trim the fat.

Back in the day, that was built-in. Now, it’s part of a plugin that comes with vim. All you have to do is :packadd cfilter and you gain access to the :Cfilter command (note the capital C). The :Cfilter <pattern> does exactly what you think it does. :Cfilter foo will take the current quickfix list and filter every entry that does not match foo, effectively generating a new quickfix list. Rinse and repeat until you’re left with what matters to you.

Really? You want me to enable a plugin every time I have to use a command?

No. Not really. Just add vim.cmd("packadd cfilter") in your configuration and forget about it forever.

Oops, I went too far

Was your filter pattern too wide? Did you remove entries you wanted to keep?

Well, just like the undo tree vim keeps internally, you can rely on the fact that vim keeps the various quickfix lists at hand. You can use :colder to go backward and :cnewer to go forward in the list. I’ve had keymaps for those too but I don’t use them enough to remember the keys so I removed them.

What else can it do?

Well, there are a couple of powerful features I would have loved to know about before. But hey, that’s the beauty of those tools. It takes a few hours to learn the basics and years to master… Well no, it has no end.

Hey, you’re losing it again. Back on track, now!

Yeah, sorry. I’m sure you noticed every command that dealt with the quickfix list started with a c? What can it do? Well, it can :cdo.

The :cdo <command> will loop over each entry of the quickfix list and run the <command> on it. This is why it’s important to know about :Cfilter: you don’t want to :cdo too wide.

Looks like a sniper thing. I’m more of a tank myself.

Well, you can always use :cfdo <command>.

That one is a tad more messy. It runs the command on every file referenced in the quickfix list.

While :cdo will surgically target the specific lines, cfdo will act on whole files. We don’t always need to be subtle, do we?

Conclusion

No matter how you fill the quickfix list, it comes in handy to know how to manipulate its items, move through them, go back and forth in time and act on its content.

The quickfix list is a shared list across a vim instance. If you need a more per-window similar feature, have a look at the location list. Most of what’s written here works on them. Only with a l instead of a c to prefix the commands.

Feel free to drop me a line on mastodon if you want to have a chat about all this!