How can I perform a search-and-replace and leave the cursor at the end of the replacement? I want to replace $...$ with \(...\) as I type in insert mode.

Notice that I can't use an abbreviation, since $ is replaced with two different strings.

The solution I've cobbled together so far is equivalent to:

function Foo()
    let pat = '\$\(.\{-}\)\$'
    if line =~ pat
        execute 's/' . pat . '/\\(\1\\)/'
        call cursor('.', matchend(getline('.'), '\\)'))

inoremap $ $<C-O>:call Foo()<CR>

Is there a better way to do this? The only alternative I found is to add the length of '\\)' to the column number from searchpos, and that isn't any nicer.


One way to get this effect is to instead adaptively enter either \( or \) depending on which is needed. So the first $ will type \( and the second will type \). To figure out which we need, we use searchpairpos, which returns [0,0] when outside the \(..\) pair. Besides being cleaner to code, it is also a bit nicer while typing and works over multiple lines.

function! IDollar()
  let [l:l,l:c] = searchpairpos('\\(', '', '\\)', 'cbWn')
  return l:l ? '\)' : '\('

inoremap <silent><expr> $ IDollar()

If you would rather the exact behavior mentioned in your question, here is an improved version of your script. There are a few differences:

  1. use setline instead of s/ because there are fewer side-effects,
  2. use cursor() since we know the cursor always needs to move forward by 2,
  3. use the <c-r>= pattern instead of <c-o> since <c-o> resets the last insert position (useful when backspace does not contain start).

function! Foo()
  let l:pat = '\$\([^\$]\+\)\$'
  let l:line = getline('.')
  if l:line =~ l:pat
    call setline('.', substitute(l:line, l:pat, '\\(\1\\)', ''))
    call cursor(0, col('.')+2)
  return ''
inoremap $ $<c-r>=Foo()<cr>
  • I can't imagine a better answer than this, but I'll wait for a while before I accept it, just in case. – mephistolotl Dec 18 '17 at 5:49
  • Your first solution is perfect, and taught me a lot about the search functions. Your second solution is also very educational. – mephistolotl Dec 20 '17 at 3:38
  • upvote for <c-r>= alone.. thank you! – elbeardmorez Jun 24 '19 at 11:02

You can use surround.vim

let g:surround_{char2nr('R')} = "\\(\r\\)"
imap $ $<esc>cs$R`]a

The idea is to create a custom surrounding, "R", which is \(...\). We use this surrounding to replace $...$. Use the '] mark to restart the insert at the end of the changed text.

For more help see:

:h surround-customizing
:h surround-mappings
:h ']
:h char2nr(
:h curly-braces-names

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.