Come posso sostituire un elenco di stringhe in VIM?
-
05-07-2019 - |
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?
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.