2008-09-22 55 views
31

我正在嘗試在vim狀態行中顯示活字數。我通過在我的.vimrc中設置狀態行並在其中插入一個函數來做到這一點。這個函數的想法是返回當前緩衝區中的字數。這個號碼然後顯示在狀態行上。這應該很好地工作,因爲狀態線在幾乎每一個可能的機會更新,所以計數將永遠保持'活'。Vim中的快速字數統計功能

問題是我目前定義的函數很慢,所以當vim顯示爲最小文件以外的所有文件時顯然很慢;由於這個功能被頻繁執行。

總之,有沒有人有一個聰明的技巧來產生一個函數,在計算當前緩衝區中的單詞數量並且返回結果方面非常快?

+0

你目前的功能? – 2008-09-22 11:59:33

+10

對於其他人來這裏的一般字數,使用`g Ctrl-g`。 – naught101 2014-05-19 05:27:05

回答

23

下面是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()} 
+0

s/s:// g;或者s/s:/ l:/ g,如果你真的想要一個明確的範圍。 – 2010-05-10 10:05:01

+4

FWIW,這在Ubuntu 12.04上不適用於Vim 7.3。我不能追加到線的末尾!也就是說,「A」和「a」(後者在一行中的最後一個字符時)不會將光標留在最後一個字符之外,而是直接放在最前面。我無法想象爲什麼。 – chreekat 2012-11-01 20:22:07

8

保留當前行的計數和緩衝區其餘部分的單獨計數。在當前行輸入(或刪除)單詞時,只更新該計數,但顯示當前行計數和其餘緩衝計數的總和。

當您更改線條時,將當前線條計數添加到緩衝區計數中,計算當前線條中的單詞並a)設置當前線條計數並b)從緩衝區計數中減去它。

定期重新計算緩衝區也是明智的做法(請注意,由於您知道發生編輯的位置,因此您不必一次對整個緩衝區進行計數)。

3

所以我寫了:

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

但它打印出的信息到狀態欄,所以我不認爲這將是適合你的使用情況。雖然它非常快!

4

每當您停止輸入一段時間(具體來說,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 

Enjoy!

0

在Steve Moyer提供的答案中使用該方法,我能夠生成以下解決方案。這是一種相當不雅的黑客恐懼,我覺得必須有一個更好的解決方案,但它的工作原理比每次狀態行更新時簡單計算緩衝區中的所有單詞要快得多。我還應該注意到,這個解決方案是獨立於平臺的,並不認爲系統具有'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的字數!

1

我把大部分的這種從編寫函數的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,而不是僅僅?),但我相信基本功能在那裏。

編輯:

在此尋找一遍,我真的很喜歡的Mikael Jansson的解決方案。我不喜歡擺脫wc(不便攜,也許很慢)。如果我們將UpdateWordCount函數替換爲上面的代碼(將函數重命名爲UpdateWordCount),那麼我認爲我們有更好的解決方案。

+0

局部變量應保持在本地。 s:變量是腳本局部全局變量,如C中的靜態變量。如果需要,可以使用+ =。你也可以做一個:foreach getline(1,'$'),但我不知道哪個解決方案更快。 – 2010-05-10 10:01:05

1

我的建議:

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

我不知道如何比較速度的一些其他的解決方案,但它肯定是比大多數簡單得多。

24

我真的很喜歡邁克爾鄧恩的回答上面,但我發現,當我編輯它導致我無法訪問最後一列。所以,我對於功能的微小的變化:

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

1

我是新來的Vim的腳本,但我可能建議

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

由於它不會覆蓋現有的狀態行,所以更清潔一點。

我的發帖理由是指出這個函數有一個令人費解的bug:即它打破了append命令。打到A應該讓你進入插入模式,光標位於該行最後一個字符的右側。但是,啓用此自定義狀態欄後,它會將您置於最後一個字符的左側。

任何人都有什麼想法是什麼原因造成的?

1

這是對Michael Dunn's version的改進,緩存字數,因此更少的處理是必需的。

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 
2

我對此採用了稍微不同的方法。而不是確保字數功能特別快,我只在光標停止移動時才調用它。這些命令將做到這一點:

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

提問者想也許並不完全符合,但比這裏的一些答案簡單得多,而且對我的使用情況不夠好(一瞥下來看看鍵入一個句子後字數或兩個)。

設置updatetime較小的值也有助於在這裏:​​

set updatetime=300 

沒有對這個詞的巨大的開銷輪詢計,因爲CursorHoldCursorHoldI只火一次當光標停止運動,並非每一個updatetime ms。

2

下面是對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\

0

萬一有人從谷歌到了這裏,我修改Abslom DAAK的回答與Airline工作。我救下的

~/.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