Question

Je suis un utilisateur de vim et je souhaite pouvoir effectuer une boucle sur une gamme de sous-chaînes lorsque je le remplace. Comment puis-je utiliser de la magie vim pour partir d'un ensemble de lignes comme ceci:

Afoo
Bfoo
Cfoo
Dfoo

à

Abar
Bbar
Cbaz
Dbaz

?

Je souhaite rechercher dans mon fichier dès le début la prochaine occurrence de foo et remplacer les deux premières instances par bar et les deux autres par baz. L'utilisation d'une boucle for est-elle la meilleure option? Si c'est le cas, comment utiliser la variable de boucle dans la commande de substitution?

Était-ce utile?

La solution

Je voudrais utiliser une fonction qui a un état et appeler cette fonction à partir 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 utilisez-les avec:

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

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

EDIT: Voici une version intégrée en tant que commande qui:

  • accepte autant de remplacements que nous le souhaitons, tous les remplacements doivent être séparés par le caractère de séparation;
  • prend en charge les références arrière;
  • ne peut remplacer que les N premières occurrences, N == le nombre de remplacements spécifié si l'appel de commande est claqué (avec un!)
  • ne supporte pas les drapeaux habituels comme g, i (:h :s_flags) - pour cela, il faudrait par exemple imposer l'appel de commande pour toujours se terminer par un / (ou le caractère de séparation), sinon le caractère le dernier texte est interprété comme un drapeau.

Voici la définition de la 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, en considérant le texte initial:

AfooZ
BfooE
CfooR
DfooT

la commande

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

produirait:

ZbarA
BbarE
RbarC
DbarT

Autres conseils

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

J'ai écrit un plugin swapit http: //www.vim. org / scripts / script.php? script_id = 2294 , ce qui peut notamment vous aider à parcourir les listes de chaînes. P. ex.

 :Swaplist foobar foo bar baz

puis tapez

 This line is a foo

créer une simple ligne yank / coller, aller au dernier mot et ctrl-un échange.

 qqyyp$^A

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

 100@q

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é à votre problème même si son {cword} sensible.

Pourquoi pas:

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

Je ne suis pas sûr que cela couvre l’ampleur du problème, mais il a le mérite d’être un simple substitut. Une solution plus complexe peut couvrir la solution si celle-ci ne le fait pas.

Voici comment je tenterais 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.
1000@a      Do a many times.

Tous les conseils sur la façon de le faire mieux sont les bienvenus.

merci, Martin.

Il sera probablement beaucoup plus facile d’enregistrer une macro qui peut remplacer les deux premières, puis d’utiliser: s pour le reste.

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

Une fois que vous avez obtenu cette macro, faites 2@a pour remplacer les deux premières occurrences dans le tampon actuel et utilisez :s normalement pour remplacer le reste.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top