Skip to content

Add the :cbefore and :cafter quickfix commands#4340

Closed
yegappan wants to merge 1 commit intovim:masterfrom
yegappan:cbefore
Closed

Add the :cbefore and :cafter quickfix commands#4340
yegappan wants to merge 1 commit intovim:masterfrom
yegappan:cbefore

Conversation

@yegappan
Copy link
Member

@yegappan yegappan commented May 5, 2019

Add the :cbefore and :cafter commands to jump to the quickfix entries
before and after the current cursor location in the current buffer.

@codecov-io
Copy link

Codecov Report

Merging #4340 into master will increase coverage by 0.03%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #4340      +/-   ##
==========================================
+ Coverage    80.1%   80.13%   +0.03%     
==========================================
  Files         108      108              
  Lines      142192   142226      +34     
==========================================
+ Hits       113906   113976      +70     
+ Misses      28286    28250      -36
Impacted Files Coverage Δ
src/quickfix.c 93.56% <100%> (+0.06%) ⬆️
src/os_unix.c 60.02% <0%> (-0.05%) ⬇️
src/terminal.c 80.62% <0%> (+0.03%) ⬆️
src/ui.c 55.07% <0%> (+0.07%) ⬆️
src/window.c 86.7% <0%> (+0.09%) ⬆️
src/screen.c 81.52% <0%> (+0.1%) ⬆️
src/ex_cmds2.c 87.31% <0%> (+0.11%) ⬆️
src/syntax.c 79.74% <0%> (+0.12%) ⬆️
src/sign.c 93.51% <0%> (+0.12%) ⬆️
src/netbeans.c 27.44% <0%> (+0.22%) ⬆️
... and 2 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 9dfa313...da0208f. Read the comment docs.

@brammool brammool closed this in cf6a55c May 5, 2019
@lacygoill
Copy link

lacygoill commented Feb 9, 2020

Right now, :cafter fails if the cursor is right on the last entry in a buffer (E553 is raised). Would it be possible to make :0cafter and :0cbefore succeed in that case? That is, they would accept an entry at the current cursor position when prefixed with a 0 count.

@vim-ml
Copy link

vim-ml commented Feb 9, 2020 via email

@lacygoill
Copy link

Hi, and thank you for commenting.

What happens if the cursor is not on a line with a quickfix entry? In that
case does this behave like :cafter or :1cafter or the cursor doesn't move?

:0cafter would behave just like :cafter or :1cafter, with one exception: it would accept an entry at the cursor position. A 0 count would be to :[cl]after and :[cl]before what the c flag is for the search() function.

So, if the cursor is not on a line with a quickfix entry:

  • :0cafter would fail if there is no entry in the buffer
  • :0cafter would make the cursor move to the next entry in the buffer when one exists

When :0cafter is executed while an entry is right under the cursor, the latter would not move, but the current entry in the quickfix list (as reported by :echo getqflist({'idx':0}).idx) would still be reset to the entry under the cursor.

How are you planning to use this?

I've always wanted a command which would move to the next entry in the quickfix list, taking into account the current cursor position. That is, moving to the next entry relative to the current cursor position if one exists in the current buffer, otherwise to the next entry in the quickfix list.

The closest builtin command is :cafter, but it doesn't work if there is no match after the cursor (E553 is raised). In that case, I need :cnext:

try | cafter | catch | cnext | endtry

But it doesn't always work as I would expect when the cursor is after the last entry in the buffer. Consider this experiment:

$ echo "entry1\nentry2" >/tmp/file1 ; \
  echo "entry3" >/tmp/file2 ; \
  echo "entry4" >/tmp/file3 ; \
  vim -Nu NONE /tmp/file{1..3} +'sil vim /entry/gj ##' +'cc 1|sil next'

If I execute :try | cafter | catch | cnext | endtry, I jump to the second entry in the first file, while I would want to jump to the fourth entry in the third file; because – from my point of view – entry4 is the next entry, not entry2; I pay much more attention to my cursor location than to the index of the current entry in the quickfix list (I often forget what it is).

One solution is to run :cbefore|cafter before :cnext

try
    cafter
catch
    sil! cbefore
    sil! cafter
    cnext
endtry

The purpose of :cbefore|cafter is to reset the current entry to the last entry in the buffer, so that when :cnext is run, the next entry is chosen relative to the latter (last entry in buffer) and not relative to whatever entry the current index referred to originally.

This works as I would expect most of the time:

  • when there is no entry in the buffer, both :cbefore and :cafter fail, and only :cnext is run

  • when there is 1 entry in the buffer, and the cursor is before it, :cbefore fails, but :cafter succeeds; the current entry is reset to the last entry in the buffer, before :cnext is run

  • when there is 1 entry in the buffer, and the cursor is after it, :cbefore succeeds, but :cafter fails; the current entry is reset to the last entry in the buffer, before :cnext is run

  • when there are more than 2 entries in the buffer, both :cbefore and :cafter succeed; the current entry is reset to the last entry in the buffer, before :cnext is run

But there is one exception where something unexpected happen:

  • when there is 1 entry in the buffer, and the cursor is on it, both :cbefore and :cafter fail; the current entry is not reset to the last entry in the buffer, before :cnext is run

You can check this with the previous experiment:

$ echo "entry1\nentry2" >/tmp/file1 ; \
  echo "entry3" >/tmp/file2 ; \
  echo "entry4" >/tmp/file3 ; \
  vim -Nu NONE /tmp/file{1..3} +'sil vim /entry/gj ##' +'cc 1|sil next'

If you run this command:

:try|cafter|catch|sil! cbefore|sil! cafter|cnext|endtry

You still jump to entry2, while I want entry4.

I'm working on a workaround. It can be boiled down to this:

nno ]q :call Cnext()<cr>

fu Cnext()
    let idx = Only_one_entry_in_current_buffer()
    if idx
        exe 'cc '..idx
        cnext
        return
    endif
    try
        cafter
    catch
        sil! cbefore
        sil! cafter
        cnext
    endtry
endfu

fu Only_one_entry_in_current_buffer()
    let curbuf = expand('%:p')
    let orig_qfl = getqflist() | let qfl = deepcopy(orig_qfl)
    call filter(qfl, 'fnamemodify(bufname(getqflist()[v:key].bufnr), "%:p") is# curbuf')
    if len(qfl) == 1
        return index(orig_qfl, qfl[0]) + 1
    else
        return 0
    endif
endfu

It works, but it makes the code much more complex, and it's slow. I can notice the slowness, because I can repeat some of my custom mappings by pressing ;; when I keep pressing ; to jump near an entry far away from the current one, the quickfix window is not updated which makes it impossible to be used very fast.

To fix this, I need a timer-based guard to prevent the function from being re-invoked uselessly too many times in a short period of time:

" written at the start of `Only_one_entry_in_current_buffer()`
if exists('s:was_invoked_recently') | return | endif
let s:was_invoked_recently = timer_start(5, {-> execute('unlet! s:was_invoked_recently')})

If :0cafter did not fail when an entry is right under the cursor, it would not make the cursor move, but it would still reset the current entry to the one under the cursor, which is what I need. I think I could then rewrite my code like this:

nno ]q :call Cnext()<cr>

fu Cnext()
    try
        cafter
    catch
        sil! 0cbefore
        sil! 0cafter
        cnext
    endtry
endfu

This is much simpler, and probably more reliable.


I've also considered the possibility of moving the cursor 1 character backward before running :cafter, so that the latter succeeds; then I could run :cnext. But the entry could be positioned at the very start of the buffer; in which case, there is no earlier position I could move to.
I would need some special code to detect the motion has failed, or will fail.
Then I would need to move 1 character forward before running :cbefore.
But what if there is no later position either (only 1 character in the buffer)?

Besides, moving the cursor may change the view, which I guess (?) is not an issue if :cnext succeeds, but what if it fails? I would need to make sure the view and the cursor position are restored.
There are too many questions, too many code paths, and too many possible pitfalls.

janlazo added a commit to janlazo/neovim that referenced this pull request Jan 1, 2021
Problem:    Cannot navigate to errors before/after the cursor.
Solution:   Add the :cbefore and :cafter commands. (Yegappan Lakshmanan,
            closes vim/vim#4340)
vim/vim@cf6a55c
RRethy pushed a commit to RRethy/neovim that referenced this pull request Jan 6, 2021
Problem:    Cannot navigate to errors before/after the cursor.
Solution:   Add the :cbefore and :cafter commands. (Yegappan Lakshmanan,
            closes vim/vim#4340)
vim/vim@cf6a55c
mikesart pushed a commit to mikesart/neovim that referenced this pull request Mar 10, 2021
Problem:    Cannot navigate to errors before/after the cursor.
Solution:   Add the :cbefore and :cafter commands. (Yegappan Lakshmanan,
            closes vim/vim#4340)
vim/vim@cf6a55c
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants