2009-11-27 18 views
5

Soy un usuario de vim, y quiero poder recorrer un rango de subcadenas cuando lo estoy sustituyendo. ¿Cómo puedo utilizar un poco de magia vim para pasar de un conjunto de líneas de la siguiente manera:¿Cómo puedo sustituir de una lista de cadenas en VIM?

Afoo 
Bfoo 
Cfoo 
Dfoo 

a

Abar 
Bbar 
Cbaz 
Dbaz 

?

Quiero buscar mi archivo desde el comienzo de la próxima ocurrencia de foo, y vuelva a colocar los dos primeros casos con bar, y el segundo con dos baz. ¿Está utilizando un bucle for la mejor opción? Si es así, ¿cómo uso la variable loop en el comando de sustitución?

Respuesta

9

Usaría una función que tiene un estado y llamaré a esta función desde% s. Algo así como:

" 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 

y utilizarlos con:

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

La llamada a los dos comandos debería integrarse en un solo comando si lo desea.


EDIT: He aquí una versión integrada como una orden que:

  • acepta tantas sustituciones como deseamos, tiene que ser separado con el separador de caracteres todos los reemplazos;
  • admite referencias anteriores;
  • puede sustituir sólo las primeras apariciones de N, N == El número de reemplazos especificados si se golpeó la llamada de comando (con un!)
  • no es compatible con banderas habituales como g, i (:h :s_flags) - para que, tendríamos, por ejemplo, que imponer la llamada de comando siempre termina con un/(o cualquier carácter separador), si no el último texto se interpreta como banderas.

Aquí es la definición de comandos:

: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 

que podría ser utilizado de esta manera:

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

o incluso, teniendo en cuenta el texto inicial:

AfooZ 
BfooE 
CfooR 
DfooT 

el comando

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

produciría:

ZbarA 
BbarE 
RbarC 
DbarT 
0

Probablemente va a ser mucho más fácil grabar una macro que pueda reemplazar a las dos primeras, y luego usar: s para el resto.

La macro podría verse como /foo^Mcwbar^[. Si no está familiarizado con el modo macro, simplemente pulse q, a (el registro para almacenarlo) y luego las teclas /foo <Enter> cwbar <Escape>.

Ahora, una vez que tenga esa macro, haga [email protected] para reemplazar las dos primeras instancias en el búfer en uso y use :s normalmente para reemplazar el resto.

1

Así es como intentaría esa 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. 

Cualquier consejo sobre cómo hacerlo mejor es bienvenido.

gracias, Martin.

+0

Parece que te falta un 'n' antes de tu' .'' final – Rich

2

Esto no es estrictamente lo que quiere, pero puede ser útil para los ciclos.

He escrito un plug-in SWAPit http://www.vim.org/scripts/script.php?script_id=2294 que entre otras cosas puede ayudar con la bicicleta a través de listas de cadenas. P.ej.

:Swaplist foobar foo bar baz 

continuación, escriba

This line is a foo 

crear una línea simple tirón/pegar, ir a la última palabra y ctrl-swap.

qqyyp$^A 

luego ejecutar el patrón de intercambio

[email protected] 

para obtener

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

Probablemente se podría aplicar a su problema aunque su cword {} sensible.

2

Por qué no:

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

no estoy seguro que cubra la amplitud del problema, pero tiene la virtud de ser un sustituto simple. Una más compleja puede cubrir la solución si esta no.

+0

Hola Derek, Solo quería decir que vi algunos de tus videos de vim ayer en vimeo. Solo quería decir que disfruté observándolos y aprendí algunas cosas. Su solución es a la que habría llegado, si estuviera un poco mejor con la expresión regular. Gracias por esta simple solución. – Ksiresh

Cuestiones relacionadas