Fonction de comptage de mots rapide dans Vim

J'essaie d'afficher un nombre de mots en direct dans la ligne d'état vim. Je le fais en définissant ma ligne d'état dans mon .vimrc et y insérer une fonction. L'idée de cette fonction est de retourner le nombre de mots dans le tampon courant. Ce numéro est ensuite affiché sur la ligne d'état. Cela devrait bien fonctionner car la ligne d'état est mise à jour à peu près toutes les occasions possibles, de sorte que le compte restera toujours "en direct".

Le problème est que la fonction que j'ai actuellement définie est lent et donc vim est évidemment lent quand il est utilisé pour tous les fichiers sauf les plus petits; en raison de cette fonction exécutée si fréquemment.

En résumé, quelqu'un a-t-il une astuce astucieuse pour produire une fonction extrêmement rapide pour calculer le nombre de mots dans le tampon actuel et renvoyer le résultat?

32
vim
demandé sur Greg Sexton 2008-09-22 15:58:22

13 réponses

Voici une version utilisable de L'idée de Rodrigo Queiro. Il ne change pas la barre d'état et restaure la variable 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

Cela semble être assez rapide pour inclure directement dans la ligne d'état, par exemple:

:set statusline=wc:%{WordCount()}
23
répondu 2009-02-16 13:13:01

J'aime vraiment la réponse de Michael Dunn ci-dessus, mais j'ai trouvé que lorsque je modifiais, cela me rendait incapable d'accéder à la dernière colonne. J'ai donc un changement mineur pour la fonction:

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

Je l'ai inclus dans ma ligne d'état sans aucun problème:

:set statusline=wc:%{WordCount()}

24
répondu Abslom Daak 2015-11-01 16:50:19

Gardez un compte pour la ligne en cours et un compte séparé pour le reste du tampon. Lorsque vous tapez (ou supprimez) des mots sur la ligne en cours, mettez à jour uniquement ce nombre, mais affichez la somme du nombre de lignes en cours et du reste du nombre de tampons.

Lorsque vous changez de ligne, ajoutez le nombre de lignes actuel au nombre de tampons, comptez les mots dans la ligne actuelle et a) définissez le nombre de lignes actuel et b) soustrayez-le du nombre de tampons.

Il serait également sage de recompter le tampon périodiquement (notez que vous n'avez pas à compter tout le tampon à la fois, puisque vous savez où l'édition se produit).

9
répondu Steve Moyer 2008-09-22 12:09:42

Cela recalculera le nombre de mots chaque fois que vous arrêtez de taper pendant un certain temps (en particulier, 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

Profitez!

4
répondu Mikael Jansson 2008-09-22 18:04:59

J'ai Donc écrit:

func CountWords()
    exe "normal g\"
    let words = substitute(v:statusmsg, "^.*Word [^ ]* of ", "", "")
    let words = substitute(words, ";.*", "", "")
    return words
endfunc

Mais il imprime des informations dans la barre d'état, donc je ne pense pas qu'il conviendra à votre cas d'utilisation. C'est très rapide, cependant!

3
répondu Rodrigo Queiro 2008-09-22 14:20:55

, j'ai utilisé une approche légèrement différente pour cette. Plutôt que de m'assurer que la fonction de comptage de mots est particulièrement rapide, je l'appelle seulement lorsque le curseur cesse de bouger. Ces commandes le feront:

:au CursorHold * exe "normal g\<c-g>"
:au CursorHoldI * exe "normal g\<c-g>"

Peut-être pas tout à fait ce que le questionneur voulait, mais beaucoup plus simple que certaines des réponses ici, et assez bon pour mon cas d'utilisation (regardez vers le bas pour voir le nombre de mots après avoir tapé une phrase ou deux).

Définir updatetime sur une valeur plus petite aide également ici:

set updatetime=300

Il n'y a pas un énorme overhead polling pour le nombre de mots car CursorHold et CursorHoldI ne déclenchent qu'une seule fois lorsque le curseur cesse de bouger, pas toutes les updatetime ms.

2
répondu Leo 2012-11-24 09:25:54

Voici un raffinement de la réponse D'Abslom Daak qui fonctionne également en mode visuel.

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

Inclus dans la ligne d'état comme avant. Voici une ligne d'état alignée à droite:

set statusline=%=%{WordCount()}\ words\

2
répondu Guy Gur-Ari 2013-09-19 13:17:01

J'ai pris la majeure partie de ceci des pages d'aide de vim sur des fonctions d'écriture.

function! WordCount()
  let lnum = 1
  let n = 0
  while lnum <= line('$')
    let n = n + len(split(getline(lnum)))
    let lnum = lnum + 1
  endwhile
  return n
endfunction

Bien sûr, comme les autres, vous aurez besoin de:

:set statusline=wc:%{WordCount()}

Je suis sûr que cela peut être nettoyé par quelqu'un pour le rendre plus vimmy (s:N au lieu de juste n?), mais je crois que la fonctionnalité de base est là.

Modifier:

En regardant à nouveau, j'aime vraiment la solution de Mikael Jansson. Je n'aime pas bombarder à wc (pas portable et peut-être lent). Si nous remplaçons sa fonction UpdateWordCount par code que j'ai ci-dessus (renommer ma fonction en UpdateWordCount), alors je pense que nous avons une meilleure solution.

1
répondu dahu 2010-05-09 00:14:40

Ma suggestion:

function! UpdateWordCount()
  let b:word_count = eval(join(map(getline("1", "$"), "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)}'

Je ne sais pas comment cela se compare en vitesse à certaines des autres solutions, mais c'est certainement beaucoup plus simple que la plupart.

1
répondu godlygeek 2010-05-11 04:38:57

Je suis nouveau dans les scripts Vim, mais je pourrais suggérer

function WordCount()
    redir => l:status
    exe "silent normal g\<c-g>"
    redir END
    return str2nr(split(l:status)[11])
endfunction

Comme étant un peu plus propre car il n'écrase pas la ligne d'état existante.

Ma raison de poster est de souligner que cette fonction a un bug déroutant: à savoir, elle casse la commande append. Appuyer sur A devrait vous faire passer en mode insertion avec le curseur positionné à droite du caractère final de la ligne. Cependant, avec cette barre d'état personnalisée activée, il vous mettra à gauche de la finale caractère.

Quelqu'un a une idée de ce qui cause cela?

1
répondu jth 2012-01-07 22:07:26

Ceci est une amélioration de la version de Michael Dunn , mettant en cache le nombre de mots afin de réduire encore le traitement.

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 
1
répondu Felipe Morales 2017-05-23 12:32:20

En utilisant la méthode dans la réponse fournie par Steve Moyer, j'ai pu produire la solution suivante. C'est un hack plutôt inélégant que j'ai peur et je pense qu'il doit y avoir une solution plus propre, mais cela fonctionne, et est beaucoup plus rapide que de simplement compter tous les mots dans un tampon chaque fois que la ligne d'état est mise à jour. Je devrais également noter que cette solution est indépendante de la plate-forme et ne suppose pas qu'un système a 'wc' ou quelque chose de similaire.

Ma solution ne met pas à jour périodiquement le tampon, mais la réponse fournie par Mikael Jansson serait en mesure de fournir cette fonctionnalité. Je n'ai pas encore trouvé d'instance où ma solution devient désynchronisée. Cependant, je l'ai seulement testé brièvement comme un nombre de mots en direct précis n'est pas essentiel à mes besoins. Le modèle que j'utilise pour faire correspondre les mots est également simple et est destiné aux documents texte simples. Si quelqu'un a une meilleure idée pour un motif ou d'autres suggestions n'hésitez pas à poster une réponse ou de modifier cette post.

Ma solution:

"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("$")
        let data = data + getline(line(".")+1, "$") 
    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

C'est ensuite ajouté à la ligne d'état par:

:set statusline=wc:%{WordCount()}

J'espère que cela aidera ceux qui recherchent un nombre de mots en direct dans Vim. Mais celui qui n'est pas toujours exact. Alternativement, bien sûr, g ctrl-g vous fournira le nombre de mots de Vim!

0
répondu Greg Sexton 2008-09-23 11:30:28

Au cas où quelqu'un d'autre viendrait ici de Google, j'ai modifié la réponse D'Abslom Daak pour travailler avec Airline. J'ai enregistré ce qui suit comme

~/.vim/bundle/vim-airline/autoload/airline/extensions/pandoc.vim

Et ajouté

call airline#extensions#pandoc#init(s:ext)

À 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
0
répondu Dan Sheffler 2014-07-03 18:16:59