Inserte un número incremental en cada línea en una selección o en una coincidencia

10

Tengo un problema que puedo pensar en dos enfoques generales para resolver, pero no sé detalles para ninguno de los enfoques.

...
Level 1:    cũng    also
Level 1:    và      and
Level 1:    như     like; such as
Level 2:    các     plural marker
Level 2:    của     belonging to
...

Para cada línea que comience "Nivel n" quiero insertar un número, comenzando con "01". Por simplicidad, antepongamos el número.

Enfoque 1: seleccione manualmente todas las líneas con el mismo nivel. Invocar magia pronto voy a aprender.

Enfoque 2: escriba una búsqueda y reemplace que coincida con todas las líneas con un nivel determinado que en cada coincidencia incluya un número en el texto de reemplazo, que se incrementa en uno con cada coincidencia.

Encontré preguntas similares en StackOverflow o en otros sitios de Vim, pero cada uno parece tener uno o más de los siguientes problemas:

  1. Se trata de insertar el número de línea actual en lugar de un número arbitrario pero incremental.
  2. No pone a cero el número.
  3. En realidad, no funciona para las selecciones en mi Vim 7.4 que se ejecuta en Windows 7. (Estas dan como resultado el error E481: No range allowed).

Estoy ejecutando gVim en Windows con mswin.vim, pero una solución que funcione en todas las instalaciones de Villa de Vanilla sin tener que personalizar la configuración podría ser la mejor.

hippietrail
fuente
Las mejores etiquetas que podría pensar para la pregunta no existen: selección y rango, así que siéntase libre de volver a etiquetar o crear una de esas etiquetas.
hippietrail
1
Este complemento no es una solución completa para su problema, pero es tremendamente útil para agregar columnas de números: VisIncr . Documentos aquí . FWIW
lcd047

Respuestas:

17

Similar a la respuesta en https://vi.stackexchange.com/a/818/227 , puede usar el comando global.

Con él, puede indicar a vim que busque líneas que coincidan con un patrón y luego ejecutar comandos en él.

En su caso, desea anteponer texto a las líneas que comienzan con "Nivel N:", por lo que nuestro comando global podría ser

:g/^Level \d:/{COMMANDS}

Usar el comando sustituto (reemplazo de expresión regular) para el comando

Los comandos son más divertidos. Por lo general, me gusta hacer un reemplazo de expresión regular para cosas como esta, ya que es fácil de usar variables.

Ejemplo para tu pregunta

:let i = 1 | g/^Level \d:/s/^/\=printf("%02d ", i)/ | let i = i+1

Cómo funciona

En la sección de reemplazo de un comando de sustitución puede ser una expresión.

Lo primero que haremos es establecer una variable ipara que sea el número inicial. Elegí 1, pero cualquier número servirá.let i = 1

Luego ejecutamos nuestro comando global, que nos prepara para hacer una acción en líneas coincidentes. g/^Level \d:/

Haremos que nuestro comando global inserte el valor e incremente nuestro contador usando el comando de sustitución y el comando let. s/^/\=printf("%02d ", i)/ | let i = i+1

La expresión regular del comando de sustitución encuentra el comienzo de la línea ^y la reemplaza con una expresión, y nuestra expresión será el resultado de una impresión formateada. Al igual que en el lenguaje C, el printf de vim toma parámetros de formato. %02dsignifica convertir un argumento como si fuera un número decimal d, ocupando al menos 2 espacios 2y rellenar con 0 0. Para obtener detalles y otras opciones de conversión (incluido el formato de punto flotante), consulte :help printf. Le damos a printf nuestra variable de conteo iy nos da 01la primera vez, 02la segunda, etc. Esto se usa mediante el comando de sustitución para reemplazar el comienzo de la línea, insertando efectivamente el resultado de printf al principio.

Nota que puse un espacio después de la d: "%02d ". No lo solicitó en la pregunta (y no vi resultados de ejemplo), pero sospeché que deseaba separar el número de la palabra "Nivel". Elimine el espacio de la cadena dada a printf para tener el número insertado justo al lado de la L en el Nivel.

Finalmente, eso let i = i + 1incrementa nuestro contador después de cada sustitución.

Esto se puede aplicar generalmente para reemplazar partes de líneas que coinciden con otros criterios con datos funcionales arbitrarios.

Usar comandos normales combinados

Esto es bueno para inserciones simples o edición compleja. Al igual que con el sustituto, usaremos global para coincidir, pero en lugar de la sustitución de expresiones regulares, ejecutaremos una serie de operaciones como si fueran escritas por el usuario.

Ejemplo para tu pregunta

:let i = 1 | g/^Level \d:/execute "normal! I" . printf("%02d ", i) | let i = i+1

Cómo funciona

Los valores utilizados son muy similares al sustituto (todavía estamos usando printf para formatear nuestro número para que sea 0 rellenado con 2 dígitos), pero la operación es diferente.

Aquí usamos el comando execute, que toma una cadena y la ejecuta como un comando ex ( :help :exe). Construimos una cadena que combina "normal! I" con nuestros datos, que será "normal! I01" la primera vez y "normal! I02" la segunda vez, etc.

El normalcomando realiza operaciones como si estuviera en modo normal. En este ejemplo, nuestro comando normal es I, que se inserta al comienzo de la línea. Si lo hubiéramos usado dd, eliminaría la línea, oabriría una nueva línea después de la línea coincidente. Es como si escribiera I(o cualquier otra operación) usted mismo en modo normal. Usamos el !after normalpara asegurarnos de que no haya mapeos en nuestro camino. Ver :help :normal.

Lo que se inserta entonces es el valor de nuestra printf, como en el primer ejemplo de uso de sustituto.

Este método puede ser más sofisticado que regex, porque puede hacer cosas como execute "normal! ^2wy" . i . "th$p"ir al principio del texto ^, avanzar 2 palabras 2w, tirar hasta el carácter de la 'h' y" . i . "th, moverse al final de la línea $y pegar p.

Esto es casi como ejecutar una macro, pero en realidad no usa un registro y puede combinar cadenas de cualquier expresión. Esto me parece muy poderoso.

Enfoque donde cada nivel tiene su propio contador

Es posible que desee que cada nivel obtenga su propio contador. Si conoce el número máximo de niveles con anticipación, puede hacer lo siguiente (agregar código adicional para encontrar el nivel más alto podría no ser demasiado difícil, pero haría que esta respuesta fuera demasiado larga. Esto es cada vez más larga).

Primero, liberemos i, en caso de que ya lo hayamos usado como un entero. No podemos convertir i en una lista, tenemos que crearlo de esa manera.

:unlet! i

A continuación, establezcamos i como una lista que contiene el número de niveles. Mostraste 2 en tu pregunta, pero supongamos 10 por diversión. Dado que la indexación de la lista está basada en 0, y no quiero molestarme en corregir 1 basada en su lista, crearemos suficientes elementos (11) y nunca usaremos el índice 0.

:let j = 0
:let i = []
:while j < 11 | let i += [1] | let j += 1 | endwhile

A continuación, necesitamos una forma de obtener el número de nivel. Afortunadamente, el sustituto también está disponible como una función, por lo que le daremos nuestra línea y extraeremos el número de nivelsubstitute(getline("."), "^Level \\(\\d\\):.*", "\\=submatch(1)", "")

Como ahora es una lista de 11 1s (cada índice es el contador de nuestro nivel), ahora podemos ajustar cualquiera de los ejemplos anteriores para usar el resultado de esta sustitución:

Mediante comando sustituto:

:unlet! i | unlet! j | let j = 0 | let i = [] | while j < 11 | let i += [1] | let j += 1 | endwhile
:g/^Level \d:/let ind=str2nr(substitute(getline("."), "^Level \\(\\d\\):.*", "\\=submatch(1)", "")) | s/^/\=printf("%02d ", i[ind])/ | let i[ind] += 1

Por comando normal:

:unlet! i | unlet! j | let j = 0 | let i = [] | while j < 11 | let i += [1] | let j += 1 | endwhile
:g/^Level \d:/let ind=str2nr(substitute(getline("."), "^Level \\(\\d\\):.*", "\\=submatch(1)", "")) | execute "normal! I" . printf("%02d ", i[ind]) | let i[ind] += 1

Entrada de ejemplo:

Level 1: stuff

Level 1: Stuff

Some text
Level 3: Other

Level 1: Meh

Level 2: More

Salida de ejemplo:

01 Level 1: stuff

02 Level 1: Stuff

Some text
01 Level 3: Other

03 Level 1: Meh

01 Level 2: More
John O'M.
fuente
Guau, muy enciclopédico. Solo he leído la primera parte y siento que he aprendido más sobre Vim solo de eso que en los últimos años. Este es el tipo de respuesta que hace que Stack Exchange sea mejor que los sitios promedio de preguntas y respuestas y también muestra los beneficios de tener un SE específico de Vim.
hippietrail
3

Puede construir desde https://stackoverflow.com/a/4224454/15934 para poner a cero sus números.

" A reminder: how to start numbers at the first line
:'<,'>s/^\s*\zs/\=(line('.') - line("'<")+1).'. '

Pero para simplificar la acción de los números de relleno, elegiría un par de funciones y un comando:

command!  -range=% -nargs=? PrependNumbers <line1>,<line2>call s:Prepend(<args>)

function! s:ReplExpr(nb_digits, number)
  return repeat('0', a:nb_digits - strlen(a:number)).a:number
endfunction

function! s:Prepend(...) range
  let pattern = a:0 > 0 ? '\ze'. a:1 : '^'
  let nb_values = (a:lastline - a:firstline) + 1
  let nb_digits = strlen(nb_values)
  exe ':'.a:firstline.','a:lastline.'s#'.pattern.'#\=s:ReplExpr(nb_digits, 1+ line(".")-'.a:firstline.')." "#'
endfunction

Luego, seleccione sus líneas y escriba :PrependNumber(verá :'<,'>PrependNumber. [Nota: El comando toma un parámetro opcional: el patrón antes del cual se insertará el número]

Luc Hermitte
fuente
¿Pero no line(".")significa "usar el número de línea actual"? Ese era mi problema 1. con respuestas anteriores que pude encontrar.
hippietrail
1
Sí lo es. Es por eso que resta el número de línea de la primera línea en el rango.
Luc Hermitte
Ah, bien, aún no lo he probado porque ni siquiera recuerdo cómo ingresar un script en Vim ... tengo que leerlo primero (-:
hippietrail
1
Como estás debajo de Windows, copia y pega el código $HOME/vimfiles/plugin/whatevernameyouwish.vim. O incluso su $HOME/_vimrc(nombre de archivo de Windows) si lo prefiere (en una primera vez). Si no está seguro de qué $ HOME hay en su máquina, pregúntele a Vim qué cree que es ->:echo $HOME
Luc Hermitte
1
Ni siquiera necesita :sourcesi el script vim está instalado correctamente en el directorio correcto ({rtp} / plugin, o {rtp} / ftplugin / {filetype} / ftplugin para complementos específicos de tipo de archivo). :sourcees con lo que tuvimos que jugar hace más de una década y media.
Luc Hermitte
2

Podrías abordarlo grabando una macro. Comenzando con su archivo original, anteponga la primera instancia con el número.

01 Level 1:    cũng    also
Level 1:    và      and
Level 1:    như     like; such as
Level 2:    các     plural marker
Level 2:    của     belonging to

Mueva el cursor hacia 01, selecciónelo y tírelo con yiw. Ahora desea registrar sus acciones desde este punto.

qq/^Level 1<CR>P<C-A>A<space><esc>0yiwq

  • qq Iniciar una macro en el registro q
  • /^Level 1<CR> Busque una línea que comience con "Nivel 1"
  • P pegar antes del cursor (contiene su número)
  • <C-A> incrementa el número
  • A<space><esc> Insertar un espacio después del número
  • 0 Ir al inicio
  • yiw tirar del número actual
  • q Fin de la macro

Luego repita esta macro usando @q.

jecxjo
fuente
1
¿Es <C-A>control + a? Eso selecciona todo en Vim en Windows.
hippietrail
Es Control + a. En vim debería incrementar un número. Si ha cambiado sus combinaciones de teclas para realizar acciones de Windows en lugar de acciones de Vim, entonces no podrá hacer la mayoría de las cosas que las personas publican aquí.
jecxjo
No, la versión Vim para Windows viene con diferentes enlaces debido a la sabiduría infinita de alguien. Solo estoy usando los enlaces de vainilla listos para usar. Nunca he cambiado un enlace de clave Vim en más de veinte años, que puedo recordar (-:
hippietrail
No debería ser el caso, mi instalación de Windows tiene enlaces vim normales. ¿Podría ser posible que hayas instalado la compilación de vim de otra persona? ¿O podrías estar ejecutando vim en modo "Fácil"? Creo que Windows instala iconos de escritorio con opciones de modo normal y fácil.
jecxjo
3
No, es porque la instalación predeterminada en Windows carga mswin.vim. Si crea su propio vimrc y no carga mswin.vim, obtendrá los enlaces vim normales. Mantengo un solo vimrc para todas mis instalaciones (Linux, Mac y Windows) y nunca trato con mswin.vim. Para obtener más información sobre este problema, consulte stackoverflow.com/questions/289681/…
jecxjo