Fast word count function in Vim

后端 未结 16 2031
后悔当初
后悔当初 2020-12-08 00:27

I am trying to display a live word count in the vim statusline. I do this by setting my status line in my .vimrc and inserting a function into it. The idea of this function

相关标签:
16条回答
  • 2020-12-08 01:06

    So I've written:

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

    But it prints out info to the statusbar, so I don't think it will be suitable for your use-case. It's very fast, though!

    0 讨论(0)
  • 2020-12-08 01:06

    My suggestion:

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

    I'm not sure how this compares in speed to some of the other solutions, but it's certainly a lot simpler than most.

    0 讨论(0)
  • 2020-12-08 01:09

    In case someone else is coming here from Google, I modified Abslom Daak's answer to work with Airline. I saved the following as

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

    and added

    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
    
    0 讨论(0)
  • 2020-12-08 01:10

    This is an improvement on Michael Dunn's version, caching the word count so even less processing is needed.

    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 
    
    0 讨论(0)
  • 2020-12-08 01:11

    I took the bulk of this from the vim help pages on writing functions.

    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
    

    Of course, like the others, you'll need to:

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

    I'm sure this can be cleaned up by somebody to make it more vimmy (s:n instead of just n?), but I believe the basic functionality is there.

    Edit:

    Looking at this again, I really like Mikael Jansson's solution. I don't like shelling out to wc (not portable and perhaps slow). If we replace his UpdateWordCount function with the code I have above (renaming my function to UpdateWordCount), then I think we have a better solution.

    0 讨论(0)
  • 2020-12-08 01:14

    Using the method in the answer provided by Steve Moyer I was able to produce the following solution. It is a rather inelegant hack I'm afraid and I feel that there must be a neater solution, but it works, and is much faster than simply counting all of the words in a buffer every time the status line is updated. I should note also that this solution is platform independent and does not assume a system has 'wc' or something similar.

    My solution does not periodically update the buffer, but the answer provided by Mikael Jansson would be able to provide this functionality. I have not, as of yet, found an instance where my solution becomes out of sync. However I have only tested this briefly as an accurate live word count is not essential to my needs. The pattern I use for matching words is also simple and is intended for simple text documents. If anyone has a better idea for a pattern or any other suggestions please feel free to post an answer or edit this post.

    My solution:

    "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
    

    This is then added to the status line by:

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

    I hope this helps anyone looking for a live word count in Vim. Albeit one that isn't always exact. Alternatively of course g ctrl-g will provide you with Vim's word count!

    0 讨论(0)
提交回复
热议问题