Funzione di conteggio parole veloce in Vim
-
02-07-2019 - |
Domanda
Sto cercando di visualizzare un conteggio delle parole in tempo reale nella linea di stato vim. Lo faccio impostando la mia linea di stato nel mio .vimrc e inserendo una funzione in esso. L'idea di questa funzione è di restituire il numero di parole nel buffer corrente. Questo numero viene quindi visualizzato sulla riga di stato. Questo dovrebbe funzionare bene poiché la linea di stato viene aggiornata praticamente in ogni occasione possibile, in modo che il conteggio rimanga sempre "attivo".
Il problema è che la funzione che ho attualmente definito è lenta e quindi vim è ovviamente lenta quando viene utilizzata per tutti tranne i file più piccoli; a causa di questa funzione eseguita così frequentemente.
In sintesi, qualcuno ha un trucco intelligente per produrre una funzione che è incredibilmente veloce nel calcolare il numero di parole nel buffer corrente e restituire il risultato?
Soluzione
Ecco una versione utilizzabile dell'idea di Rodrigo Queiro. Non cambia la barra di stato e ripristina la variabile statusmsg.
function WordCount()
let s:old_status = v:statusmsg
exe "silent normal g\<c-g>"
let s:word_count = str2nr(split(v:statusmsg)[11])
let v:statusmsg = s:old_status
return s:word_count
endfunction
Sembra essere abbastanza veloce da includere direttamente nella riga di stato, ad esempio:
:set statusline=wc:%{WordCount()}
Altri suggerimenti
Mi piace molto la risposta di Michael Dunn sopra ma ho scoperto che durante la modifica non riuscivo ad accedere all'ultima colonna. Quindi ho una piccola modifica per la funzione:
function! WordCount()
let s:old_status = v:statusmsg
let position = getpos(".")
exe ":silent normal g\<c-g>"
let stat = v:statusmsg
let s:word_count = 0
if stat != '--No lines in buffer--'
let s:word_count = str2nr(split(v:statusmsg)[11])
let v:statusmsg = s:old_status
end
call setpos('.', position)
return s:word_count
endfunction
L'ho incluso nella mia riga di stato senza problemi:
: set statusline = wc:% {WordCount ()}
Mantenere un conteggio per la riga corrente e un conteggio separato per il resto del buffer. Mentre digiti (o elimini) le parole sulla riga corrente, aggiorna solo quel conteggio, ma visualizza la somma del conteggio delle righe corrente e il resto del conteggio del buffer.
Quando si cambiano le righe, aggiungere il conteggio delle righe correnti al conteggio del buffer, contare le parole nella riga corrente e a) impostare il conteggio delle righe correnti eb) sottrarlo dal conteggio del buffer.
Sarebbe anche saggio ripetere periodicamente il buffer (si noti che non è necessario contare l'intero buffer in una volta, poiché si sa dove si sta verificando la modifica).
Questo ricalcola il numero di parole ogni volta che smetti di digitare per un po '(in particolare, updatetime
ms).
let g:word_count="<unknown>"
fun! WordCount()
return g:word_count
endfun
fun! UpdateWordCount()
let s = system("wc -w ".expand("%p"))
let parts = split(s, ' ')
if len(parts) > 1
let g:word_count = parts[0]
endif
endfun
augroup WordCounter
au! CursorHold * call UpdateWordCount()
au! CursorHoldI * call UpdateWordCount()
augroup END
" how eager are you? (default is 4000 ms)
set updatetime=500
" modify as you please...
set statusline=%{WordCount()}\ words
Enjoy!
Quindi ho scritto:
func CountWords() exe "normal g\" let words = substitute(v:statusmsg, "^.*Word [^ ]* of ", "", "") let words = substitute(words, ";.*", "", "") return words endfunc
Ma stampa le informazioni sulla barra di stato, quindi non credo che sarà adatto al tuo caso d'uso. È molto veloce, però!
Ho usato un approccio leggermente diverso per questo. Invece di assicurarmi che la funzione di conteggio delle parole sia particolarmente veloce, la chiamo solo quando il cursore si ferma. Questi comandi lo faranno:
:au CursorHold * exe "normal g\<c-g>"
:au CursorHoldI * exe "normal g\<c-g>"
Forse non proprio quello che voleva l'interrogante, ma molto più semplice di alcune delle risposte qui, e abbastanza buono per il mio caso d'uso (guarda in basso per vedere il conteggio delle parole dopo aver digitato una o due frasi).
L'impostazione di updatetime
su un valore inferiore aiuta anche qui:
set updatetime=300
Non esiste un enorme polling ambientale per il conteggio delle parole perché CursorHold
e CursorHoldI
sparano solo una volta quando il cursore smette di muoversi, non ogni updatetime
ms.
Ecco un perfezionamento della risposta di Abslom Daak che funziona anche in modalità visiva.
function! WordCount()
let s:old_status = v:statusmsg
let position = getpos(".")
exe ":silent normal g\<c-g>"
let stat = v:statusmsg
let s:word_count = 0
if stat != '--No lines in buffer--'
if stat =~ "^Selected"
let s:word_count = str2nr(split(v:statusmsg)[5])
else
let s:word_count = str2nr(split(v:statusmsg)[11])
end
let v:statusmsg = s:old_status
end
call setpos('.', position)
return s:word_count
endfunction
Incluso nella riga di stato come prima. Ecco una linea di stato allineata a destra:
set statusline =% =% {WordCount ()} \ words \
Ho preso gran parte di questo dalle pagine di aiuto di vim sulle funzioni di scrittura.
function! WordCount()
let lnum = 1
let n = 0
while lnum <= line('
Naturalmente, come gli altri, dovrai:
:set statusline=wc:%{WordCount()}
Sono sicuro che questo può essere ripulito da qualcuno per renderlo più vimmy (s: n invece di solo n?), ma credo che ci sia la funzionalità di base.
Modifica:
Ripensandoci, mi piace molto la soluzione di Mikael Jansson. Non mi piace sborsare su wc
(non portatile e forse lento). Se sostituiamo la sua funzione UpdateWordCount
con il codice che ho sopra (rinominando la mia funzione in UpdateWordCount
), allora penso che abbiamo una soluzione migliore.
)
let n = n + len(split(getline(lnum)))
let lnum = lnum + 1
endwhile
return n
endfunction
Naturalmente, come gli altri, dovrai:
<*>Sono sicuro che questo può essere ripulito da qualcuno per renderlo più vimmy (s: n invece di solo n?), ma credo che ci sia la funzionalità di base.
Modifica:
Ripensandoci, mi piace molto la soluzione di Mikael Jansson. Non mi piace sborsare su wc
(non portatile e forse lento). Se sostituiamo la sua funzione UpdateWordCount
con il codice che ho sopra (rinominando la mia funzione in UpdateWordCount
), allora penso che abbiamo una soluzione migliore.
Il mio suggerimento:
function! UpdateWordCount()
let b:word_count = eval(join(map(getline("1", "<*>quot;), "len(split(v:val, '\\s\\+'))"), "+"))
endfunction
augroup UpdateWordCount
au!
autocmd BufRead,BufNewFile,BufEnter,CursorHold,CursorHoldI,InsertEnter,InsertLeave * call UpdateWordCount()
augroup END
let &statusline='wc:%{get(b:, "word_count", 0)}'
Non sono sicuro di come questo paragoni in velocità ad alcune delle altre soluzioni, ma è sicuramente molto più semplice della maggior parte.
Sono nuovo dello scripting di Vim, ma potrei suggerire
function WordCount()
redir => l:status
exe "silent normal g\<c-g>"
redir END
return str2nr(split(l:status)[11])
endfunction
come un po 'più pulito poiché non sovrascrive la riga di stato esistente.
La mia ragione per la pubblicazione è di sottolineare che questa funzione ha un bug sconcertante: vale a dire, interrompe il comando append. Colpire A dovrebbe farti entrare nella modalità di inserimento con il cursore posizionato a destra del carattere finale sulla linea. Tuttavia, con questa barra di stato personalizzata abilitata ti porterà alla sinistra del carattere finale.
Qualcuno ha idea di cosa sia la causa?
Questo è un miglioramento su versione di Michael Dunn , memorizzando nella cache il conteggio delle parole, quindi è necessaria una elaborazione ancora minore.
function! WC()
if &modified || !exists("b:wordcount")
let l:old_status = v:statusmsg
execute "silent normal g\<c-g>"
let b:wordcount = str2nr(split(v:statusmsg)[11])
let v:statusmsg = l:old_status
return b:wordcount
else
return b:wordcount
endif
endfunction
Usando il metodo nella risposta fornita da Steve Moyer sono stato in grado di produrre la seguente soluzione. È un hack piuttosto inelegante, temo e sento che ci deve essere una soluzione più ordinata, ma funziona ed è molto più veloce del semplice conteggio di tutte le parole in un buffer ogni volta che la riga di stato viene aggiornata. Vorrei anche notare che questa soluzione è indipendente dalla piattaforma e non presuppone che un sistema abbia "wc" o qualcosa di simile.
La mia soluzione non aggiorna periodicamente il buffer, ma la risposta fornita da Mikael Jansson sarebbe in grado di fornire questa funzionalità. Non ho ancora trovato un'istanza in cui la mia soluzione non è sincronizzata. Tuttavia, l'ho testato solo brevemente poiché un conteggio delle parole dal vivo accurato non è essenziale per le mie esigenze. Il modello che uso per abbinare le parole è anche semplice ed è inteso per semplici documenti di testo. Se qualcuno ha un'idea migliore per uno schema o altri suggerimenti, non esitare a pubblicare una risposta o modificare questo post.
La mia soluzione:
"returns the count of how many words are in the entire file excluding the current line
"updates the buffer variable Global_Word_Count to reflect this
fu! OtherLineWordCount()
let data = []
"get lines above and below current line unless current line is first or last
if line(".") > 1
let data = getline(1, line(".")-1)
endif
if line(".") < line(":set statusline=wc:%{WordCount()}
quot;)
let data = data + getline(line(".")+1, "<*>quot;)
endif
let count_words = 0
let pattern = "\\<\\(\\w\\|-\\|'\\)\\+\\>"
for str in data
let count_words = count_words + NumPatternsInString(str, pattern)
endfor
let b:Global_Word_Count = count_words
return count_words
endf
"returns the word count for the current line
"updates the buffer variable Current_Line_Number
"updates the buffer variable Current_Line_Word_Count
fu! CurrentLineWordCount()
if b:Current_Line_Number != line(".") "if the line number has changed then add old count
let b:Global_Word_Count = b:Global_Word_Count + b:Current_Line_Word_Count
endif
"calculate number of words on current line
let line = getline(".")
let pattern = "\\<\\(\\w\\|-\\|'\\)\\+\\>"
let count_words = NumPatternsInString(line, pattern)
let b:Current_Line_Word_Count = count_words "update buffer variable with current line count
if b:Current_Line_Number != line(".") "if the line number has changed then subtract current line count
let b:Global_Word_Count = b:Global_Word_Count - b:Current_Line_Word_Count
endif
let b:Current_Line_Number = line(".") "update buffer variable with current line number
return count_words
endf
"returns the word count for the entire file using variables defined in other procedures
"this is the function that is called repeatedly and controls the other word
"count functions.
fu! WordCount()
if exists("b:Global_Word_Count") == 0
let b:Global_Word_Count = 0
let b:Current_Line_Word_Count = 0
let b:Current_Line_Number = line(".")
call OtherLineWordCount()
endif
call CurrentLineWordCount()
return b:Global_Word_Count + b:Current_Line_Word_Count
endf
"returns the number of patterns found in a string
fu! NumPatternsInString(str, pat)
let i = 0
let num = -1
while i != -1
let num = num + 1
let i = matchend(a:str, a:pat, i)
endwhile
return num
endf
Questo viene quindi aggiunto alla riga di stato da:
<*>Spero che questo aiuti chiunque cerchi un conteggio delle parole dal vivo in Vim. Anche se uno che non è sempre esatto. In alternativa, ovviamente, g ctrl-g ti fornirà il conteggio delle parole di Vim!
Nel caso in cui qualcun altro venga qui da Google, ho modificato la risposta di Abslom Daak per lavorare con Airline . Ho salvato quanto segue come
~ / .vim / bundle / vim-aerea / autoload / aerea / estensioni / pandoc.vim
e aggiunto
chiama compagnia aerea # estensioni # pandoc # init (s: ext)
a extensions.vim
let s:spc = g:airline_symbols.space
function! airline#extensions#pandoc#word_count()
if mode() == "s"
return 0
else
let s:old_status = v:statusmsg
let position = getpos(".")
let s:word_count = 0
exe ":silent normal g\<c-g>"
let stat = v:statusmsg
let s:word_count = 0
if stat != '--No lines in buffer--'
let s:word_count = str2nr(split(v:statusmsg)[11])
let v:statusmsg = s:old_status
end
call setpos('.', position)
return s:word_count
end
endfunction
function! airline#extensions#pandoc#apply(...)
if &ft == "pandoc"
let w:airline_section_x = "%{airline#extensions#pandoc#word_count()} Words"
endif
endfunction
function! airline#extensions#pandoc#init(ext)
call a:ext.add_statusline_func('airline#extensions#pandoc#apply')
endfunction
Una variante del raffinamento di Guy Gur-Ari
- conta le parole solo se il controllo ortografico è abilitato,
- conta il numero di parole selezionate in modalità visiva
- mantiene l'audio fuori dall'insert e dalla modalità normale e
- si spera sia più agnostico rispetto alla lingua del sistema (se diverso dall'inglese)
function! StatuslineWordCount()
if !&l:spell
return ''
endif
if empty(getline(line('
che può essere aggiunto alla linea di stato, diciamo,
set statusline+=%.10{StatuslineWordCount()} " wordcount
)))
return ''
endif
let mode = mode()
if !(mode ==# 'v' || mode ==# 'V' || mode ==# "\<c-v>" || mode =~# '[ni]')
return ''
endif
let s:old_status = v:statusmsg
let position = getpos('.')
let stat = v:statusmsg
let s:word_count = 0
exe ":silent normal g\<c-g>"
try
if mode ==# 'v' || mode ==# 'V'
let s:word_count = split(split(v:statusmsg, ';')[1])[0]
elseif mode ==# "\<c-v>"
let s:word_count = split(split(v:statusmsg, ';')[2])[0]
elseif mode =~# '[ni]'
let s:word_count = split(split(v:statusmsg, ';')[2])[3]
end
" index out of range
catch /^Vim\%((\a\+)\)\=:E\%(684\|116\)/
return ''
endtry
let v:statusmsg = s:old_status
call setpos('.', position)
return "\ \|\ " . s:word_count . 'w'
endfunction
che può essere aggiunto alla linea di stato, diciamo,
<*>