função de contagem rápida palavra no Vim
-
02-07-2019 - |
Pergunta
Eu estou tentando exibir uma contagem de palavras ao vivo na linha de status vim. I fazer isso definindo a minha linha de status no meu .vimrc e inserindo uma função para ele. A ideia desta função é retornar o número de palavras no buffer atual. Este número é então exibida na linha de status. Isso deve funcionar bem como a linha de status é atualizado em praticamente todas as oportunidades possíveis para que a contagem será sempre 'ao vivo'.
O problema é que a função que tenho atualmente definido é lento e por isso vim é obviamente lenta quando ele é usado para todos, mas os arquivos menores; devido a esta função que está sendo executada com tanta freqüência.
Em resumo, alguém tem um truque inteligente para a produção de uma função que é incrivelmente rápido a calcular o número de palavras no buffer atual e retornando o resultado?
Solução
Aqui está uma versão utilizável da idéia de Rodrigo Queiró. Isso não muda a barra de status, e restaura a variável 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
Este parece ser rápido o suficiente para incluir diretamente na linha de status, por exemplo:.
:set statusline=wc:%{WordCount()}
Outras dicas
Eu realmente gosto a resposta de Michael Dunn acima, mas eu achei que quando eu estava editando ela estava causando-me para ser incapaz de acessar a última coluna. Então, eu tenho uma pequena alteração para a função:
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
Eu incluí-lo na minha linha de status, sem quaisquer problemas:
:set statusline=wc:%{WordCount()}
Mantenha uma contagem para a linha atual e uma contagem separada para o resto do buffer. Conforme você digita (ou eliminar) as palavras na linha atual, atualização só que a contagem, mas exibir a soma da contagem da linha atual e o resto da contagem de buffer.
Quando você mudar de linha, adicione a contagem de linha atual para a contagem de buffer, contar as palavras na linha atual e a) definir a contagem de linha atual e b) subtrai-lo a partir da contagem de buffer.
Além disso, seria sensato para contar o buffer periodicamente (note que você não tem que contar toda a reserva de uma vez, desde que você sabe onde a edição está ocorrendo).
Isto irá recalcular o número de palavras, sempre que você parar de escrever por um tempo (especificamente, ms updatetime
).
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
Aproveite!
Então, eu escrevi:
func CountWords() exe "normal g\" let words = substitute(v:statusmsg, "^.*Word [^ ]* of ", "", "") let words = substitute(words, ";.*", "", "") return words endfunc
Mas ele imprime informações para a barra de status, então eu não acho que vai ser adequado para o seu caso de uso. É muito rápido, embora!
Eu usei uma abordagem ligeiramente diferente para isso. Ao invés de Verifique se a função contagem de palavras é especialmente rápido, eu só chamá-lo quando o cursor pára de se mover. Estes comandos irá fazê-lo:
:au CursorHold * exe "normal g\<c-g>"
:au CursorHoldI * exe "normal g\<c-g>"
Talvez não exatamente o que se pretendia, mas muito mais simples do que algumas das respostas aqui, e bom o suficiente para o meu caso de uso (olhar para baixo para ver contagem de palavras depois de digitar uma frase ou duas).
Configuração updatetime
para um valor menor também ajuda aqui:
set updatetime=300
Não há uma enorme polling sobrecarga para a contagem de palavras porque CursorHold
e CursorHoldI
fogo só uma vez quando o cursor pára de se mover, nem todo ms updatetime
.
Aqui é um refinamento da resposta de Abslom Daak que também funciona no modo visual.
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
incluído na linha de status, como antes. Aqui está uma linha de status alinhado à direita:
set statusline=%=%{WordCount()}\ words\
Eu levei a maior parte deste das páginas de ajuda vim em escrever funções.
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
Claro que, como os outros, você vai precisar:
:set statusline=wc:%{WordCount()}
Estou certo de que este pode ser limpo por alguém para torná-lo mais vimmy (s:? N em vez de apenas n)., Mas acredito que a funcionalidade básica está lá
Editar:
Olhando para este novo, eu realmente gosto solução de Mikael Jansson. Eu não gosto de gastar dinheiro com a wc
(não portátil e talvez lento). Se substituirmos a sua função UpdateWordCount
com o código que eu tenho acima (renomeando a minha função para UpdateWordCount
), então eu acho que nós temos uma solução melhor.
A minha sugestão:
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)}'
Eu não sei como isso se compara em velocidade para algumas das outras soluções, mas é certamente muito mais simples do que a maioria.
Eu sou novo para Vim scripting, mas eu poderia sugerir
function WordCount()
redir => l:status
exe "silent normal g\<c-g>"
redir END
return str2nr(split(l:status)[11])
endfunction
como sendo um pouco mais limpo, uma vez que não substitua a linha de estado existente.
Minha razão para postagem é de salientar que esta função tem um bug intrigante: a saber, ele quebra o comando de acréscimo. Bater A deve deixá-lo em modo de inserção com o cursor posicionado à direita do caractere final sobre a linha. No entanto, com esta barra de status personalizada permitiu que vai colocá-lo para a esquerda do caráter final.
Alguém tem alguma idéia o que causa isso?
Esta é uma melhoria sobre o Michael Dunn versão , cache a contagem de palavras por isso mesmo menos processamento é necessário.
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 o método na resposta dada por Steve Moyer eu era capaz de produzir a seguinte solução. É um truque bastante deselegante eu tenho medo e eu sinto que deve haver uma solução mais limpa, mas funciona, e é muito mais rápido do que a simples contagem das palavras em um buffer de cada vez que a linha de status é atualizado. Devo observar também que esta solução é independente de plataforma e não assume um sistema tem 'wc' ou algo similar.
A minha solução não atualiza periodicamente o buffer, mas a resposta fornecida por Mikael Jansson seria capaz de fornecer essa funcionalidade. Eu não tenho, até o momento, encontrou um caso em que a minha solução torna-se fora de sincronia. No entanto eu só testei isso brevemente como uma contagem de palavras ao vivo exata não é essencial para as minhas necessidades. O padrão que eu uso para palavras combinando também é simples e é destinado a documentos de texto simples. Se alguém tiver uma idéia melhor para um padrão ou alguma outra sugestão sinta-se livre para postar uma resposta ou editar este post.
A minha solução:
"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
Este é então adicionado à linha de status por:
:set statusline=wc:%{WordCount()}
Espero que isso ajude quem procura uma contagem de palavras ao vivo no vim. Embora um que nem sempre é exata. Alternativamente, é claro g ctrl-g irá fornecer-lhe com contagem de palavras do Vim!
Em caso de alguém está vindo aqui de Google, eu modifiquei a resposta de Abslom Daak ao trabalho com Airline . Salvei o seguinte como
~/.vim/bundle/vim-airline/autoload/airline/extensions/pandoc.vim
e acrescentou
call airline#extensions#pandoc#init(s:ext)
para 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
Uma variação de Guy Gur-Ari é refinamento que
- só conta palavras, se a verificação ortográfica estiver ativada,
- conta o número de palavras selecionadas no modo visual
- mantém fora mudo de inserção e modo normal, e
- espero é mais agnóstico para o idioma do sistema (quando diferente do Inglês)
function! StatuslineWordCount()
if !&l:spell
return ''
endif
if empty(getline(line('$')))
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
que pode ser anexado ao linha de status, digamos,
set statusline+=%.10{StatuslineWordCount()} " wordcount