2008-09-22 22 views
31

Estoy tratando de mostrar un recuento de palabras activas en la línea de estado de vim. Lo hago estableciendo mi línea de estado en mi .vimrc e insertando una función en ella. La idea de esta función es devolver el número de palabras en el búfer actual. Este número se muestra en la línea de estado. Esto debería funcionar bien ya que la línea de estado se actualiza en casi todas las oportunidades posibles, por lo que el recuento siempre permanecerá "en vivo".Función de conteo rápido de palabras en Vim

El problema es que la función que he definido actualmente es lenta y, por tanto, vim es obviamente lenta cuando se usa para todos los archivos, excepto para los más pequeños; debido a que esta función se ejecuta con tanta frecuencia.

En resumen, ¿alguien tiene un ingenioso truco para producir una función que es tremendamente rápida al calcular el número de palabras en el búfer en uso y devolver el resultado?

+0

¿Cuál es su función actual? –

+10

Para otros que vienen aquí para un conteo general de palabras, use 'g Ctrl-g'. – naught101

Respuesta

23

Aquí hay una versión utilizable de la idea de Rodrigo Queiro. No cambia la barra de estado, y restaura 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 

Esto parece ser lo suficientemente rápido como para incluir directamente en la línea de estado, por ejemplo .:

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

s/s: // g; o s/s:/l:/g si realmente quiere un alcance explícito. –

+4

FWIW, esto no funciona con Vim 7.3 en Ubuntu 12.04. No puedo agregar al final de la línea! Es decir, tanto "A" como "a" (este último mientras está en el último carácter de una línea) no dejan el cursor más allá del último carácter, sino directamente enfrente de él. No puedo imaginar por qué. – chreekat

8

Mantenga un recuento para la línea actual y un conteo separado para el resto del búfer. A medida que escribe (o elimina) palabras en la línea actual, actualice solo ese conteo, pero muestre la suma del conteo de línea actual y el resto del recuento de memoria intermedia.

Cuando cambia las líneas, agregue el recuento de línea actual al recuento de memoria intermedia, cuente las palabras en la línea actual y a) establezca el recuento de línea actual yb) restarlo del recuento de memoria intermedia.

También sería aconsejable volver a contar el búfer periódicamente (tenga en cuenta que no tiene que contar todo el búfer de una vez, ya que sabe dónde se está produciendo la edición).

3

Así que he escrito:

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

Pero imprime información de la barra de estado, así que no creo que sea adecuado para su caso de uso. ¡Es muy rápido, sin embargo!

4

Esto recalculará el número de palabras siempre que deje de escribir durante un tiempo (específicamente, 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!

0

Usando el método en la respuesta proporcionada por Steve Moyer, pude producir la siguiente solución. Es un truco bastante poco elegante, me temo, y creo que debe haber una solución más ordenada, pero funciona, y es mucho más rápido que simplemente contar todas las palabras en un buffer cada vez que se actualiza la línea de estado. También debo señalar que esta solución es independiente de la plataforma y no supone que un sistema tenga 'wc' o algo similar.

Mi solución no actualiza periódicamente el búfer, pero la respuesta proporcionada por Mikael Jansson podría proporcionar esta funcionalidad. Hasta el momento, no he encontrado una instancia en la que mi solución no esté sincronizada. Sin embargo, solo he probado esto brevemente, ya que un conteo exacto de palabras en vivo no es esencial para mis necesidades. El patrón que uso para emparejar palabras también es simple y está destinado a documentos de texto simples. Si alguien tiene una mejor idea para un patrón o cualquier otra sugerencia, no dude en publicar una respuesta o editar esta publicación.

Mi solución:

"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 

Esto se añade a la línea de estado por:

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

espero que esto ayude a cualquiera que busque un recuento de palabras en vivo en Vim. Aunque uno que no siempre es exacto.¡Alternativamente, por supuesto, g ctrl-g le proporcionará el recuento de palabras de Vim!

1

me llevó la mayor parte de esto desde las páginas de ayuda vim sobre la escritura de funciones.

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 

Por supuesto, al igual que los demás, tendrá que:

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

Estoy seguro de que esto puede ser limpiado por alguien para que sea más vimmy (s n en lugar de sólo n?), pero creo que la funcionalidad básica está ahí.

Editar:

En cuanto a esto de nuevo, me gusta mucho la solución de Mikael Jansson. No me gusta bombardear al wc (no portátil y quizás lento). Si reemplazamos su función UpdateWordCount con el código que tengo arriba (renombrando mi función al UpdateWordCount), entonces creo que tenemos una mejor solución.

+0

Las variables locales deben permanecer locales. s: las variables son variables globales de script local, como variables estáticas en C. Si lo desea, puede usar + =. También puede hacer a: foreach en getline (1, '$'), pero no sé qué solución es más rápida. –

1

Mi sugerencia:

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)}' 

No estoy seguro de cómo se compara la velocidad de algunas de las otras soluciones, pero es sin duda mucho más simple que la mayoría.

24

Me gustó mucho la respuesta de Michael Dunn, pero descubrí que cuando estaba editando me causaba la imposibilidad de acceder a la última columna. Así que tengo un pequeño cambio para la función:

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 

he incluido en mi línea de estado sin ningún problema:

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

+0

Por desgracia, esta debería ser la solución aceptada. :) – chreekat

+2

Hay un pequeño error tipográfico aquí. La línea debajo de la primera línea en negrita debería leer 'exe ': silenciosa normal g \ " 'en su lugar. –

+1

@JackFranklin No fue un error tipográfico. El OP había usado HTML codificado a mano en su Markdown (para producir negrita dentro de una sección de código), y el motor de renderizado de SO respondía mal. He eliminado el HTML para solucionar el problema. Gracias por notar que el código parecía estar equivocado. – Telemachus

1

Soy nuevo a Vim secuencias de comandos, sino que puede ser sugerir

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

como un poco más limpio, ya que no sobrescribe la línea de estado existente.

Mi razón de publicación es para señalar que esta función tiene un error desconcertante: es decir, rompe el comando de agregar. Al pulsar A, debe colocarlo en el modo de inserción con el cursor ubicado a la derecha del carácter final en la línea. Sin embargo, con esta barra de estado personalizada habilitada, te pondrá a la izquierda del personaje final.

¿Alguien tiene alguna idea de por qué?

1

Esto es una mejora en Michael Dunn's version, almacenando en caché el conteo de palabras por lo que se necesita menos procesamiento.

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 
2

He utilizado un enfoque ligeramente diferente para esto.En lugar de asegurarme de que la función de conteo de palabras sea especialmente rápida, solo la invoco cuando el cursor deja de moverse. Estos comandos harán:

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

Tal vez no era lo que se deseaba, pero mucho más simple que algunas de las respuestas aquí, y lo suficientemente bueno para mi caso de uso (mirar hacia abajo para ver el recuento de palabras después de escribir una oración o dos).

Configuración updatetime a un valor menor también ayuda aquí:

set updatetime=300 

No hay una enorme sobrecarga de votación para el recuento de palabras, porque CursorHoldCursorHoldI y sólo el fuego una vez cuando el cursor deja de moverse, no todas las updatetime ms.

+0

Probé muchas funciones en esta página pero encontré esta respuesta de 3 líneas para ser la única que funcionaba en mi Gvim (versión 8.0.550) en LInux. – rnso

2

Aquí hay un refinamiento de la respuesta de Abslom Daak que también funciona en 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 

Incluido en la línea de estado como antes. Aquí es una línea de estado alineado a la derecha:

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

0

En caso de que alguien va a venir de Google, he modificado la respuesta de Abslom Daak trabajar con Airline. Me salvó la siguiente como

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

y añadió

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

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 
Cuestiones relacionadas