Как проще всего поменять местами вхождения двух строк в Vim?

Каков самый простой способ заменить все вхождения string_a на string_b, в то же время изменив все, что уже было string_b, на string_a? Мой текущий метод выглядит следующим образом:

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

Хотя это работает, это требует дополнительного набора текста и кажется неэффективным. Кто-нибудь знает лучший способ сделать это?


person btw    schedule 26.08.2010    source источник
comment
Вы можете (также) захотеть задать этот вопрос на SuperUser.com.   -  person janmoesen    schedule 26.08.2010
comment
Это отличный вопрос — кто-то может подумать, что это легко… Я думаю, можно было бы написать функцию, которая принимает два параметра и выполняет три шага за вас, но я также ожидал найти такую ​​функцию с помощью быстрого веб-поиска.   -  person Jay    schedule 26.08.2010
comment
Этот подход потерпит неудачу, если ваш файл уже где-то содержит string_c. Это нормально, если вы человек и можете выбрать слово, которого, как вы знаете, нет в вашем файле, но было бы сложнее научить функцию угадывать хорошее промежуточное слово. Лучше сделать это за один проход.   -  person Brian Carper    schedule 27.08.2010
comment
@BrianCarper Я думаю, что это может привести к сбою, даже если string_c не появляется в тексте, например. если текст представляет собой алфавит abcde..., string_a — это bcd, а string_c — это aa.   -  person potrzebie    schedule 21.12.2012
comment
Как ответил Питер Ринкер на другой вопрос, Abolish теперь прекрасно справляется с этой задачей!   -  person Ry-♦    schedule 09.07.2013


Ответы (5)


Я бы сделал это так:

:%s/\v(foo|bar)/\={'foo':'bar','bar':'foo'}[submatch(0)]/g

Но это слишком много печатать, поэтому я бы сделал так:

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

Но это по-прежнему требует ввода foo и bar дважды, поэтому я бы сделал что-то вроде этого:

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

Если одно из ваших слов содержит /, вы должны передать разделитель, который, как вы знаете, не содержит ни одно из ваших слов, например.

:call SwapWords({'foo/bar':'foo/baz'}, '@')

Это также имеет то преимущество, что можно поменять местами несколько пар слов одновременно.

:call SwapWords({'foo':'bar', 'baz':'quux'})
person Brian Carper    schedule 26.08.2010
comment
Это круто! Моя единственная проблема заключается в том, что он меняет местами вхождения везде. Есть ли способ передать строки, в которых вы хотите произвести замену, подобно тому, как :17,34s/foo/bar/g будет заменять только строки с 17 по 34? - person dsg; 27.12.2010
comment
@dsg Можно сделать, внеся два изменения в SwapWords(). Поместите ключевое слово range после ...) в первой строке, затем замените '%s' ближе к концу на a:firstline . ',' . a:lastline . 's'. Тогда он займет диапазон точно так же, как :s. - person 00dani; 02.08.2013

Вы можете легко сделать это с помощью плагина Abolish Тима Поупа.

:%S/{transmit,receive}/{receive,transmit}
person Peter Rincker    schedule 10.07.2013
comment
%S /{‹Test›,‹Foo›}/{‹Foo›,‹Test›}/g Когда я пытаюсь поменять местами целые слова, это не работает. Что я должен делать? - person if __name__ is None; 07.08.2013
comment
@JanNetherdrake Если вы хотите использовать целые слова, используйте флаг Subvert w. :%S/{test,foo}/{foo,test}/gw. См. :h abolish-search для получения дополнительной информации. - person Peter Rincker; 07.08.2013

Вот как я меняю местами два слова skip и limit:

%s/skip/xxxxx/g | %s/limit/skip/g | %s/xxxxx/limit/g

Почти уверен, что кто-то мог бы превратить его в более короткую команду, которая принимает два аргумента.

person ptgamr    schedule 19.06.2016

плагин swapstrings предоставляет для этого удобную команду:

:SwapStrings string_a string_b
person Ingo Karkat    schedule 23.12.2013

Вы можете сделать это с помощью одной команды, как показано в моем коде ниже:

:%s/\<\(string_a\|string_b\)\>/\=strpart("string_bstring_a", 8 * ("string_b" == submatch(0)), 8)/g
person Sebi    schedule 26.08.2010