Función de conteo rápido de palabras en Vim
-
02-07-2019 - |
Pregunta
Estoy intentando mostrar un recuento de palabras en vivo en la línea de estado de vim.Hago esto configurando 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 luego 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á "activo".
El problema es que la función que he definido actualmente es lenta y, por lo tanto, vim es obviamente lento cuando se usa para todos los archivos excepto los más pequeños;debido a que esta función se ejecuta con tanta frecuencia.
En resumen, ¿alguien tiene un truco inteligente para producir una función que sea sorprendentemente rápida para calcular el número de palabras en el búfer actual y devolver el resultado?
Solución
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 incluirlo directamente en la línea de estado, por ejemplo:
:set statusline=wc:%{WordCount()}
Otros consejos
Realmente me gusta la respuesta anterior de Michael Dunn, pero descubrí que cuando estaba editando no podía acceder a la última columna.Entonces tengo un cambio menor 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
Lo incluí en mi línea de estado sin ningún problema:
:set statusline=wc:%{WordCount()}
Mantenga un recuento para la línea actual y un recuento separado para el resto del búfer.A medida que escribe (o elimina) palabras en la línea actual, actualice solo ese recuento, pero muestre la suma del recuento de líneas actual y el resto del recuento del búfer.
Cuando cambie de línea, agregue el recuento de líneas actual al recuento del búfer, cuente las palabras en la línea actual y a) establezca el recuento de líneas actual y b) réstelo del recuento del búfer.
También sería aconsejable volver a contar el búfer periódicamente (tenga en cuenta que no es necesario contar todo el búfer a la vez, ya que sabe dónde se está produciendo la edición).
Esto volverá a calcular la cantidad de palabras cada vez que dejes de escribir por un tiempo (específicamente, updatetime
EM).
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
¡Disfrutar!
Entonces 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 en la barra de estado, por lo que no creo que sea adecuado para su caso de uso.¡Aunque es muy rápido!
Utilicé un enfoque ligeramente diferente para esto.En lugar de asegurarme de que la función de recuento de palabras sea especialmente rápida, sólo la llamo cuando el cursor deja de moverse.Estos comandos lo harán:
:au CursorHold * exe "normal g\<c-g>"
:au CursorHoldI * exe "normal g\<c-g>"
Quizás no sea exactamente lo que quería el autor de la pregunta, pero es mucho más simple que algunas de las respuestas aquí y lo suficientemente buena para mi caso de uso (mira 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 gran encuesta general para el recuento de palabras porque CursorHold
y CursorHoldI
solo fuego una vez cuando el cursor deja de moverse, no todos updatetime
EM.
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--'
Si stat = ~ "^seleccionado" Sea s: word_count = str2nr (dividido (v: statusmsg) [5]) de lo contrario S: word_count = str2nr (split (v: statusmsg) [11]) final
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í hay una línea de estado alineada a la derecha:
set statusline=%=%{WordCount()}\ words\
Tomé la mayor parte de esto de las páginas de ayuda de vim sobre funciones de escritura.
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, necesitarás:
:set statusline=wc:%{WordCount()}
Estoy seguro de que alguien puede limpiar esto para hacerlo más potente (s:n en lugar de solo n?), pero creo que la funcionalidad básica está ahí.
Editar:
Viendo esto de nuevo, me gusta mucho la solución de Mikael Jansson.No me gusta desembolsar wc
(no portátil y quizás lento).Si reemplazamos su UpdateWordCount
funcionar con el código que tengo arriba (cambiando el nombre de mi función a UpdateWordCount
), entonces creo que tenemos una mejor solución.
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 esta velocidad con algunas de las otras soluciones, pero ciertamente es mucho más simple que la mayoría.
Soy nuevo en las secuencias de comandos de Vim, pero podría 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.
El motivo de mi publicación es señalar que esta función tiene un error desconcertante:es decir, rompe el comando de agregar.Golpear A Debería llevarlo al modo de inserción con el cursor colocado a la derecha del carácter final de la línea.Sin embargo, con esta barra de estado personalizada habilitada, te colocará a la izquierda del carácter final.
¿Alguien tiene alguna idea de qué causa esto?
Esta es una mejora en La versión de Michael Dunn., almacenando en caché el recuento de palabras, por lo que se necesita incluso 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
Utilizando el método de la respuesta proporcionada por Steve Moyer, pude producir la siguiente solución.Me temo que es un truco bastante poco elegante y creo que debe haber una solución mejor, pero funciona y es mucho más rápido que simplemente contar todas las palabras en un búfer cada vez que se actualiza la línea de estado.Debo señalar también 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 un caso en el que mi solución no esté sincronizada.Sin embargo, sólo lo he probado brevemente ya que un recuento preciso de palabras en vivo no es esencial para mis necesidades.El patrón que utilizo para unir palabras también es simple y está destinado a documentos de texto simples.Si alguien tiene una idea mejor para un patrón o alguna 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
Luego se agrega a la línea de estado mediante:
:set statusline=wc:%{WordCount()}
Espero que esto ayude a cualquiera que busque un recuento de palabras en vivo en Vim.Aunque no siempre es exacto.Alternativamente, por supuesto, g ctrl-g le proporcionará el recuento de palabras de Vim.
En caso de que alguien más venga aquí desde Google, modifiqué la respuesta de Abslom Daak para que funcione Aerolínea.Guardé lo siguiente como
~/.vim/bundle/vim-airline/autoload/airline/extensions/pandoc.vim
y agregado
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
Una variación de Guy Gur-Ari refinamiento eso
- solo cuenta palabras si la revisión ortográfica está habilitada,
- cuenta el número de palabras seleccionadas en modo visual
- mantiene el silencio fuera del modo de inserción y normal, y
- Con suerte, es más independiente del idioma del sistema (cuando es diferente del 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 se puede agregar a la línea de estado, por ejemplo,
set statusline+=%.10{StatuslineWordCount()} " wordcount