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?

È stato utile?

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)

Da

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,

<*>
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top