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?

Foi útil?

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
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top