Функция быстрого подсчета слов в Vim

StackOverflow https://stackoverflow.com/questions/114431

  •  02-07-2019
  •  | 
  •  

Вопрос

Я пытаюсь отобразить количество слов в строке состояния vim.Я делаю это, устанавливая строку состояния в файле .vimrc и вставляя в нее функцию.Идея этой функции — вернуть количество слов в текущем буфере.Этот номер затем отображается в строке состояния.Это должно работать хорошо, поскольку строка состояния обновляется практически при каждой возможности, поэтому счетчик всегда будет оставаться «живым».

Проблема в том, что функция, которую я сейчас определил, работает медленно, и поэтому vim явно работает медленно, когда она используется для всех файлов, кроме самых маленьких;из-за того, что эта функция выполняется так часто.

Подводя итог, есть ли у кого-нибудь хитрый трюк, позволяющий создать функцию, которая невероятно быстро вычисляет количество слов в текущем буфере и возвращает результат?

Это было полезно?

Решение

Вот полезная версия идеи Родриго Кейро.Он не меняет строку состояния и восстанавливает переменную 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

Кажется, это достаточно быстро, чтобы включить его непосредственно в строку состояния, например:

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

Другие советы

Мне очень нравится ответ Майкла Данна выше, но я обнаружил, что во время редактирования я не мог получить доступ к последнему столбцу.Итак, у меня есть небольшое изменение для функции:

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

Я без проблем включил его в строку статуса:

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

Сохраняйте счетчик для текущей строки и отдельный счетчик для остальной части буфера.Когда вы вводите (или удаляете) слова в текущей строке, обновляйте только этот счетчик, но отображайте сумму текущего счетчика строк и остального счетчика буфера.

Когда вы меняете строки, добавьте текущий счетчик строк к счетчику буфера, подсчитайте слова в текущей строке и а) установите текущий счетчик строк и б) вычтите его из счетчика буфера.

Также было бы разумно периодически пересчитывать буфер (обратите внимание, что вам не обязательно пересчитывать весь буфер сразу, поскольку вы знаете, где происходит редактирование).

Это будет пересчитывать количество слов всякий раз, когда вы на некоторое время перестанете печатать (в частности, 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

Наслаждаться!

Итак, я написал:

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

Но он выводит информацию в строку состояния, поэтому я не думаю, что он подойдет для вашего случая использования.Зато это очень быстро!

Для этого я использовал немного другой подход.Вместо того, чтобы следить за тем, чтобы функция подсчета слов работала особенно быстро, я вызываю ее только тогда, когда курсор перестает двигаться.Эти команды сделают это:

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

Возможно, это не совсем то, что хотел задавший вопрос, но намного проще, чем некоторые ответы здесь, и достаточно хорошо для моего варианта использования (взгляните вниз, чтобы увидеть количество слов после ввода одного или двух предложений).

Параметр updatetime на меньшее значение также помогает здесь:

set updatetime=300

Не существует огромного опроса по количеству слов, потому что CursorHold и CursorHoldI только огонь один раз когда курсор перестает двигаться, не каждый updatetime РС.

Вот уточнение ответа Абслома Даака, которое также работает в визуальном режиме.

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]) конец
    let v:statusmsg = s:old_status
  end
  call setpos('.', position)
  return s:word_count 
endfunction

Включен в строку состояния, как и раньше.Вот строка состояния, выровненная по правому краю:

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

Большую часть этой информации я взял со страниц справки vim по написанию функций.

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

Конечно, как и остальным, вам необходимо:

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

Я уверен, что кто-нибудь может это исправить, чтобы сделать его более динамичным (s:n вместо просто n?), но я считаю, что базовая функциональность здесь присутствует.

Редактировать:

Глядя на это еще раз, мне очень нравится решение Микаэля Янссона.Я не люблю раскошелиться wc (не портативный и, возможно, медленный).Если мы заменим его UpdateWordCount функцию с кодом, который у меня есть выше (переименовав мою функцию в UpdateWordCount), то я думаю, у нас есть лучшее решение.

Мое предложение:

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

Я не уверен, насколько это можно сравнить по скорости с некоторыми другими решениями, но оно определенно намного проще, чем большинство других.

Я новичок в написании сценариев Vim, но могу предложить

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

как немного чище, поскольку он не перезаписывает существующую строку состояния.

Моя причина публикации — указать, что в этой функции есть загадочная ошибка:а именно, это нарушает команду добавления.Удар А должен перевести вас в режим вставки с курсором, расположенным справа от последнего символа в строке.Однако если эта настраиваемая строка состояния включена, вы поместитесь слева от последнего символа.

Кто-нибудь знает, что вызывает это?

Это улучшение Версия Майкла Данна, кэшируя количество слов, поэтому требуется еще меньше обработки.

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 

Используя метод из ответа Стива Мойера, я смог найти следующее решение.Боюсь, это довольно неэлегантный хак, и я чувствую, что должно быть более аккуратное решение, но оно работает и намного быстрее, чем просто подсчитывать все слова в буфере каждый раз, когда обновляется строка состояния.Я также должен отметить, что это решение не зависит от платформы и не предполагает, что в системе есть «wc» или что-то подобное.

Мое решение не обновляет буфер периодически, но ответ, предоставленный Микаэлем Янссоном, сможет обеспечить эту функциональность.Я пока не нашел случая, когда мое решение рассинхронизировалось.Однако я проверил это лишь вкратце, поскольку точное количество слов в реальном времени не является необходимым для моих нужд.Шаблон, который я использую для сопоставления слов, также прост и предназначен для простых текстовых документов.Если у кого-то есть лучшая идея для выкройки или какие-либо другие предложения, пожалуйста, оставьте ответ или отредактируйте этот пост.

Мое решение:

"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

Затем это добавляется в строку состояния:

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

Я надеюсь, что это поможет всем, кто ищет живой подсчет слов в Vim.Хоть и не всегда точный.Альтернативно, конечно, g ctrl-g предоставит вам количество слов в Vim!

На случай, если сюда придет кто-то еще из Google, я изменил ответ Абслома Даака для работы с Авиакомпания.Я сохранил следующее как

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

и добавил

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

к 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

Вариант Гая Гур-Ари. уточнение что

  • считает слова только в том случае, если включена проверка орфографии,
  • подсчитывает количество выбранных слов в визуальном режиме
  • сохраняет молчание вне режима вставки и обычного режима, а также
  • надеюсь, он более агностичен к системному языку (если он отличается от английского)
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

который можно добавить к строке состояния, скажем,

    set statusline+=%.10{StatuslineWordCount()}     " wordcount
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top