Подтвердить что ты не робот

Быстрая функция подсчета слов в Vim

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

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

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

Ответ 3

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

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

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

Ответ 4

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

Наслаждайтесь!

Ответ 5

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

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

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

Ответ 6

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

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

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

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

set updatetime=300

Для подсчета слов нет большого опроса, потому что CursorHold и CursorHoldI срабатывают только один раз, когда курсор останавливается, а не каждый updatetime ms.

Ответ 7

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

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

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

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

Ответ 8

Я взял основную часть этого из страниц справки 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()}

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

Edit:

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

Ответ 9

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

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

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

Ответ 10

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

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

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

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

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

Ответ 11

Это улучшение версии Michael Dunn, кэширование подсчета слов, поэтому требуется еще меньше обработки.

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 

Ответ 12

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

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

Мое решение:

"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!

Ответ 13

В случае, если кто-то приезжает сюда из Google, я модифицировал ответ Abslom Daak для работы с Airline. Я сохранил следующее как

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

и добавил

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

to 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

Ответ 14

Вариант изысканности Гая Гур-Ари, который

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