2009-11-27 8 views
5

Je suis un utilisateur de vim, et je veux être en mesure de faire une boucle sur une série de sous-chaînes quand je suis en train de le remplacer. Comment puis-je utiliser un peu de magie vim pour passer d'un ensemble de lignes comme ceci:Comment puis-je substituer à partir d'une liste de chaînes dans VIM?

Afoo 
Bfoo 
Cfoo 
Dfoo 

à

Abar 
Bbar 
Cbaz 
Dbaz 

?

Je veux rechercher mon fichier depuis le début pour la prochaine occurrence de foo, et remplacer les deux premières instances avec bar, et les deux autres avec baz. L'utilisation d'une boucle for est-elle la meilleure option? Si oui, comment puis-je utiliser la variable de boucle dans la commande de substitution?

Répondre

9

J'utiliserais une fonction qui a un état, et j'appellerais cette fonction de% s. Quelque chose comme:

" untested code 
function! InitRotateSubst() 
    let s:rs_idx = 0 
endfunction 

function! RotateSubst(list) 
    let res = a:list[s:rs_idx] 
    let s:rs_idx += 1 
    if s:rs_idx == len(a:list) 
     let s:rs_idx = 0 
    endif 
    return res 
endfunction 

et les utiliser avec:

:call InitRotateSubst() 
:%s/foo/\=RotateSubst(['bar', 'bar', 'baz', 'baz'])/ 

L'appel aux deux commandes pourraient être encapsulées dans une seule commande si vous le souhaitez.


EDIT: Voici une version intégrée comme une commande:

  • accepte autant de remplacements que nous voulons, tous les remplacements doivent être séparés par le caractère de séparation;
  • prend en charge les références arrière;
  • peut remplacer seulement les N premières occurrences, N == le nombre de remplacements spécifiés si l'appel de commande est cogné (avec un!)
  • ne supporte pas les drapeaux habituels comme g, i (:h :s_flags) - pour cela, nous aurions par exemple à imposer l'appel de la commande pour toujours finir avec un/(ou n'importe quel caractère séparateur), sinon le dernier texte est interprété comme des drapeaux.

Voici la définition de commande:

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

function! s:RotateSubstitute(bang, repl_arg) range 
    let do_loop = a:bang != "!" 
    " echom "do_loop=".do_loop." -> ".a:bang 
    " reset internal state 
    let s:rs_idx = 0 
    " obtain the separator character 
    let sep = a:repl_arg[0] 
    " obtain all fields in the initial command 
    let fields = split(a:repl_arg, sep) 

    " prepare all the backreferences 
    let replacements = fields[1:] 
    let max_back_ref = 0 
    for r in replacements 
    let s = substitute(r, '.\{-}\(\\\d\+\)', '\1', 'g') 
    " echo "s->".s 
    let ls = split(s, '\\') 
    for d in ls 
     let br = matchstr(d, '\d\+') 
     " echo '##'.(br+0).'##'.type(0) ." ~~ " . type(br+0) 
     if !empty(br) && (0+br) > max_back_ref 
    let max_back_ref = br 
     endif 
    endfor 
    endfor 
    " echo "max back-ref=".max_back_ref 
    let sm = '' 
    for i in range(0, max_back_ref) 
    let sm .= ','. 'submatch('.i.')' 
    " call add(sm,) 
    endfor 

    " build the action to execute 
    let action = '\=s:DoRotateSubst('.do_loop.',' . string(replacements) . sm .')' 
    " prepare the :substitute command 
    let args = [fields[0], action ] 
    let cmd = a:firstline . ',' . a:lastline . 's' . sep . join(args, sep) 
    " echom cmd 
    " and run it 
    exe cmd 
endfunction 

function! s:DoRotateSubst(do_loop, list, replaced, ...) 
    " echom string(a:000) 
    if ! a:do_loop && s:rs_idx == len(a:list) 
    return a:replaced 
    else 
    let res0 = a:list[s:rs_idx] 
    let s:rs_idx += 1 
    if a:do_loop && s:rs_idx == len(a:list) 
     let s:rs_idx = 0 
    endif 

    let res = '' 
    while strlen(res0) 
     let ml = matchlist(res0, '\(.\{-}\)\(\\\d\+\)\(.*\)') 
     let res .= ml[1] 
     let ref = eval(substitute(ml[2], '\\\(\d\+\)', 'a:\1', '')) 
     let res .= ref 
     let res0 = ml[3] 
    endwhile 

    return res 
    endif 
endfunction 

qui pourrait être utilisé de cette façon:

:%RotateSubstitute#foo#bar#bar#baz#baz# 

ou même, compte tenu du texte initial:

AfooZ 
BfooE 
CfooR 
DfooT 

la commande

%RotateSubstitute/\(.\)foo\(.\)/\2bar\1/\1bar\2/ 

produirait:

ZbarA 
BbarE 
RbarC 
DbarT 
0

Il va probablement être beaucoup plus facile d'enregistrer une macro qui peut remplacer les deux premiers, puis utiliser: s pour le reste.

La macro peut ressembler à /foo^Mcwbar^[. Si vous n'êtes pas familier avec le mode macro, appuyez simplement sur q, a (le registre pour le stocker), puis sur les touches /foo <Enter> cwbar <Escape>.

Maintenant, une fois que vous avez cette macro, faites [email protected] pour remplacer les deux premières occurrences dans le tampon actuel et utilisez :s normalement pour remplacer le reste.

1

Voici comment j'essaierais cette macro.

qa   Records macro in buffer a 
/foo<CR> Search for the next instance of 'foo' 
3s   Change the next three characters 
bar   To the word bar 
<Esc>  Back to command mode. 
n   Get the next instance of foo 
.   Repeat last command 
n   Get the next instance of foo 
3s   Change next three letters 
baz   To the word bar 
<Esc>  Back to command mode. 
.   Repeat last command 
q   Stop recording. 
[email protected]  Do a many times. 

Tout conseil sur la façon de le faire mieux est le bienvenu.

merci, Martin.

+0

Il semblerait que vous manquiez un 'n' avant votre dernier '.' – Rich

2

Ce n'est pas strictement ce que vous voulez, mais peut être utile pour les cycles.

J'ai écrit un swap de plugin http://www.vim.org/scripts/script.php?script_id=2294 qui, entre autres, peut aider à faire défiler des listes de chaînes. Par exemple.

:Swaplist foobar foo bar baz 

puis tapez

This line is a foo 

créer un simple coup sec/ligne de pâte, aller au dernier mot et ctrl-swap.

qqyyp$^A 

puis exécutez le modèle d'échange

[email protected] 

pour obtenir

This line is foo 
This line is bar 
This line is baz 
This line is foo 
This line is bar 
This line is baz 
This line is foo 
This line is bar 
This line is baz 
This line is foo 
This line is bar 
This line is baz 
... 

Il pourrait probablement être appliquée à votre problème, bien que son {} cword sensibles.

2

Pourquoi ne pas:

:%s/\(.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo\(\_.\{-}\)foo/\1bar\2bar\3baz\4baz/ 

Je ne suis pas sûr qu'il couvre l'ampleur du problème mais a la vertu d'être un simple substitut. Une solution plus complexe peut couvrir la solution si celle-ci ne l'est pas.

+0

Hey Derek, Je voulais juste dire que j'ai regardé certaines de vos vidéos vim hier sur vimeo. Je voulais juste dire que j'ai aimé les regarder et appris quelques choses. Votre solution est ce que j'aurais atteint, si j'étais un peu mieux avec regex. Merci pour cette solution simple. – Ksiresh