Create a mapping for Vim's command-line that e

2020-07-09 08:28发布

问题:

Suppose that I have a document like this, and I want to search for all occurences of the URL:

Vim resources: [http://example.com/search?q=vim][q]
...
[q]: http://example.com/search?q=vim

I don't want to type it out in full, so I'll place my cursor on the first URL, and run "uyi[ to yank it into the 'u' register. Now to search for it, I'd like to just paste the contents of that register into the search field by running:

/\V<c-r>u<CR> 

This results in Vim searching for the string 'http:' - because the '/' character terminates the search field.

I can get around the problem by running this instead:

/\V<c-r>=escape(@u, '\/')<CR><CR>

But it's a lot of typing!

How can I create a mapping for Vim's commandline that simplifies this workflow?

My ideal workflow would go something like this:

  • press /\V to bring up the search prompt, and use very nomagic mode
  • hit ctrl-x to trigger the custom mapping (ctrl-x is available)
  • Vim listens for the next key press... (pressing <Esc> would cancel)
  • pressing 'u' would escape the contents of the 'u' register, and insert on the command line

回答1:

Try this:

cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
function! s:PasteEscaped()
  " show some kind of feedback
  echo ":".getcmdline()."..."

  " get a character from the user
  let char = getchar()

  if char == "\<esc>"
    return ''
  else
    let register_content = getreg(nr2char(char))
    return escape(register_content, '\/')
  endif
endfunction

By the way, something that might be useful to know (if you don't already) is that you can use ? as the delimiter for :s. Which means that you could write a search-and-replace for an url like so:

:s?http://foo.com?http://bar.com?g


回答2:

I've accepted Andrew Radev's solution, which solved the hard parts. But here's the version that I've added to my vimrc file, which adds a couple of enhancements:

cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
function! s:PasteEscaped()
  echo "\\".getcmdline()."\""
  let char = getchar()
  if char == "\<esc>"
    return ''
  else
    let register_content = getreg(nr2char(char))
    let escaped_register = escape(register_content, '\'.getcmdtype())
    return substitute(escaped_register, '\n', '\\n', 'g')
  endif
endfunction

This should work:

  • whether you use / or ? (to search forwards, or backwards)
  • and when the pasted register includes multiple lines

Also, I changed the prompt. While waiting for a register, the prompt switches to \ - which seems like a suitable cue for 'PasteEscaped'. Also, I've appended a ", which mimics Vim's behavior after pressing <c-r> at the command line.

If you've any further suggestions for improvements, please leave a comment.



回答3:

How about different workflow? For example, creating your own operator to search target text as is:

" https://gist.github.com/1213642
" Requiement: https://github.com/kana/vim-operator-user
map YourFavoriteKeySequence  <Plug>(operator-search-target-text)
call operator#user#define('search-target-text', 'OperatorSerachTargetText')
function! OperatorSerachTargetText(motion_wise)
  execute 'normal!' '`['.operator#user#visual_command_from_wise_name(a:motion_wise).'`]"xy'
  let @/ = '\V' . escape(substitute(@x, '[\r\n]$', '', ''), '\')
  normal! n
endfunction


回答4:

I like @nelstrom's solution and made a small change to support escaping [ and ].

 cnoremap <c-x> <c-r>=<SID>PasteEscaped()<cr>
 function! s:PasteEscaped()
   echo "\\".getcmdline()."\""
   let char = getchar()
   if char == "\<esc>"
     return ''
   else
     let register_content = getreg(nr2char(char))
     let escaped_register = escape(register_content, '\'.getcmdtype())
     let escaped_register2 = substitute(escaped_register,'[','\\[','g')
     let escaped_register3 = substitute(escaped_register2,']','\\]','g')
     return substitute(escaped_register3, '\n', '\\n', 'g')
   endif
 endfunction


标签: vim escaping