2010-08-26 11 views
34

Quelle est la manière la plus facile de remplacer toutes les occurrences de string_a par string_b tout en changeant en même temps tout ce qui était déjà string_b en string_a? Ma méthode actuelle est la suivante:Le moyen le plus simple d'échanger des occurrences de deux chaînes dans Vim?

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

Bien que cela fonctionne, il nécessite une saisie supplémentaire et semble inefficace. Quelqu'un sait-il d'une meilleure façon de le faire?

+2

Vous pouvez aussi essayer cette question sur SuperUser.com. – janmoesen

+0

C'est une bonne question - on pourrait penser que c'est facile ... J'imagine que l'on pourrait écrire une fonction qui accepte deux paramètres et passe par les trois étapes pour vous, mais je m'attendais aussi à trouver une telle fonction avec une recherche rapide sur le web . – Jay

+1

Cette approche échouerait si votre fichier contenait déjà "string_c". C'est bien si vous êtes un humain et que vous pouvez choisir un mot qui, vous le savez, n'est pas dans votre fichier, mais il serait plus difficile d'enseigner une fonction pour deviner un bon mot intermédiaire. Mieux vaut le faire en un seul passage. –

Répondre

25

Je ferais comme ça:

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

Mais c'est trop taper, donc je ferais ceci:

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 

Mais cela nécessite encore taper foo et bar deux fois, donc je ferais quelque chose comme ceci:

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

Si l'un de vos mots contient une /, vous devez passer dans un séparateur que vous savoir qu'aucun de vos mots contient, .EG

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

Cela a aussi l'avantage de pouvoir échanger plusieurs paires de mots à la fois.

:call SwapWords({'foo':'bar', 'baz':'quux'}) 
+0

C'est génial! Mon seul problème est qu'il échange les occurrences partout. Y a-t-il un moyen de passer les lignes sur lesquelles vous voulez que le swap se produise de la même façon que ': 17,34s/foo/bar/g' ne se substituera qu'aux lignes 17 à 34? – dsg

+1

@dsg Peut être fait en apportant deux modifications à SwapWords(). Mettez le mot-clé 'range' après le' ...) 'sur la première ligne, puis remplacez ''% s'' près de la fin par' a: firstline. ','. a: dernière ligne 's'' Ensuite, cela prendra une plage exactement comme ': s'. – 00dani

1

Vous pouvez le faire avec une seule commande, comme indiqué dans mon code ci-dessous:

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

Mise à jour: l'éditeur ici en stackoverflow me rend fou, car il maintient la suppression backslahes ...

8

Vous pouvez le faire facilement avec le plugin de Tim Pope Abolish

:%S/{transmit,receive}/{receive,transmit} 
+0

% S/{, }/{, }/g Lorsque j'essaie d'échanger des mots entiers, cela ne fonctionne pas. Que devrais-je faire? –

+1

@JanNetherdrake Si vous voulez utiliser des mots entiers, utilisez le drapeau 'w' de Subvert. ':% S/{test, foo}/{foo, test}/gw'. Voir ': h abolish-search' pour plus d'informations. –

2

Le swapstrings plugin fournit une commande à portée de main pour cela:

:SwapStrings string_a string_b 
2

Voici comment j'échange deux mots skip & limit .

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

quelqu'un pourrait vous Jolie transformer en une commande plus courte qui accepte deux arguments.