What is the easiest way to replace all occurrences of string_a
with string_b
while at the same time changing anything that was already string
The swapstrings plugin provides a handy command for this:
:SwapStrings string_a string_b
You can do this easily with Tim Pope's Abolish plugin
:%S/{transmit,receive}/{receive,transmit}
You can do it with a single command as shown in my code below:
:%s/\<\(string_a\|string_b\)\>/\=strpart("string_bstring_a", 8 * ("string_b" == submatch(0)), 8)/g
I'd do it like this:
:%s/\v(foo|bar)/\={'foo':'bar','bar':'foo'}[submatch(0)]/g
But that's too much typing, so I'd do this:
function! Mirror(dict)
for [key, value] in items(a:dict)
let a:dict[value] = key
endfor
return a:dict
endfunction
function! S(number)
return submatch(a:number)
endfunction
:%s/\v(foo|bar)/\=Mirror({'foo':'bar'})[S(0)]/g
But that still requires typing foo
and bar
twice, so I'd do something like this:
function! SwapWords(dict, ...)
let words = keys(a:dict) + values(a:dict)
let words = map(words, 'escape(v:val, "|")')
if(a:0 == 1)
let delimiter = a:1
else
let delimiter = '/'
endif
let pattern = '\v(' . join(words, '|') . ')'
exe '%s' . delimiter . pattern . delimiter
\ . '\=' . string(Mirror(a:dict)) . '[S(0)]'
\ . delimiter . 'g'
endfunction
:call SwapWords({'foo':'bar'})
If one of your words contains a /
, you have to pass in a delimiter which you know none of your words contains, .e.g
:call SwapWords({'foo/bar':'foo/baz'}, '@')
This also has the benefit of being able to swap multiple pairs of words at once.
:call SwapWords({'foo':'bar', 'baz':'quux'})
Here is how I swap two words skip
& limit
:
%s/skip/xxxxx/g | %s/limit/skip/g | %s/xxxxx/limit/g
Pretty sure someone could turn it into a shorter command which accepts two arguments.