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?
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()}
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()}
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).
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!
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!
, 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.
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\
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.
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.
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?
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
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!
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