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?

¿Fue útil?

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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top