Domanda

Sono un utente VIM e voglio essere in grado di eseguire il ciclo su una serie di sottostringhe quando lo sostituisco. Come posso usare un po 'di magia vim per passare da una serie di linee come questa:

Afoo
Bfoo
Cfoo
Dfoo

a

Abar
Bbar
Cbaz
Dbaz

Voglio cercare il mio file dall'inizio per la prossima occorrenza di foo e sostituire le prime due istanze con bar e le seconde due con baz. L'utilizzo di un ciclo for è l'opzione migliore? In tal caso, come posso utilizzare la variabile loop nel comando di sostituzione?

È stato utile?

Soluzione

Userei una funzione che ha uno stato e chiamerei questa funzione da% s. Qualcosa del tipo:

" 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

E usali con:

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

Se lo si desidera, la chiamata ai due comandi può essere incapsulata in un singolo comando.


EDIT: ecco una versione integrata come comando che:

  • accetta tutte le sostituzioni che desideriamo, tutte le sostituzioni devono essere separate dal carattere separatore;
  • supporta riferimenti a ritroso;
  • può sostituire solo le prime occorrenze N, N == il numero di sostituzioni specificato se la chiamata al comando è esclusa (con un!)
  • non supporta i normali flag come g, i (:h :s_flags) - per questo, per esempio, dovremmo imporre che la chiamata di comando finisca sempre con un / (o qualunque carattere separatore), se non il l'ultimo testo viene interpretato come flag.

Ecco la definizione del comando:

: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

che potrebbe essere utilizzato in questo modo:

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

o anche, considerando il testo iniziale:

AfooZ
BfooE
CfooR
DfooT

il comando

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

produrrebbe:

ZbarA
BbarE
RbarC
DbarT

Altri suggerimenti

Questo non è esattamente quello che vuoi ma può essere utile per i cicli.

Ho scritto un plugin swapit http: //www.vim. org / scripts / script.php? script_id = 2294 che, tra le altre cose, può aiutare a scorrere gli elenchi di stringhe. Eg.

 :Swaplist foobar foo bar baz

quindi digitare

 This line is a foo

crea una semplice linea yank / paste, vai all'ultima parola e ctrl-a swap.

 qqyyp$^A

quindi esegue il modello di scambio

 100@q

per ottenere

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
...

Probabilmente potrebbe essere applicato al tuo problema sebbene sia {cword} sensibile.

Perché no:

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

Non sono sicuro che copra l'ampiezza del problema ma ha la virtù di essere un semplice sostituto. Uno più complesso può coprire la soluzione se questa non lo fa.

Ecco come tenterei quella 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.

Qualsiasi consiglio su come farlo meglio è il benvenuto.

Grazie, Martin.

Probabilmente sarà molto più semplice registrare una macro che può sostituire le prime due e quindi usare: s per il resto.

La macro potrebbe apparire come /foo^Mcwbar^[. Se non hai familiarità con la modalità macro, premi q, a (il registro in cui memorizzarlo) e poi i tasti /foo <Enter> cwbar <Escape>.

Ora una volta ottenuta quella macro, fai 2@a per sostituire le prime due occorrenze nel buffer corrente e usa :s normalmente per sostituire il resto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top