質問
vim ステータスラインにライブ単語数を表示しようとしています。これを行うには、.vimrc にステータス行を設定し、そこに関数を挿入します。この関数の目的は、現在のバッファ内のワード数を返すことです。この数値はステータス行に表示されます。ステータスラインはほぼあらゆる機会に更新されるため、カウントは常に「ライブ」のままになるため、これはうまく機能するはずです。
問題は、現在定義している関数が遅いため、最小のファイルを除くすべてのファイルに使用すると 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()}
他のヒント
上記の Michael Dunn の回答はとても気に入っていますが、編集中に最後の列にアクセスできなくなることがわかりました。したがって、関数に小さな変更を加えました。
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()}
現在の行のカウントと、バッファーの残りのカウントを別個に保持します。現在の行に単語を入力 (または削除) すると、その数のみが更新されますが、現在の行数と残りのバッファー数の合計が表示されます。
行を変更するときは、現在の行数をバッファー数に加算し、現在の行内の単語をカウントし、a) 現在の行数を設定し、b) それをバッファー数から減算します。
バッファを定期的に再カウントすることも賢明です (編集が行われている場所がわかっているため、バッファ全体を一度にカウントする必要はないことに注意してください)。
これにより、しばらく入力を停止するたびに単語数が再計算されます (具体的には、 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
楽しむ!
そこで私はこう書きました:
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>"
おそらく質問者が望んでいたものとはまったく異なりますが、ここでのいくつかの回答よりもはるかに単純で、私のユースケースには十分です(1つか2つの文を入力した後、下に目を向けて単語数を確認してください)。
設定 updatetime
値を小さくすることもここで役に立ちます。
set updatetime=300
単語数に関する大規模なオーバーヘッド ポーリングはありません。 CursorHold
そして CursorHoldI
火だけ 一度 カーソルの動きが止まるとき、すべてではありません updatetime
MS。
これは、ビジュアルモードでも機能する 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--'
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\
この大部分は、関数の作成に関する 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()}
誰かがこれをクリーンアップして、より活気のあるものにすることができると確信していますが(単なるnではなくs: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)}'
これが他のソリューションと速度の点でどのように比較できるかはわかりませんが、ほとんどのソリューションよりもはるかにシンプルであることは確かです。
私は 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
Steve Moyer が提供した回答の方法を使用すると、次の解決策を生成することができました。これは残念ながらかなり洗練されていないハックであり、もっときちんとした解決策があるに違いないと感じていますが、これは機能し、ステータス行が更新されるたびにバッファ内のすべての単語を単純にカウントするよりもはるかに高速です。このソリューションはプラットフォームに依存せず、システムに「トイレ」などの機能があることを前提としていないことにも注意してください。
私のソリューションはバッファを定期的に更新しませんが、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 の単語数が表示されます。
他の誰かがGoogleからここに来ている場合に備えて、Abslom Daakの回答を修正して機能するようにしました 航空会社. 。以下のように保存しました
~/.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