Vim中的快速单词计数功能

时间:2020-03-06 14:32:11  来源:igfitidea点击:

我试图在vim状态行中显示实时字数。我可以通过在.vimrc中设置状态行并向其中插入一个函数来做到这一点。此函数的想法是返回当前缓冲区中的单词数。然后,该号码将显示在状态行上。这应该很好地工作,因为状态线几乎在每个可能的机会上都会更新,因此计数始终保持"实时"。

问题是我当前定义的功能很慢,因此,除最小的文件外,所有其他文件都使用vim时,它的速度显然很慢。由于此功能执行如此频繁。

总而言之,有人会产生一个聪明的窍门来产生一个快速地计算当前缓冲区中的单词数并返回结果的函数吗?

解决方案

对当前行保留一个计数,对其余缓冲区保留一个单独的计数。当我们在当前行上键入(或者删除)单词时,仅更新该计数,但显示当前行计数与其余缓冲区计数的总和。

更改行时,将当前行计数添加到缓冲区计数,对当前行中的单词计数,然后a)设置当前行计数,b)从缓冲区计数中减去它。

定期重新计算缓冲区也是明智的(请注意,由于我们知道在哪里进行编辑,因此不必一次计算整个缓冲区的数量)。

所以我写了:

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

但是它将信息打印到状态栏,因此我认为它不适合用例。不过,速度非常快!

每当我们停止输入一段时间(特别是" 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

享受!

使用Steve Moyer提供的答案中的方法,我能够产生以下解决方案。恐怕这是一个不太雅致的hack,我觉得必须有一个更整洁的解决方案,但是它可以工作,并且比每次状态行更新时简单地计算缓冲区中的所有单词都快得多。我还应注意,此解决方案与平台无关,并且不假定系统具有" 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的字数统计!

这是Rodrigo Queiro想法的可用版本。它不会更改状态栏,并且会还原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()}

我从有关编写函数的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?),但是我相信这里有基本功能。

编辑:

再次看这个,我真的很喜欢Mikael Jansson的解决方案。我不喜欢炮轰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)}'

我不确定这与其他解决方案的速度相比如何,但是肯定比大多数解决方案简单得多。