2011-12-21 12 views
13

Così ho un file di testo come questo:Vim Scripting - funzione di chiamata una volta per tutte le linee selezionate

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

vorrei sostituire <total> con il totale di 10 e 20. In questo momento sto facendo con le seguenti funzioni:

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 ottiene la somma dei numeri passati in, SumSelection chiama somma su tutti i numeri di linee selezionate, e (presumibilmente) shift + e chiamate SumSelection in modalità visiva (o qualsiasi altra cosa che si sceglie chiamarlo.)

Il problema è che quando premo Maiusc + e quando ho selezionato alcune linee, invece di :call SumSelection() ottengo davvero :'<,'>call SumSelection() che significa che la funzione viene chiamata una volta per ogni riga selezionata. Non va bene, vero? Quindi, per quanto posso dire, non c'è modo di aggirare questo. Che cosa posso fare per ottenere la funzione di:

  1. essere chiamato solo una volta
  2. forse totale questi in modo più efficiente

risposta

18

Beh, avere una funzione di eseguire solo una volta in un intervallo, aggiungere la parola chiave range. Per esempio.

fun Foo() range 
    ... 
endfun 

Allora la vostra funzione può prendersi cura del campo di se stesso con i parametri speciali a: Firstline e un: lastLine. (Vedere :help a:firstline per i dettagli.)

però penso che in questo caso le vostre esigenze sono solo circa abbastanza semplice da realizzare con un one-liner utilizzando :global.

Potremmo fare davvero meglio con input e output specificati. Ma assumendo un file contenente

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 

e una funzione definita come

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

allora questo sommare gli elementi secondari (una sola riga)

: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')/

e produrre questo output

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 

A proposito, il comando precedente agisce sull'intero buffer. Se si evidenzia prima un intervallo e poi si preme la chiave :, vim aggiungerà automaticamente il comando con '<,'>, il che significa che il comando seguente funzionerà dall'inizio alla fine dell'intervallo evidenziato.

E.g. evidenziando linee da 1 a 5 e poi eseguendo il comando (:'<,'> g/...) produce questo risultato

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 finale. Se uno dei gruppi non ha elementi secondari, ad es.

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 

il comando si interrompe con un errore 'intervallo valido' quando raggiunge il secondo elemento. Puoi fare in modo che Vim ignori questo e continui a prescindere anteponendo l'intero comando a :silent!.

EDIT

Solo una breve spiegazione del mio flusso di lavoro e perché il lungo one-liner.

  1. Mi piace :g ed ex comandi molto.
  2. Ho sviluppato il comando in modo interattivo utilizzando la cronologia della riga di comando e la finestra della riga di comando per la modifica.
  3. Quando ho finito, incollo comandi singoli come questo nel buffer: ho una mappatura per eseguire la riga corrente come un comando ex. In questo modo il comando è sempre facilmente disponibile quando ne ho bisogno. Per un uso più regolare si potrebbe desiderare un comando, una funzione o un ftplugin.
+2

è ... bello ... Scherzi a parte, grazie per il miglior SO rispondere Probabilmente ho mai ricevuto. –

+2

Btw, per chiunque pensi che il comando: g sopra sembra complesso, non lo è davvero. La sintassi è molto concisa e potrebbe dargli quell'aspetto, ma le operazioni sono davvero piuttosto semplici; non ci sono trucchi intelligenti. Basta cercare i bit che non si trovano nella meravigliosa documentazione, o chiedere a SO o al gruppo vim_use di Google. – 1983

+4

Mi c'è voluto molto tempo per capire ... ecco la spiegazione: : g avvia un comando globale /^ Articolo/il comando viene eseguito in ogni riga che corrisponde^Articolo Sia G: s = 0 contrassegna a Memorizza la posizione del cursore in un +, -/\ nItem \ | \% $/s/\ v (\ d +) \ ze \ s + min $/\ = Sum (submatch (0))/g Questa è una bestia enorme di un comando sostitutivo. La parte fino alla prima "s" è la gamma, la parte tra "s" e "$" è il modello e la chiamata di funzione è la sostituzione (nulla viene effettivamente sostituito) 'a tornare alla posizione contrassegnato con s//\ = (g: s. 'min')/ sostituire infine lo

1

So che il mio vim7.3-script è molto più lungo di adscriven.
Ma può portare a termine il lavoro.

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]} 
Problemi correlati