Búsqueda global y reemplazo en Vim, comenzando desde la posición del cursor y envolviendo

112

Cuando busco con el / comando de modo normal:

/\vSEARCHTERM

Vim comienza la búsqueda desde la posición del cursor y continúa hacia abajo, envolviendo hasta arriba. Sin embargo, cuando busco y reemplazo usando el :substitutecomando:

:%s/\vBEFORE/AFTER/gc

En cambio, Vim comienza en la parte superior del archivo.

¿Hay alguna manera de hacer que Vim realice la búsqueda y reemplace comenzando desde la posición del cursor y envolviendo hasta la parte superior una vez que llega al final?

basteln
fuente
2
\vpattern- patrón 'muy mágico': los caracteres no alfanuméricos se interpretan como símbolos regex especiales (no es necesario escapar)
Carlos Araya
¿Cuál es el punto para comenzar desde la posición actual y envolver el archivo en lugar de usar solo %? No veo ningún uso razonable para que estés revisando todo el archivo de todos modos.
tymik
@tymik El propósito era comenzar a buscar desde el cursor y revisar cada resultado paso a paso. Pero no he usado Vim en años.
basteln

Respuestas:

50

1.  No es difícil lograr el comportamiento mediante una sustitución de dos pasos:

:,$s/BEFORE/AFTER/gc|1,''-&&

Primero, el comando de sustitución se ejecuta para cada línea comenzando desde la actual hasta el final del archivo:

,$s/BEFORE/AFTER/gc

Luego, ese :substitutecomando se repite con el mismo patrón de búsqueda, cadena de reemplazo y banderas, usando el :& comando (ver :help :&):

1,''-&&

Este último, sin embargo, realiza la sustitución en el rango de líneas desde la primera línea del archivo hasta la línea donde se estableció la marca de contexto anterior, menos uno. Dado que el primer :substitutecomando almacena la posición del cursor antes de iniciar los reemplazos reales, la línea a la que se dirige  ''es la línea que era la actual antes de que se ejecutara el comando de sustitución. (La '' dirección se refiere a la ' pseudo-marca; consulte :help :rangey :help ''para obtener más detalles).

Tenga en cuenta que el segundo comando (después del | separador de comandos, consulte :help :bar) no requiere ningún cambio cuando el patrón o las banderas se modifican en el primero.

2.  Para ahorrar algo de escritura, para que aparezca el esqueleto del comando de sustitución anterior en la línea de comando, se puede definir un mapeo en modo normal, así:

:noremap <leader>cs :,$s///gc\|1,''-&&<c-b><right><right><right><right>

La <c-b><right><right><right><right>parte final es necesaria para mover el cursor al principio de la línea de comando ( <c-b>) y luego cuatro caracteres a la derecha ( <right> × 4), poniéndolo así entre los dos primeros signos de barra, listo para que el usuario comience a escribir el patrón de búsqueda. . Una vez que el patrón deseado y el reemplazo están listos, el comando resultante se puede ejecutar presionando Enter.

(Se podría considerar tener en //lugar de ///en el mapeo anterior, si se prefiere escribir el patrón, luego escribir la barra inclinada de separación, seguida de la cadena de reemplazo, en lugar de usar la flecha derecha para mover el cursor sobre una barra inclinada de separación ya presente que comienza la pieza de repuesto.)

ib.
fuente
1
El único problema con esta solución es que las opciones last (l) y quit (q) no detienen la ejecución del segundo comando sustituto
Steve Vermeulen
@eventualEntropy Vea mi solución a continuación sobre cómo solicitar otra 'q' presione.
q335r49
2
No olvides (como hice yo) escapar de la tubería si estás poniendo esto en un mapeo de claves.
jazzabeanie
11
Dios mío, ¿qué significan todos esos personajes arbitrarios? ¿Cómo aprendiste eso?
mapache rocoso
2
@Tropilio: A continuación, puede intentar algo como esto: :noremap <leader>R :,$s///gc\|1,''-&&<c-b><right><right><right><right>. Se coloca :,$s///gc|1,''-&&en la línea de comando con el cursor entre los dos primeros signos de barra. Una vez que escriba el patrón y el reemplazo deseados, puede ejecutar el comando resultante presionando Enter .
ib.
174

Ya está utilizando un rango, %que es la abreviatura de 1,$significar todo el archivo. Para ir de la línea actual al final que usa .,$. El punto significa la línea actual y $significa la última línea. Entonces el comando sería:

:.,$s/\vBEFORE/AFTER/gc

Pero .se puede suponer la línea o actual, por lo tanto, se puede eliminar:

:,$s/\vBEFORE/AFTER/gc

Para obtener más ayuda, consulte

:h range
Peter Rincker
fuente
Gracias, ya lo sabía. Quiero que la búsqueda y el reemplazo se completen una vez que llegue al final del documento. Con:., $ S no hace eso, solo dice "Patrón no encontrado".
basteln
7
Para "las siguientes diez líneas":10:s/pattern/replace/gc
aquí
10
aunque no es lo que el OP estaba buscando, esto fue muy útil para mí, ¡gracias!
Tobias J
51

%es un atajo para 1,$
(Vim help => :help :%igual a 1, $ (el archivo completo).)

. es la posición del cursor para que pueda hacer

:.,$s/\vBEFORE/AFTER/gc

Reemplazar desde el principio del documento hasta el cursor

:1,.s/\vBEFORE/AFTER/gc

etc

Le sugiero encarecidamente que lea el manual sobre el rango, :help rangeya que casi todos los comandos funcionan con un rango.

mb14
fuente
Gracias, ya lo sabía. Quiero que la búsqueda y el reemplazo se completen una vez que llegue al final del documento. Con:., $ S no hace eso, solo dice "Patrón no encontrado".
basteln
@basteln: hubo un error tipográfico en la publicación // ¿copiaste y pegaste?
sehe
Entonces, desea reemplazar TODO, pero como desea pedir confirmación, desea comenzar desde la posición actual. Entonces hazlo dos veces.
mb14
puede utilizar ny & en su lugar.
mb14
hmm, supongo que ny & es la mejor solución, aunque no es exactamente como debería ser (con el mensaje sí / no en cada partido). Pero definitivamente lo suficientemente bueno, ¡gracias! :)
basteln
4

FINALMENTE se me ocurrió una solución al hecho de que al salir de la búsqueda se llega al principio del archivo sin escribir una función enorme ...

No creerías cuánto tiempo me tomó pensar en esto. Simplemente agregue un mensaje si desea ajustar: si el usuario presiona qnuevamente, no ajuste. Básicamente, salga de la búsqueda tocando en qqlugar de q! (Y si desea ajustar, simplemente escriba y).

:,$s/BEFORE/AFTER/gce|echo 'Continue at beginning of file? (y/q)'|if getchar()!=113|1,''-&&|en

De hecho, tengo esto asignado a una tecla de acceso rápido. Entonces, por ejemplo, si desea buscar y reemplazar cada palabra debajo del cursor, comenzando desde la posición actual, con q*:

exe 'nno q* :,$s/\<<c-r>=expand("<cword>")<cr>\>//gce\|echo "Continue at beginning of file? (y/q)"\|if getchar()==121\|1,''''-&&\|en'.repeat('<left>',77)
q335r49
fuente
En mi opinión, este enfoque, que inserta un mensaje adicional entre los dos comandos propuestos en mi respuesta, agrega una complejidad innecesaria sin mejorar la usabilidad. En la versión original , si sale del primer comando de sustitución (con q, lo Esc ) y no desea continuar desde arriba, ya puede presionar qo Esc nuevamente para salir del segundo comando de inmediato. Es decir, la adición propuesta del mensaje parece duplicar efectivamente la funcionalidad ya presente.
ib.
Amigo ... ¿has probado tu enfoque? Digamos que quiero reemplazar las siguientes 3 apariciones de "dejar" con "unlet", y luego, esta es la clave, continuar editando el párrafo . Tu enfoque "Presiona y, y, y, ahora presiona q. Ahora comienzas a buscar en la primera línea del párrafo. Presiona q nuevamente para salir. Ahora regresa a donde estabas antes, para continuar editando. Ok, g ;. Entonces, básicamente: yyyqq ... confunde ... g; (o u ^ R) Mi método: yyyqq
q335r49
El método ideal es, por supuesto yyyq, continuar editando, sin saltos desorientadores. Pero yyyqqes casi tan bueno, si considera un doble toque prácticamente un solo toque en términos de facilidad de uso.
q335r49
Ni el comando original ni su versión con un indicador devuelven el cursor a su posición anterior en ese escenario. El primero deja al usuario en la primera aparición del patrón desde arriba (donde estaba en el momento de presionar qla segunda vez). Este último deja el cursor en la última aparición que se reemplazó (justo antes de qpresionar la primera ).
ib.
1
En la versión original , si es preferible volver automáticamente el cursor a su posición anterior (sin que el usuario que pulsa Ctrl + O ), uno puede simplemente añadir el norm!``comando: :,$s/BEFORE/AFTER/gc|1,''-&&|norm!``.
ib.
3

Aquí hay algo muy aproximado que aborda las preocupaciones acerca de envolver la búsqueda con el enfoque de dos pasos ( :,$s/BEFORE/AFTER/gc|1,''-&&) o con un intermedio "¿Continuar al principio del archivo?" Acercarse:

" Define a mapping that calls a command.
nnoremap <Leader>e :Substitute/\v<<C-R>=expand('<cword>')<CR>>//<Left>

" And that command calls a script-local function.
command! -nargs=1 Substitute call s:Substitute(<q-args>)

function! s:Substitute(patterns)
  if getregtype('s') != ''
    let l:register=getreg('s')
  endif
  normal! qs
  redir => l:replacements
  try
    execute ',$s' . a:patterns . 'gce#'
  catch /^Vim:Interrupt$/
    return
  finally
    normal! q
    let l:transcript=getreg('s')
    if exists('l:register')
      call setreg('s', l:register)
    endif
  endtry
  redir END
  if len(l:replacements) > 0
    " At least one instance of pattern was found.
    let l:last=strpart(l:transcript, len(l:transcript) - 1)
    " Note: type the literal <Esc> (^[) here with <C-v><Esc>:
    if l:last ==# 'l' || l:last ==# 'q' || l:last ==# '^['
      " User bailed.
      return
    endif
  endif

  " Loop around to top of file and continue.
  " Avoid unwanted "Backwards range given, OK to swap (y/n)?" messages.
  if line("''") > 1
    1,''-&&"
  endif
endfunction

Esta función utiliza un par de trucos para comprobar si deberíamos pasar al principio:

  • No se envuelve si el usuario presionó "l", "q" o "Esc", cualquiera de los cuales indica un deseo de abortar.
  • Detecte la última pulsación de tecla grabando una macro en el registro "s" e inspeccionando el último carácter de la misma.
  • Evite sobrescribir una macro existente guardando / restaurando el registro "s".
  • Si ya está grabando una macro cuando usa el comando, todas las apuestas están canceladas.
  • Intenta hacer lo correcto con las interrupciones.
  • Evita las advertencias de "rango hacia atrás" con una guardia explícita.
inquietante
fuente
1
Extraje esto en un pequeño complemento y lo puse en GitHub aquí .
wincent
¡Acaba de instalar su complemento, funciona a las mil maravillas! ¡Muchas gracias por hacer esto!
Tropilio
1

Llegué tarde a la fiesta, pero confié demasiado a menudo en respuestas de stackoverflow tan tardías para no hacerlo. Reuní sugerencias sobre reddit y stackoverflow, y la mejor opción es usar el \%>...cpatrón en la búsqueda, que coincide solo después del cursor.

Dicho esto, también estropea el patrón para el siguiente paso de reemplazo y es difícil de escribir. Para contrarrestar esos efectos, una función personalizada debe filtrar el patrón de búsqueda posteriormente, restableciéndolo. Vea abajo.

Me he enfrentado a un mapeo que reemplaza la siguiente ocurrencia y salta al siguiente después, y no más (era mi objetivo de todos modos). Estoy seguro de que, basándose en esto, se puede elaborar una sustitución global. Tenga en cuenta que cuando trabaje en una solución que tenga como objetivo algo así, :%s/.../.../gel patrón a continuación filtra las coincidencias en todas las líneas que quedan hasta la posición del cursor, pero se limpia después de que se completa la sustitución única, por lo que pierde ese efecto directamente después, salta al siguiente partido y, por lo tanto, puede ejecutar todos los partidos uno por uno.

fun! g:CleanColFromPattern(prevPattern) return substitute(a:prevPattern, '\V\^\\%>\[0-9]\+c', '', '') endf nmap <F3>n m`:s/\%><C-r>=col(".")-1<CR>c<C-.r>=g:CleanColFromPattern(getreg("/"))<CR>/~/&<CR>:call setreg("/", g:CleanColFromPattern(getreg("/")))<CR>``n

simlei
fuente
Gracias, estoy de acuerdo en que una respuesta tardía suele ser útil. Personalmente, ya no uso Vim desde hace mucho tiempo (por razones como esta), pero estoy seguro de que a muchas otras personas les resultará útil.
Basteln