3

I am trying to write a couple of simple functions to find the next and previous links in Vimwiki and move to them. Links in Vimwiki are indicated with the syntax [[link to something]]. Since I only want to move the cursor and I want to avoid highlighting all matches, I am using the functions searchpos() and setpos() instead of the usual / and ?. searchpos() has the same options as search(), so I imagine they work equivalently.

I have written the following:

function! MoveToNextLink()
    " Get line number and column
    let [lnum, col] = searchpos('\[\[.\{-}\]\]', 'nz')
    " Move cursor
    call setpos('.', [0, lnum, col, 0])
endfunction
function! MoveToPrevLink()
    " Get line number and column
    let [lnum, col] = searchpos('\[\[.\{-}\]\]', 'bnz')
    " Move cursor 
    call setpos('.', [0, lnum, col, 0])
endfunction
nnoremap <silent> <C-l> :call MoveToNextLink()<CR>
nnoremap <silent> <C-h> :call MoveToPrevLink()<CR>

The options for search() are: n, that indicates to not move the cursor; b, to search backwards; and z, for starting the search at the cursor position rather than at the beginning or the end of the line.

For the most part these mappings seem to work. However, I run into problems when there are multiple matches [[link to something]] in the current line:

  • The forward function MoveToNextLink works in its current form. Originally, I did not have the z option and it didn't work because all searches started at the beginning of the line, so only the first match was found, regardless of the position of the cursor in the line. Adding the z option fixed this, as expected.
  • The backward function MoveToPrevLink does not work. Similarly to the behaviour of MoveToNextLink without the z option, it only finds the last match in the current line, suggesting that search() starts looking at the end of the line and keeps searching backwards from there. Adding the z option worked as intended in the forward direction, but did not change anything in the backward direction, which was surprising.

Why is this? Am I doing something wrong, or is there a bug/inconsistency in Vim such that the z option does not work for backward search?

2 Answers 2

1

So I tested your code a bit and I realized that the issue with your backward search comes from the flag z, I don't really have a convincing explanation because reading the doc I would have used it too.

'z' start searching at the cursor column instead of Zero

I changed your pattern to a simple . and realized that <c-h> only matched the last character of each line so I removed the flag z and it started matching every previous characters of the current line.

So if you change the command in MoveToPrevLink to this:

let [lnum, col] = searchpos('\[\[.\{-}\]\]', 'bn')

It looks like it works fine, at least on the following test buffer (i.e <c-h> navigate to all of the [[linkX]] from any position in the buffer). If you have some other cases where it doesn't work you can edit your question to add test cases so that we can have a look.

[[link1]]
sometext
[[link2]] more [[link3]]
sometext
[[link4]]
[[link5]][[link6]]
4
  • Thanks a lot! This fixes it. Based on the description in the documentation, I would call this a bug... Other weird inconsistencies that I have found are: first, the W option to avoid wrapping around file words in backward direction in searchpos(), but not in forward direction; and second, search() doesn't work in a normal mapping, but it has to be invoked within a function (or at least I haven't managed to make it work in a mapping).
    – mgarort
    Feb 18, 2021 at 12:26
  • 1
    The behavior for W seems weird indeed, but it has the same behavior in both vim and neovi maybe it's a matter a documentation I don't know. However about search() in a mapping I don't know what you were trying to do but nnoremap <F1> :call search('.')<CR> works perfectly for example.
    – statox
    Feb 18, 2021 at 12:45
  • Ok that's interesting. If I put the line you suggest, nnoremap <F1> :call search('.')<CR>, at the end of my vimrc, pressing <F1> works as expected. But if I put the following mapping immediately after, nnoremap <C-l> :call search('.')<CR>, pressing <C-l> gives me the error E119: Not enough arguments for function: search. This must be because of my configuration, since a clean Vim without any custom configuration maps <C-l> as expected. I fail to understand how an error like this one can arise. Anyway, thanks a lot for your help!
    – mgarort
    Feb 18, 2021 at 14:47
  • 1
    That's weird indeed. Maybe my guide about How to debug a mapping would be useful... Good luck debugging that :)
    – statox
    Feb 18, 2021 at 15:05
0

I was looking for a way to jump around "pair" characters in normal and insert mode and stumbled across this question, thanks for this! Basically I was looking for a way to do what the AutoPairs plugin does with its Jump shortcut but also backwards, and have more control over which characters it jumps to.I copied your script and modified it. With these bindings now I can jump to the previous/next ( { [ ` ' " ] } ) or < > characters (rather than using f( or ?] or something of the sort). Mapped to <ctrl-h> and <ctrl-j> below. Thanks again for your code!

" Pair Movement:
" Move around pairs backwards
function! MoveToPrevPairs()
 "   Get line number and column
    let backsearch = '(\|)\|\[\|\]\|{\|}\|"\|`\|'''
    " Comment out above line and uncomment line below to include < >
    " let backsearch = '(\|)\|\[\|\]\|{\|}\|"\|`\|''\|<\|>'
    let [lnum, col] = searchpos(backsearch, 'bn')
    " Move cursor
    call setpos('.', [0, lnum, col, 0])
endfunction
" test line: ---(---)---[---]---{---}---`---'---"---<--->---

" nnoremap <silent> <C-l> :call MoveToNextLink()<CR>
nnoremap <silent> <C-h> <ESC>:call MoveToPrevPairs()<CR>
inoremap <silent> <C-h> <ESC>:call MoveToPrevPairs()<CR>a

" Move around pairs forward
function! MoveToNextPairs()
 "   Get line number and column
    let forwardsearch = '(\|)\|\[\|\]\|{\|}\|"\|`\|'''
    " Comment out above line and uncomment line below to include < >
    " let forwardsearch = '(\|)\|\[\|\]\|{\|}\|"\|`\|''\|<\|>'
    let [lnum, col] = searchpos(forwardsearch, 'n')
    " Move cursor
    call setpos('.', [0, lnum, col, 0])
endfunction

" nnoremap <silent> <C-l> :call MoveToNextLink()<CR>
nnoremap <silent> <C-j> <ESC>:call MoveToNextPairs()<CR>
inoremap <silent> <C-j> <ESC>:call MoveToNextPairs()<CR>a
" test line: ---(---)---[---]---{---}---`---'---"---<--->---
1
  • You might be interested in searchpair() and related functions
    – D. Ben Knoble
    Jun 8, 2022 at 18:34

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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