1

In this clip,

enter image description here

  • From visual mode here after selecting im.g.mat I trigger my vnoremap <m-/> y:%s/<c-r>0//g<left><left> bind.
  • The purpose of this bind is to initiate a global file replacement search and replace via :%s///g. The <left><left> in the map leaves the cursor in the spot to begin typing the replacement.
  • It really works and neovim here is a baller and displays the result in realtime as I type. I type replacement is cool. This works because a . in a regex happens to match .. Then I hit Esc to cancel.
  • I go ahead and try it again but this time on a section of this above vnoremap definition, with predictable failure due to the robustness issue hinted at in this page: https://vim.fandom.com/wiki/Search_for_visually_selected_text

I have an old crufty vimL config that has something built off of this. I would like to replicate it again today with my new lua config but I'm not sure where to start. I'd really like to avoid all of the insane stuff that is employed in this approach. I would be down for coding this up in lua if it can be done in a straightforward way.

Mostly looking for any clever ideas here. Because actually a regex replacement is too powerful for the typical usecase. Since it's created this problem of having to do this complex transform on the input string to remove special regex chars from it, and if we could convince neovim to do the same functionality but only for plain text replacement I'd be off to the races.

3
  • I stumbled on this answer which seems to maybe have something much more readable i can use stackoverflow.com/a/6171215/340947 will post answer if I get this to a satisfactory state.
    – Steven Lu
    Mar 4, 2023 at 4:30
  • You could probably use \V and only need to escape() the slashes for delimiting the pattern
    – D. Ben Knoble
    Mar 4, 2023 at 13:56
  • Yeah I think the actual one I want is \V but just not either of these is good enough for now.
    – Steven Lu
    Mar 8, 2023 at 19:44

2 Answers 2

2

I have had this function for years in my config:

" return a representation of the selected text
" suitable for use as a search pattern
function! GetSelection(escape)
    let old_reg = getreg("v")
    normal! gv"vy
    let raw_search = getreg("v")
    call setreg("v", old_reg)
    if a:escape
        return substitute(escape(raw_search, '\/.*$^~[]'), "\n", '\\n', "g")
    endif
    return raw_search
endfunction

where I…

  1. back up register v,
  2. reselect the last selection and yank it to register v,
  3. grab the content of register v,
  4. restore register v to its previous value,
  5. try to escape what can/should be escaped if explicitly asked to and return it,
  6. or return the raw text.

The interesting part is step 5, where the raw text is passed through :help escape() so that characters that have a special meaning in regular expressions get treated literally, and then through a substitution that deals with NLs.

Here is one of the ways I use it, which is quite close to what you are trying to do:

" substiture selected text in current paragraph
xnoremap <Space><Space> :<C-u>'{,'}s/<C-r>=GetSelection(1)<CR>/
" substitute selected text in whole buffer
xnoremap <Space>%       :<C-u>%s/<C-r>=GetSelection(1)<CR>/

subst

0

Yes, as I noted in my comment I achieved good results with something based off of https://stackoverflow.com/a/6171215/340947

My solution right now:

  "" From https://stackoverflow.com/a/6171215/340947
  " Escape special characters in a string for exact matching.
  " This is useful to copying strings from the file to the search tool
  " Based on this - http://peterodding.com/code/vim/profile/autoload/xolox/escape.vim
  function! EscapeString (string)
    let string=a:string
    " Escape regex characters
    let string = escape(string, '^$.*\/~[]')
    " Escape the line endings
    let string = substitute(string, '\n', '\\n', 'g')
    return string
  endfunction

  " Get the current visual block for search and replaces
  " This function passed the visual block through a string escape function
  " Based on this - https://stackoverflow.com/questions/676600/vim-replace-selected-text/677918#677918
  function! GetVisual() range
    " Save the current register and clipboard
    let reg_save = getreg('"')
    let regtype_save = getregtype('"')
    let cb_save = &clipboard
    set clipboard&

    " Put the current visual selection in the " register
    normal! ""gvy
    let selection = getreg('"')

    " Put the saved registers and clipboards back
    call setreg('"', reg_save, regtype_save)
    let &clipboard = cb_save

    "Escape any special characters in the selection
    let escaped_selection = EscapeString(selection)

    return escaped_selection
  endfunction

  " Ctrl+R works well to mean "replace"
  vnoremap <c-r> <Esc>:%s/<c-r>=GetVisual()<cr>//g<left><left>

It remains to be seen how often I'll use this visual-initialized global replacement bind i've set up. It's going to be in an environment where it'll be competing with:

  • (smart) LSP based symbol replacement features
  • (works everywhere, scope beyond single buffer) A gnarly symbol replacement shell script which provides an interactive interface and applies a text replacement filtered over git ls-files

But it's very nice to have this kind of vim way that shows multi-replacement taking place in realtime as you type the replacement. I'm tempted to go further with this sometime to make the bind also write to a dotfile from which I can run another shell program that will go apply the same change across a repo afterward, integrating this with my second bullet there.

1
  • You're saying that i haven't provided enough detail to make it a satisfactory answer? i guess that's kind of fair.
    – Steven Lu
    Mar 20, 2023 at 6:37

Your Answer

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

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