Is there any built-in way to jump to the next or previous error in the quickfix/location list relative to the current cursor position?

Say my quickfix list looks like:

---quickfix list---
a.txt - line 1
a.txt - line 2
b.txt - line 6
b.txt - line 7

I am currently in b.txt on line 1. If I try :cnext Vim will jump to a.txt on line 1.

Instead I would prefer to jump to b.txt on line 6 (and whatever column is specified in the qf list)

b.txt - 1 => b.txt - 6
  • Note, Vim patch 8.1.1256 and patch 8.1.1275 added the :cbefore, :cabove, cbelow and cafter commands (together with the respecting locationlist commands l...). See also the now (deleted answer). May 6 '19 at 11:39

There is no built in way to do file specific :cnext. You would have to roll your own function/command to do such a task.

Here is my interpretation of :Cfnext:

command! -bar -count=1 Cfnext exe 'cc'.(get(get(filter(map(getqflist(), 'extend(v:val, {"idx": v:key})'), 'bufname(v:val.bufnr) == expand("%") && line(".") <= v:val.lnum && col(".") < v:val.col'), 0, {}), 'idx', getqflist({'idx':1}).idx-1)+<count>)

Here is a cleaned up and more complete version:

command! -bar -count=1 Cfnext execute <SID>cfnext(<count>, 'qf')
command! -bar -count=1 Cfprev execute <SID>cfnext(<count>, 'qf', 1)
command! -bar -count=1 Lfnext execute <SID>cfnext(<count>, 'loc')
command! -bar -count=1 Lfprev execute <SID>cfnext(<count>, 'loc', 1)

function! s:cfnext(count, list, ...) abort
  let reverse = a:0 && a:1 
  let func = 'get' . a:list . 'list'
  let params = a:list == 'loc' ? [0] : []
  let cmd = a:list == 'loc' ? 'll' : 'cc'

  let items = call(func, params)
  if len(items) == 0
    return 'echoerr ' . string('E42: No Errors')

  call map(items, 'extend(v:val, {"idx": v:key + 1})')
  if reverse
    call reverse(items)

  let [bufnr, cmp] = [bufnr('%'), reverse ? 1 : -1]
  let context = [line('.'), col('.')]
  if v:version > 800 || has('patch-8.0.1112')
    let current = call(func, extend(copy(params), [{'idx':1}])).idx
    redir => capture | execute cmd | redir END
    let current = str2nr(matchstr(capture, '(\zs\d\+\ze of \d\+)'))
  call add(context, current)

  call filter(items, 'v:val.bufnr == bufnr && s:cmp(context, [v:val.lnum, v:val.col, v:val.idx]) == cmp')

  let idx = get(get(items, 0, {}), 'idx', 'E553: No more items')

  if type(idx) == type(0)
    return cmd . idx
     return 'echoerr' . string(idx)

function! s:cmp(a, b)
  for i in range(len(a:a))
    if a:a[i] < a:b[i]
      return -1
    elseif a:a[i] > a:b[i]
      return 1
  return 0

For more help see:

:h getqflist()
:h filter()
:h index()
:h :cc
:h bufname()
:h expand()
:h map()
:h extend()
  • 1
    You could simplify very slightly with let qf = map(qf => call map(qf and same with filter(.
    – Mass
    Nov 28 '17 at 20:28
  • 1
    It looks like the ALE plugin always set the column number to 1 (or at least, some ALE checkers do). So if it's not working, you may to consider removing that part. You can also replace <= with < to jump "over" the current item (instead of activating it again). Also, I think bufname(v:val.bufnr) == fname is the same as v:val.bufnr == bufnr('')? Nov 29 '17 at 1:16
  • 1
    Here is my version btw, it adds better errors and works for both the quickfix and location list. Nov 29 '17 at 1:42
  • 1
    @Mass & @Carpetsmoker, I have cleaned up code to address the issues you both pointed out. @Carpetsmoker, I think you want to be column aware. Otherwise you can not use this with :vimgrep/pat/g % when you have multiple matches on the same line. Nov 29 '17 at 17:43
  • 1
    Hm, I just tried again and I have various issues if I add the col('.') check (skipping entries, not going back to the first). I don't know if this is a problem in my code or ALE, I need to investigate in more depth later. For me personally it's not an issue as I rarely have cases with two or more items on the same line (I never use e.g. :vimgrep). Nov 30 '17 at 3:50

The patch 8.1.1256 has just added the commands :cabove, :cbelow, :labove and :lbelow, which, I think, do what you want.

As usual, the commands prefixed by c operate on a quickfix list, and the ones prefixed by l operate on a location list. And here, above makes the cursor jump to the previous entry, while below makes it jump to the next one.

The commands accept a count, so you can skip some entries when you jump. For example, :3cbelow should make you jump to the third entry after your current position.

There are some caveats though.

The entries in the quickfix list must be sorted by their buffer number and line number. I think Vim's default commands – which populate a quickfix list – do that by default, like :vimgrep and :helpgrep; but it may not be the case with a quickfix list populated by a custom command (provided by a third-party plugin for example).

Also, if several entries are on the same line, it seems that only the first one is considered.

Relevant help:

                                                        *:cabo* *:cabove*
:[count]cabo[ve]        Go to the [count] error above the current line in the
                        current buffer.  If [count] is omitted, then 1 is
                        used.  If there are no errors, then an error message
                        is displayed.  Assumes that the entries in a quickfix
                        list are sorted by their buffer number and line
                        number. If there are multiple errors on the same line,
                        then only the first entry is used.  If [count] exceeds
                        the number of entries above the current line, then the
                        first error in the file is selected.

                                                        *:lab* *:labove*
:[count]lab[ove]        Same as ":cabove", except the location list for the
                        current window is used instead of the quickfix list.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.