Вопрос
Я пытаюсь отобразить количество слов в строке состояния 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