Skip to content

Instantly share code, notes, and snippets.

@bfrg
Last active October 3, 2024 17:12
Show Gist options
  • Save bfrg/63e49d4a7c2d43fe9206b104e7837aff to your computer and use it in GitHub Desktop.
Save bfrg/63e49d4a7c2d43fe9206b104e7837aff to your computer and use it in GitHub Desktop.
Toggle the quickfix and location-list windows and resize the quickfix window automatically
" Open/close/toggle the quickfix and location-list windows
" ------------------------------------------------------------------------------
" File: autoload/qf/window.vim
" ------------------------------------------------------------------------------
" Toggle quickfix or location-list window
function! qf#window#toggle(loclist = 0)
" Check if quickfix window is open in current tabpage, or if current
" window's location-list window is open
let is_open = getwininfo()
\ ->filter('v:val.tabnr == tabpagenr()')
\ ->filter({_,info -> a:loclist
\ ? info.loclist && info.winid == getloclist(0, {'winid': 1}).winid
\ : !info.loclist && info.quickfix
\ })
\ ->len()
if is_open
execute a:loclist ? 'lclose' : 'cclose' .. (&filetype ==# 'qf' ? '| wincmd p' : '')
else
let size = a:loclist ? getloclist(0, {'size': 1}).size : getqflist({'size': 1}).size
if size
" See https://github.com/vim/vim/issues/5037
execute a:loclist ? 'lopen 1' : 'botright copen 1'
execute 'resize' min([10, size])
endif
endif
endfunction
" Same as :cwindow and :lwindow but resize the quickfix window automatically
function! qf#window#open(loclist = 0)
let size = a:loclist ? getloclist(0, {'size': 1}).size : getqflist({'size': 1}).size
if !size
execute a:loclist ? 'lclose' : 'cclose'
else
execute a:loclist ? 'lopen 1' : 'botright copen 1'
execute 'resize' min([10, size])
endif
endfunction
function! qf#window#close()
if getwininfo(win_getid())[0].loclist
lclose
else
cclose
" Required only after cclose
wincmd p
endif
endfunction
" ------------------------------------------------------------------------------
" File: vimrc
" ------------------------------------------------------------------------------
" Don't scroll windows when opening new horizontal splits
set splitkeep=topline
" Toggle quickfix and location-list windows
nnoremap <silent> goc :<c-u>call qf#window#toggle(0)<cr>
nnoremap <silent> gol :<c-u>call qf#window#toggle(1)<cr>
augroup quickfix
autocmd!
autocmd QuickFixCmdPost [^l]* ++nested call qf#window#open(0)
autocmd QuickFixCmdPost l* ++nested call qf#window#open(1)
autocmd VimEnter * ++nested call qf#window#open(0)
augroup END
" ------------------------------------------------------------------------------
" File: after/ftplugin/qf.vim
" ------------------------------------------------------------------------------
" Close quickfix window
nnoremap <silent> <buffer> gq :<c-u>call qf#window#close()<cr>
@Konfekt
Copy link

Konfekt commented May 28, 2024

This is convenient, thank you! I did not mind the message that there's no location list, so postponed the size check to

        " See https://github.com/vim/vim/issues/5037
        execute a:loclist ? 'lopen 1' : 'botright copen 1'
        if size | execute 'resize' min([10, size]) | endif

@bfrg
Copy link
Author

bfrg commented May 28, 2024

I completely forgot about this gist. Note that nowadays we have :h splitkeep which makes the calls of s:windo(0) and s:windo(1) redundant. Just add set splitkeep=topline to your vimrc and then whenever you split a window, the buffer won't be scrolled.

@Konfekt
Copy link

Konfekt commented May 28, 2024

Thank you, that's good to know

@Konfekt
Copy link

Konfekt commented May 28, 2024

I particularly appreciate botright copen, that's a natural way to open the quickfix list, whereas the simple copen sometimes ended up with a crammed split window.

@Konfekt
Copy link

Konfekt commented May 29, 2024

It's hard to come up with :botright copento prevent the quickfix list from appearing in the bottom right of the right split window.

@bfrg
Copy link
Author

bfrg commented May 29, 2024

It's hard to come up with :botright copen to prevent the quickfix list from appearing in the bottom right of the right split window.

It's actually explained under :help :botright. There's also :h :topleft in case you want to open a window at the top.

@Konfekt
Copy link

Konfekt commented Sep 29, 2024

I found the multiple lines on trying to open a non-existent location list tedious and condensed them to one by

        try
            execute a:loclist ? 'lopen 1' : 'botright copen 1'
        catch /E776/
            echomsg v:exception
        endtry

@bfrg
Copy link
Author

bfrg commented Sep 29, 2024

Nice. You could also add v:throwpoint to your echomsg, then you know where the exception was thrown in one line. This gives you something like:

function qf#window#Toggle, line 16: Vim(lopen):E776: No location list

@bfrg
Copy link
Author

bfrg commented Sep 29, 2024

I just noticed that Vim only throws an error when there's no location list (E776), but when there's no quickfix list, it will just open an empty quickfix window after :copen.

@Konfekt
Copy link

Konfekt commented Sep 29, 2024

Yes, I don't understand the logic; do you think a simple message would be more appropriate as well:

            if a:loclist
                lopen 1
            elseif empty(getqflist())
                echomsg 'Quickfix list is empty'
            else
                botright copen 1
            endif

How about updating the gist as a whole (including dispensing with the s:windos)?

@bfrg
Copy link
Author

bfrg commented Oct 2, 2024

I don't know if this is a bug or desired behavior. It looks inconsistent to me. Anyway, I have removed all the redundant s:windo() invocations in the gist.

And one more note, using getqflist({'size': 1}).size == 0 is more efficient than empty(getqflist()), since in the latter case the entire list is returned.

@Konfekt
Copy link

Konfekt commented Oct 3, 2024

It looks inconsistent to me.

Yes, it is.

using getqflist({'size': 1}).size == 0 is more efficient than empty(getqflist())

I did not know, I rarely looked at the source code. Since empty() requests little information, I hoped that Vim wouldn't bother returning redundant information. Do you have a pointer?

@Konfekt
Copy link

Konfekt commented Oct 3, 2024

Good to know!

@bfrg
Copy link
Author

bfrg commented Oct 3, 2024

When you call getqflist(), Vim needs to iterate through the entire list and return a copy of it. This takes a lot more time than just returning a dict containing one element as with getqflist({'size': 1}). You can benchmark it with a larger list.

In the source code take a look at get_errorlist() (called for getqflist()) and qf_get_properties() (called for getqflist({'size': 1})).

@Konfekt
Copy link

Konfekt commented Oct 3, 2024

Interesting. Surely size is there for a reason, maybe this reason. I wonder about how common it is in other framewords or programming languages to generally supply a size attribute. Here the Vimscript compiler would still have to be smart about getqflist() only being called to read size though.

@bfrg
Copy link
Author

bfrg commented Oct 3, 2024

get_qf_loc_list() is the C function that is invoked when getqflist() is called. As you can see it checks if any arguments have been passed and if so, either of the above functions are invoked, one will return an entire list (which needs to be generated), whereas the other one just returns the properties that have been passed to getqflist(). There's really no magic going on here.

@Konfekt
Copy link

Konfekt commented Oct 3, 2024

Not at all. I wonder how much one can count on such magic in other languages such as Lua, Python, Go, ...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment