1

I want to code a vim function, which searches backwards a specific char from the cursor at the current line.

Here is what I've done:

function! CustomSearch()
    let currentLine = getline(".")

    " how to loop backwards `currentLine`?
    " something like this:
    " for (int i = currentLine.size() - 1; i >= 0; i--)
    "     if (currentLine[i] == 'a')
    "         echo 'found a'
    "     endif
    " endfor
endfunction

inoremap <C-v><C-b> :call CustomSearch()<CR>
3

Here's what you're looking for:

function! CustomSearch()
    let currentLine = getline(".")
    let col = col('.')

    while col > 0
        let col -= 1
        if currentLine[col] == 'a'
            echo 'found a!'
            break
        endif
    endwhile
endfunction

inoremap <C-v><C-b> <C-o>:call CustomSearch()<CR>

I'm not sure what you want to do with that, but I feel like it would be much simpler with regex:

inoremap <C-v><C-b> <C-o>:if search('^.*a.*\%#', 'n') <BAR> echo 'Found a!' <BAR> endif<CR>
1
  • 3
    Note, that currentLine[col] does not work with multibyte characters. Apr 22 '20 at 11:48
2

You can use the range() function, which accepts a second and third optional arguments and can take a negative stride of -1 to iterate backwards:

function! CustomSearch()
  let currentLine = getline(".")

  for i in range(len(currentLine) - 1, 0, -1)
    if currentLine[i] ==# 'a'
      echo 'found a'
    endif
  endfor
endfunction

Note that you'll typically want to use ==# for your string comparisons.

Also, as @ChristianBrabandt points out, indexing a string with currentLine[col] will not work with multibyte characters. See strchars() and strcharpart() for functions that would allow you to index characters rather than bytes. But using a regex match is typically the best way to handle multibyte correctly.

UPDATE: Also pointed out by @ChristianBrabandt, using split() with a regex of '\zs' will split a string into individual characters. Using that together with reverse() allows you to iterate over characters directly. Putting it all together:

function! CustomSearch()
  let currentLine = getline(".")

  for c in reverse(split(currentLine, '\zs'))
    if c ==# 'a'
      echo 'found a'
    endif
  endfor
endfunction
2
  • 1
    an alternative is to use split(getline('.'), '\zs') and loop directly over each character Apr 22 '20 at 15:38
  • 1
    Thanks again @ChristianBrabandt! Updated my answer to include a variation with split(..., '\zs').
    – filbranden
    Apr 22 '20 at 17:02
2

I would start from

echo match(reverse(split(getline('.')[:col('.')-2], '\zs')), the_char)

that is likely to be much more efficient than any loop in vimscript.

But, what is the exact objective? There are many more possibilities depending on what you are exactly looking for:

  • a boolean that indicates whether there is one char? (=> no need to reverse)
  • a character position? (=> subtract the previous result from the list length)
  • a cursor position (they may not be the same because of tab and of multi-bytes characters)?
  • a substring? (a matchstr(getline('.')[:col('.')-2], escape(the_char, '.*\^$[').'[^'.the_char.']*$') should do it -- not 100% sure about all the characters that need to be escaped) The performance of this solution should be compared against split|reverse|match.
  • to move the cursor? (=> use search() or :exe "normal! T".thechar/:normal F)

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.