2011-12-21 16 views
13

así que tengo un archivo de texto como este:Vim Scripting - función de llamada de una vez para todas las líneas seleccionadas

Item a: <total> 
    Subitem: 10 min 
    Subitem 2: 20 min 

me gustaría sustituir <total> con el total de 10 y 20. En este momento estoy haciendo con las siguientes funciones:

let g:S = 0 "result in global variable S 
function! Sum(number) 
    let g:S = g:S + a:number 
    return a:number 
endfunction 

function! SumSelection() 
    let g:S=0 
    '<,'>s/\d\+/\=Sum(submatch(0))/g 
    echo g:S 
endfunction 

vnoremap <s-e> call SumSelection()<cr> 

Sum obtiene la suma de los números pasados ​​en SumSelection, las llamadas suma sobre todos los números en las líneas seleccionadas, y (supuestamente) Shift + e insta SumSelection en modo visual (o lo que usted elige para llamarlo)

El problema es que cuando pulso Mayús + e cuando tengo algunas líneas seleccionadas, en lugar de :call SumSelection() obtengo realmente :'<,'>call SumSelection(), lo que significa que se llama a la función una vez por línea seleccionada. No es bueno, ¿verdad? Por lo que puedo decir, no hay forma de evitar esto. ¿Qué puedo hacer para que la función de:

  1. solamente ser llamado una vez
  2. quizá un total de éstas de una manera más eficiente

Respuesta

18

Bueno, para tener una función de ejecutar una sola vez en un rango, añada la palabra clave range. P.ej.

fun Foo() range 
    ... 
endfun 

Luego, su función puede hacerse cargo de la estufa misma con los parámetros especiales un lugar de primera línea: y un: lastline. (Ver :help a:firstline para más detalles.)

Sin embargo creo que en este caso sus requisitos son justo lo suficientemente simple para ser logrado con una sola línea usando :global.

Podríamos hacer realmente con mejores entradas y salidas especificadas. Pero suponiendo un archivo que contiene

Item a: <total> 
    Subitem: 10 min 
    Subitem 2: 20 min 
Item b: <total> 
    Subitem: 10 min 
    Subitem 2: 23 min 
Item c: <total> 
    Subitem: 10 min 
    Subitem 2: 23 min 
    Subitem 3: 43 min 

y una función definida como

fun! Sum(n) 
    let g:s += a:n 
    return a:n 
endfun 

entonces esto va a resumir los subelementos (una sola línea)

:g/^Item/ let g:s = 0 | mark a | +,-/\nItem\|\%$/ s/\v(\d+)\ze\s+min$/\=Sum(submatch(0))/g | 'a s/<total>/\=(g:s . ' min')/

y producir esta salida

Item a: 30 min 
    Subitem: 10 min 
    Subitem 2: 20 min 
Item b: 33 min 
    Subitem: 10 min 
    Subitem 2: 23 min 
Item c: 76 min 
    Subitem: 10 min 
    Subitem 2: 23 min 
    Subitem 3: 43 min 

Por cierto, el comando anterior actúa en todo el búfer. Si primero resalta un rango y luego presiona la tecla :, vim precederá automáticamente a su comando con '<,'>, lo que significa que el siguiente comando funcionará desde el principio hasta el final del rango resaltado.

E.g. destacando las líneas 1 a 5 y después de ejecutar el comando (:'<,'> g/...) produce esta salida

Item a: 30 min 
    Subitem: 10 min 
    Subitem 2: 20 min 
Item b: 33 min 
    Subitem: 10 min 
    Subitem 2: 23 min 
Item c: <total> 
    Subitem: 10 min 
    Subitem 2: 23 min 
    Subitem 3: 43 min 

Una nota final. Si uno de los grupos no tiene subtemas, p.

Item a: <total> 
    Subitem: 10 min 
    Subitem 2: 20 min 
Item b: <total> 
Item c: <total> 
    Subitem: 10 min 
    Subitem 2: 23 min 
    Subitem 3: 43 min 

y el comando abortará con un error de 'Rango no válido' cuando llegue al segundo elemento. Puede hacer que Vim ignore este y continuar independientemente al agregar todo el comando al :silent!.

EDITAR

Sólo una explicación rápida de mi flujo de trabajo y por qué el largo de una sola línea.

  1. Me gusta :g y ex comandos mucho.
  2. Creé el comando de forma interactiva usando el historial de línea de comandos y la ventana de línea de comandos para editar.
  3. Cuando termino, pego comandos únicos como este en el búfer: tengo una asignación para ejecutar la línea actual como un comando ex. De esa forma, el comando siempre está disponible fácilmente cuando lo necesito. Para un uso más regular, es posible que desee un comando, función o ftplugin.
+2

es hermosa ... ... En serio, gracias por la mejor respuesta, así que he probablemente he recibido. –

+2

Por cierto, para cualquiera que piense que el comando: g anterior parece complejo, en realidad no lo es. La sintaxis es muy escueta y podría darle esa apariencia, pero las operaciones son bastante sencillas; no hay trucos inteligentes. Simplemente busque los bits que no encuentra en la maravillosa documentación, o pregunte en SO o en el grupo de vim_use Google. – 1983

+4

Me tomó mucho tiempo entender ... así que aquí es una explicación: : g se inicia un comando global /^/elemento se ejecuta el comando en cada línea que coincide con^artículo Sea G: s = 0 marque un Almacena la posición del cursor en +, -/\ nArtículo \ | \% $/s/\ v (\ d +) \ ze \ s + min $/\ = Suma (submatch (0))/g Esta es una bestia masiva de un comando sustituto. La parte hasta la primera 's' es el rango, la parte entre la 's' y '$' es el patrón y la llamada de función es la sustitución (nada es realmente reemplazado) 'a Regrese a la posición marcó un s//\ = (g: s 'min')/ finalmente reemplaza el

1

Sé que mi vim7.3-script es mucho más que adscriven 's.
Pero puede hacer el trabajo.

fun! SubTotal() 
    fun! Collect() 
     " define some global vars 
     if line('.') == 1 
      let g:dict = {} 
      let g:key = '' 
     endif 

     " set current key && add key to dict 
     fun! AddKey(k) 
      let g:key = a:k 
      let g:dict[a:k]= [] 
     endfun 

     " add value to dict[key] 
     fun! AddValue(v) 
      if g:key != '' 
       call add(g:dict[g:key], a:v) 
      endif 
     endfun 

     " scan every line 
     let line = getline('.') 
     if line =~ '^Item' 
      call AddKey(matchstr(line, '^Item\s\+\zs\w\+\ze\s*:')) 
     elseif line =~ '^\s*Subitem' 
      call AddValue(str2nr(matchstr(line, '\d\+\ze\s*min$'))) 
     endif 
     return line 
    endfun 

    " collect data line by line 
    silent %s/.*/\=Collect()/ 
    " replace <total> with sum 
    silent g/^Item/s/^Item\s\+\(\w\+\)\s*:\s*\zs.*/\=eval(join(g:dict[submatch(1)], '+'))/ 
endfun 

Item a: 30 
    Subitem: 10 min 
    Subitem 2: 20 min 
Item b: 33 
    Subitem: 10 min 
    Subitem 2: 23 min 
Item c: 76 
    Subitem: 10 min 
    Subitem 2: 23 min 
    Subitem 3: 43 min 

:echo dict 
{'a': [10, 20], 'b': [10, 23], 'c': [10, 23, 43]} 
Cuestiones relacionadas