views:

110

answers:

3

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_b into string_a? My current method is as follows:

:s/string_a/string_c/g  
:s/string_b/string_a/g  
:s/string_c/string_b/g  

Although this works, it requires extra typing and seems inefficient. Does anybody know of a better way to do this?

A: 

That's all you can do inside vim.

spbfox
@spbfox: I don't understand your answer.
Sebi
Sorry for not being clear enough. The way you do the swap is the only way you can do it inside vim. Using Unix command line tools (sed, awk etc.) you could possibly invent something more sophisticated.
spbfox
Hhhm, check the command in my answer. Of course it is very similar to the one by the poster, but at least it puts everything in one line :-) I could also imagine to record a macro to do it.
Sebi
I already got it. My bad and shame on me ;-). One more time I got the message: "There is no limit for perfection".
spbfox
+1  A: 

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

Update: the editor here in stackoverflow is driving me crazy, because it keeps removing backslahes...

Sebi
Why can't the native SO editor be vim? jeeze.
GWW
@GWW: Indeed :-)
Sebi
@GWW: Because then every other post on Meta Stack Overflow would be "Why not Emacs?", and Meta would become useless. We'd have to close Stack Overflow as "Subjective and Argumentative". If only Emacs users would see the light.
David Thornley
If only they would :(
GWW
+6  A: 

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'})
Brian Carper