0

Is there a way, built-in or with a plugin, that I can swap any number of two different patterns on an arbitrary range using a regex?

Maybe something :s-style like :{range}exchange/{pattern-1}/{pattern-2}/{flags}?

Example:

foo bar
bar foo bar

hypothetical command swaps foo and bar

bar foo
foo bar foo
0
3

This is a duplicate of an old question I cannot found anymore but for which I've a trace (through a solution): https://github.com/LucHermitte/lh-misc/blob/master/plugin/rotate_substitute.vim

:command! -bang -nargs=1 -range CycleSubstitute <line1>,<line2>call s:CycleSubstitute("<bang>", <f-args>)


" no back ref supported; makes no sense
function! s:CycleSubstitute(bang, repl_arg) range
  let do_loop = a:bang != "!"
  let sep = a:repl_arg[0]
  let fields = split(a:repl_arg, sep)
  let cleansed_fields = map(copy(fields), 'substitute(v:val, "\\\\[<>]", "", "g")')
  " build the action to execute
  let action = '\=s:DoCycleSubst('.do_loop.',' . string(cleansed_fields) . ', "^".submatch(0)."$")'
  " prepare the :substitute command
  let args = [join(fields, '\|'), action ]
  let cmd = a:firstline . ',' . a:lastline . 's'
        \. sep . join(fields, '\|')
        \. sep . action
        \. sep . 'g'
  " echom cmd
  " and run it
  exe cmd
endfunction

function! s:DoCycleSubst(do_loop, fields, what)
  let idx = (match(a:fields, a:what) + 1) % len(a:fields)
  return a:fields[idx]
endfunction

Which could be used with: %CycleSubstitute/foo/bar in your example.

2
  • Thanks for posting this! It's going in my plugins (with license of course). A little followup because I can't read vimscript very well: what do RotateSubstitute, Translate, and Do____ do in that script?
    – jrpear
    Oct 13 '20 at 4:13
  • IIRC, :RotateSubstitute replaces text matching the pattern with the first replacement first, then within the second, then with the third, and so on. :Translate is just like tr(anslate) unix command on range of lines. Your should see the Q/A's they answer in the first links at the top of the file. s:Doxxx are support functions Oct 13 '20 at 8:03
1

Great though it would be, there is no exchange command in Vim and the usual approach here is to perform A-->B, C-->A, B-->C instead of the direct but impossible A<-->C. Of course, one needs to guarantee that the intermediate B value is not present in the file. So use something weird enough for B, for example, @#%:

:%s/foo/@#%/g|%s/bar/foo/g|%s/@#%/bar/g

If you don't want to take the risk of @#% being present in the file, adapt this use of Sed to:

:%!sed 's/foo/\n/g;s/bar/foo/g;s/\n/bar/g'

As described in the linked answer, Sed's pattern space is guaranteed to have no newline characters, so this last command is certain to succeed.

0

Use capture groups.

:s/\v(pattern1)(.*)(pattern2)/\3\2\1/

You need to capture the text in between to reference it during substitution.

2
  • 2
    That only works if the patterns are in a same line and in the order specified. I'm afraid OP asks for swapping all X with Y and all Y with X irrespective of order or line.
    – Quasímodo
    Oct 11 '20 at 13:15
  • @Quasímodo i think OP clarified after Spidey posted the answer, which was otherwise correct
    – D. Ben Knoble
    Oct 12 '20 at 12:38

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.