2008-09-22 9 views
31

Ich versuche, eine Live-Wortzahl in der vim Statuszeile anzuzeigen. Dazu setze ich meine Statuszeile in meine .vimrc und füge eine Funktion ein. Die Idee dieser Funktion ist es, die Anzahl der Wörter im aktuellen Puffer zurückzugeben. Diese Nummer wird dann in der Statuszeile angezeigt. Dies sollte gut funktionieren, da die Statuszeile bei jeder möglichen Gelegenheit aktualisiert wird, so dass die Zählung immer "live" bleibt.Schnelle Wortzählung Funktion in Vim

Das Problem ist, dass die Funktion, die ich derzeit definiert habe, langsam ist und so vim ist offensichtlich träge, wenn es für alle außer den kleinsten Dateien verwendet wird; aufgrund dieser Funktion wird so häufig ausgeführt.

Zusammenfassend, hat jemand einen cleveren Trick für die Erstellung einer Funktion, die blitzschnell die Anzahl der Wörter im aktuellen Puffer berechnet und das Ergebnis zurückgibt?

+0

Was ist Ihre aktuelle Funktion? –

+10

Für andere, die für eine allgemeine Wortzählung hierher kommen, benutzen Sie 'g Ctrl-g'. – naught101

Antwort

23

Hier ist eine brauchbare Version von Rodrigo Queiro's Idee. Die Statusleiste wird nicht geändert und die Variable statusmsg wird wiederhergestellt.

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 

Dies scheint schnell genug zu sein, direkt in der Statuszeile enthält, z.B .:

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

s/s: // g; oder s/s:/l:/g, wenn Sie wirklich einen expliziten Bereich wünschen. –

+4

FWIW, dies funktioniert nicht mit Vim 7.3 auf Ubuntu 12.04. Ich kann nicht an das Ende der Linie anhängen! Das heißt, sowohl "A" als auch "a" (letzteres während des letzten Zeichens in einer Zeile) lassen den Cursor nicht über das letzte Zeichen hinaus, sondern direkt davor. Ich kann mir nicht vorstellen warum. – chreekat

8

Behalten Sie eine Zählung für die aktuelle Zeile und eine separate Zählung für den Rest des Puffers bei. Wenn Sie Wörter in der aktuellen Zeile eingeben (oder löschen), aktualisieren Sie nur diese Anzahl, aber zeigen Sie die Summe der aktuellen Zeilenanzahl und den Rest der Pufferanzahl an.

Wenn Sie Zeilen ändern, fügen Sie die aktuelle Zeilenanzahl zur Pufferanzahl hinzu, zählen Sie die Wörter in der aktuellen Zeile und a) setzen Sie die aktuelle Zeilenanzahl und b) subtrahieren Sie sie von der Pufferanzahl.

Es wäre auch ratsam, den Puffer regelmäßig neu zu zählen (beachten Sie, dass Sie nicht den gesamten Puffer auf einmal zählen müssen, da Sie wissen, wo die Bearbeitung stattfindet).

3

Also habe ich geschrieben:

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

Aber es druckt Info der Statusbar aus, so dass ich glaube nicht, dass es für Ihren Anwendungsfall geeignet ist. Es ist sehr schnell!

4

Dadurch wird die Anzahl der Wörter immer dann neu berechnet, wenn Sie für eine Weile aufhören zu tippen (speziell 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 

Viel Spaß!

0

Mit der Methode in der Antwort von Steve Moyer konnte ich die folgende Lösung herstellen. Es ist ein ziemlich uneleganter Hack, fürchte ich, und ich glaube, dass es eine bessere Lösung geben muss, aber es funktioniert und ist viel schneller als einfach alle Wörter in einem Puffer zu zählen, jedes Mal wenn die Statuszeile aktualisiert wird. Ich sollte auch beachten, dass diese Lösung plattformunabhängig ist und nicht davon ausgeht, dass ein System "wc" oder etwas Ähnliches hat.

Meine Lösung aktualisiert den Puffer nicht regelmäßig, aber die Antwort von Mikael Jansson könnte diese Funktionalität bereitstellen. Ich habe noch keine Instanz gefunden, bei der meine Lösung nicht mehr synchron ist. Ich habe dies jedoch nur kurz getestet, da eine genaue Anzahl von Live-Wörtern für meine Bedürfnisse nicht wesentlich ist. Das Muster, das ich für übereinstimmende Wörter verwende, ist ebenfalls einfach und für einfache Textdokumente gedacht. Wenn jemand eine bessere Idee für ein Muster oder andere Vorschläge hat, bitte zögern Sie nicht, eine Antwort zu posten oder diesen Beitrag zu bearbeiten.

Meine Lösung:

"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 

Dies wird dann in der Statuszeile hinzugefügt von:

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

Ich hoffe, das jemand für eine Live-Wortanzahl in Vim sucht hilft. Wenn auch nicht immer genau.Alternativ bietet Ihnen g ctrl-g natürlich auch die Wortanzahl von Vim!

1

nahm ich den Großteil davon aus den vim Hilfeseite zum Schreiben von Funktionen.

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 

Natürlich, wie die anderen, müssen Sie zu:

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

Ich bin sicher, dass diese von jemandem gereinigt werden kann, um es mehr vimmy zu machen (s: n statt nur n?), aber ich glaube die Grundfunktionalität ist da.

Edit:

wieder in diesem Blick, ich mag Lösung Mikael Jansson wirklich. Ich mag es nicht, auf wc zu schälen (nicht tragbar und vielleicht langsam). Wenn wir seine UpdateWordCount Funktion durch den Code ersetzen, den ich oben habe (meine Funktion zu UpdateWordCount umbenennend), dann denke ich, dass wir eine bessere Lösung haben.

+0

Lokale Variablen sollten lokal bleiben. s: Variablen sind script-lokale globale Variablen, wie statische Variablen in C. Wenn Sie möchten, können Sie + = verwenden. Sie können auch a: foreach auf getline (1, '$'), aber ich weiß nicht, welche Lösung schneller ist. –

1

Mein Vorschlag:

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

Ich bin nicht sicher, wie Sie dies einige der anderen Lösungen in der Geschwindigkeit vergleicht, aber es ist sicherlich viel einfacher als die meisten.

24

Ich mag Michael Dunns Antwort oben sehr, aber ich habe festgestellt, dass ich beim Bearbeiten nicht auf die letzte Spalte zugreifen konnte. Also ich habe eine kleine Änderung für die Funktion:

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 

Ich habe es in meiner Statuszeile enthalten ohne Probleme:

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

+0

Leider sollte dies die akzeptierte Lösung sein. :) – chreekat

+2

Hier ist ein kleiner Tippfehler. Die Zeile unter der ersten fettgedruckten Zeile sollte "exe" lauten: silent normal g \ "' stattdessen. –

+1

@JackFranklin Es war kein Tippfehler. Der OP hatte handcodierten HTML-Code in seinem Markdown verwendet (um in einem Codeabschnitt fett darzustellen), und die Rendering-Engine von SO reagierte schlecht. Ich habe den HTML-Code entfernt, um das Problem zu beheben. Danke, dass du bemerkt hast, dass der Code falsch ist. – Telemachus

1

Ich bin neu in Vim Scripting, aber ich könnte vorschlagen

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

als etwas sauberer, da es die vorhandene Statuszeile nicht überschreibt.

Mein Grund für die Veröffentlichung ist, darauf hinzuweisen, dass diese Funktion einen verwirrenden Fehler hat: nämlich, es bricht den Append-Befehl. Schlagen Ein sollte Sie in den Einfügemodus mit dem Cursor rechts neben dem endgültigen Zeichen auf der Linie platziert. Wenn diese benutzerdefinierte Statusleiste aktiviert ist, wird sie jedoch links vom letzten Zeichen angezeigt.

Wer hat eine Idee, was das verursacht?

1

Dies ist eine Verbesserung gegenüber Michael Dunn's version, Caching der Wortzahl, so dass noch weniger Verarbeitung erforderlich ist.

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

Ich habe einen etwas anderen Ansatz dafür verwendet.Anstatt sicherzustellen, dass die Wortzählfunktion besonders schnell ist, rufe ich sie nur dann auf, wenn sich der Cursor nicht mehr bewegt. Diese Befehle werden es tun:

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

Vielleicht nicht ganz, was die Fragesteller wollten, aber viel einfacher als einige der Antworten hier, und gut genug für meinen Anwendungsfall (Blick nach unten Wortanzahl zu sehen, nachdem einen Satzes eingeben oder zwei).

Einstellung updatetime auf einen kleineren Wert hilft auch hier:

set updatetime=300 

Es gibt nicht eine große Overhead-Abfrage für das Wort ist zählen, weil CursorHold und CursorHoldI nur Feuer einmal, wenn die Cursor nicht mehr bewegen, nicht jeder updatetime ms.

+0

Ich habe viele Funktionen auf dieser Seite ausprobiert, fand aber, dass diese 3-Zeilen-Antwort die einzige in meinem Gvim (Version 8.0.550) auf LInux ist. – rnso

2

Hier ist eine Verfeinerung von Abslom Daaks Antwort, die auch im visuellen Modus funktioniert.

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 

In der Statuszeile wie zuvor enthalten. Hier ist eine rechtsbündig Statuszeile:

set statusline=%=%{WordCount()}\ words\

0

Falls jemand anderes kommt hier von Google, modifizierte ich Abslom Daak Antwort mit Airline zu arbeiten. Ich speicherte das folgende als

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

und hinzugefügt

call airline#extensions#pandoc#init(s:ext)

zu 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