Search code examples
vimescapingsubstitution

vim: escaping strings for substitution (vimscript)


I currently write a substitute function that I often need for programming in vim.

The functions I already wrote look like this and run basically okay for searching and replacing strings which do not have any special characters inside. I already realized to escape the "/" automatically. My question is, how do I have to adapt the escape() function in the line

execute ':silent :argdo %s/' . escape(searchPattern, '/') . '/' . escape(replacePattern, '/') . '/ge'

So that automatically all of the characters that have to be escaped will be escaped?

" MAIN FUNCTION
" inspired by http://vimcasts.org/episodes/project-wide-find-and-replace/
function! SubstituteProjectwide(searchInput)
  :set hidden
  let cwd = getcwd()
  let filename=expand('%:p')
  call inputsave()
  let searchPattern = input('Search for: ', a:searchInput)
  let replacePattern = input('Replace "' . searchPattern . '" with: ')
  let filePattern = input('Filepattern: ', cwd . '/**/*.*')
  call inputrestore()
  execute ':silent :args ' . filePattern
  execute ':silent :vimgrep /' . searchPattern . '/g ##'
  execute ':silent :Qargs'
  execute ':silent :argdo %s/' . escape(searchPattern, '/\') . '/' . escape(replacePattern, '/\') . '/ge'
  execute ':silent :edit ' . filename
  echo 'Replaced "' . searchPattern . '" with "' . replacePattern . '" in ' . filePattern
endfunction

" VISUAL ENTRYPOINT WITH SELECTED TEXT
function! SubstituteProjectwideVisual()
  let v = @*
  call SubstituteProjectwide(GetVisualSelectedText())
endfunction
:vnoremap <F6> :call SubstituteProjectwideVisual()<cr>

" NORMAL ENTRYPOINT WITH WORD UNDER CURSOR
function! SubstituteProjectwideNormal()
  let wordUnderCursor = expand("<cword>")
  call SubsituteProjectwide(wordUnderCursor)
endfunction
:nnoremap <F6> :call SubstituteProjectwideNormal()<cr>

" GETTING THE FILES W## Heading ##ICH CONTAIN SEARCH PATTERN
" copied from http://vimcasts.org/episodes/project-wide-find-and-replace/
command! -nargs=0 -bar Qargs execute 'args' QuickfixFilenames()
function! QuickfixFilenames()
  let buffer_numbers = {}
  for quickfix_item in getqflist()
    let buffer_numbers[quickfix_item['bufnr']] = bufname(quickfix_item['bufnr'])
  endfor
  return join(map(values(buffer_numbers), 'fnameescape(v:val)'))
endfunction

" GETTING THE CURRENT VISUAL SELECTION
" copied from: https://stackoverflow.com/questions/1533565/how-to-get-visually-selected-text-in-vimscript
function! GetVisualSelectedText()
  let [line_start, column_start] = getpos("'<")[1:2]
  let [line_end, column_end] = getpos("'>")[1:2]
  let lines = getline(line_start, line_end)
  if len(lines) == 0
    return ''
  endif
  let lines[-1] = lines[-1][: column_end - (&selection == 'inclusive' ? 1 : 2)]
  let lines[0] = lines[0][column_start - 1:]
  return join(lines, "\n")
endfunction

UPDATE

I managed to escape many characters like that

escape(searchPattern, ' / \') . '/' . escape(replacePattern, ' / \')

But how do I know which list of characters I have to escape, when it is basically possible, that every character can be inside the search and also the replace string?


Solution

  • To do a literal substitution, specify "very-nomagic" (:help /\V) , and escape the separator (/) and \ in the source.

    In the replacement, & and ~ must be escaped, too, if the 'magic' option is set. (\V doesn't work here.)

    execute ':silent :argdo %s/\V' . escape(searchPattern, '/\') . '/' . escape(replacePattern, '/\' . (&magic ? '&~' : '')) . '/ge'
    

    Line breaks (if possible) must be changed from ^M to \n:

    execute ':silent :argdo %s/\V' . substitute(escape(searchPattern, '/\'),"\n",'\\n','ge') . '/' . escape(replacePattern, '/\' . (&magic ? '&~' : '')) . '/ge'