Related
I am looking for a way to map NERDTree-<CR> on some condition.
If current window is empty, NERDTree-<CR> is mapped to NERDTree-o. If it is not, mapped to NERDTree-t.
I am a beginner of vimscript, so it is hard to find how to do it.
I tried to make a function like below and the function to be called on 'NERDTreeCustomArgsOpen', but couldn't make it.
" Couldn't make 'IsBufferModifiable' function
" and mapping 'MyNERDTree_CR' to 'NERDTreeCustomOpenArgs'
function! IsBufferModifiable(numBuffer)
function! IsBufferEmpty(numBuffer)
return bufname(a:numBuffer) == ''
endfunction
function! IsModifiableBufferEmpty()
let emptyModifiableBuffers = filter(range(1, bufnr('$')), 'IsBufferModifiable(v:val) && IsBufferEmpty(v:val)')
if len(emptyModifiableBuffers) == 0
return 1
else
return 0
endfunction
function! MyNERDTree_CR()
if IsModifiableBufferEmpty()
call feedkeys('o')
else
call feedkeys('t')
endfunction
" Not sure it works or not
let NERDTreeCustomOpenArgs = {'file':{'where':'MyNERDTree_CR', 'reuse':'all', 'keepopen':1, 'stay':1}}
Is there any way to make them work?
Checking if a given buffer is modifiable is usually done by checking the value of the buffer-local :help 'modifiable' option:
function! IsBufferModifiable(numBuffer)
return getbufvar(a:numBuffer, '&modifiable')
endfunction
See :help getbufvar().
I've searched hard for this and haven't been able to find what I'm after.
On my statusline I want a count of the number of matches that occur in the current file. The vim command below returns what I want. I need the returned number to be displayed in my statusline.
:%s/^I^I//n
vim returns: 16 matches on 16 lines
FYI Explanation: I'm working in a CSV file. I'm searching for two tab characters ( ^I^I ) because that indicates lines I still need to do work on. So my desired statusline would indicate how much work remains in the current file.
I don't know how to enter a vim command on the statusline, I know that %{} can be used to run a function but how do I run the vim search command? I've tried variations of the following, but they clearly aren't right and just end up with an error.
:set statusline+= %{s/^I^I//n}
Help me vimy one kenobi, you're my only hope!
The first thing to mention here is that for large files this feature would be completely impractical. The reason is that the status line is redrawn after every cursor movement, after the completion of every command, and probably following other events that I am not even aware of. Performing a regex search on the entire buffer, and furthermore, not just the current buffer, but every visible window (since every window has its own status line), would slow things down significantly. Don't get me wrong; the idea behind this feature is a good one, as it would give you an immediate and fully automated indication of your remaining work, but computers are simply not infinitely performant (unfortunately), and so this could easily become a problem. I've edited files with millions of lines of text, and a single regex search can take many seconds on such buffers.
But provided your files will remain fairly small, I've figured out three possible solutions by which you can achieve this.
Solution #1: exe :s and redirect output
You can use :exe from a function to run the :s command with a parameterized pattern, and :redir to redirect the output into a local variable.
Unfortunately, this has two undesirable side effects, which, in the context of this feature, would be complete deal-breakers, since they would occur every time the status line is redrawn:
The cursor is moved to the start of the current line. (Personal note: I've never understood why vim does this, whether you're running :s from a status line call or by manually typing it out on the vim command-line.)
The visual selection, if any, is lost.
(And there actually might be more adverse effects that I'm not aware of.)
The cursor issue can be fixed by saving and restoring the cursor position via getcurpos() and setpos(). Note that it must be getcurpos() and not getpos() because the latter does not return the curswant field, which is necessary for preserving the column that the cursor "wants" to reside at, which may be different from the column the cursor is "actually" at (e.g. if the cursor was moved into a shorter line). Unfortunately, getcurpos() is a fairly recent addition to vim, namely 7.4.313, and based on my testing doesn't even seem to work correctly. Fortunately, there are the older winsaveview() and winrestview() functions which can accomplish the task perfectly and compatibly. So for now, we'll use those.
Solution #1a: Restore visual selection with gv
The visual selection issue I thought could be solved by running gv in normal mode, but for some reason the visual selection gets completely corrupted when doing this. I've tested this on Cygwin CLI and Windows gvim, and I don't have a solution for this (with respect to restoring the visual selection).
In any case, here's the result of the above design:
fun! MatchCount(pat,...)
"" return the number of matches for pat in the active buffer, by executing an :s call and redirecting the output to a local variable
"" saves and restores both the cursor position and the visual selection, which are clobbered by the :s call, although the latter restoration doesn't work very well for some reason as of vim-7.4.729
"" supports global matching (/g flag) by taking an optional second argument appended to :s flags
if (a:0 > 1)| throw 'too many arguments'| endif
let flags = a:0 == 1 ? a:000[0] : ''
let mode = mode()
let pos = winsaveview()
redir => output| sil exe '%s/'.a:pat.'//ne'.flags| redir END
call winrestview(pos)
if (mode == 'v' || mode == 'V' || mode == nr2char(22))
exe 'norm!gv'
endif
if (match(output,'Pattern not found') != -1)
return 0
else
return str2nr(substitute(output,'^[\s\n]*\(\d\+\).*','\1',''))
endif
return
endfun
set statusline+=\ [%{MatchCount('\\t\\t')}]
A few random notes:
The use of ^[\s\n]* in the match-count extraction pattern was necessary to barrel through the leading line break that gets captured during the redirection (not sure why that happens). An alternative would be to skip over any character up to the first digit with a non-greedy multiplier on the dot atom, i.e. ^.\{-}.
The doubling of the backslashes in the statusline option value is necessary because backslash interpolation/removal occurs during parsing of the option value itself. In general, single-quoted strings do not cause backslash interpolation/removal, and our pat string, once parsed, eventually gets concatenated directly with the :s string passed to :exe, thus there's no backslash interpolation/removal at those points (at least not prior to the evaluation of the :s command, when backslash interpolation of our backslashes does occur, which is what we want). I find this to be slightly confusing, since inside the %{} construct you'd expect it to be a normal unadulterated VimScript expression, but that's the way it works.
I added the /e flag for the :s command. This is necessary to handle the case of a buffer with zero matches. Normally, :s actually throws an error if there are zero matches. For a status line call, this is a big problem, because any error thrown while attempting to redraw the status line causes vim to nullify the statusline option as a defensive measure to prevent repeated errors. I originally looked for solutions that involved catching the error, such as :try and :catch, but nothing worked; once an error is thrown, a flag is set in the vim source (called_emsg) that we can't unset, and so the statusline is doomed at that point. Fortunately, I discovered the /e flag, which prevents an error from being thrown at all.
Solution #1b: Dodge visual mode with a buffer-local cache
I wasn't satisfied with the visual selection issue, so I wrote an alternative solution. This solution actually avoids running the search at all if visual mode is in effect, and instead pulls the last-known search count from a buffer-local cache. I'm pretty sure this will never cause the search count to become out-of-date, because it is impossible to edit the buffer without abandoning visual mode (I'm pretty sure...).
So now the MatchCount() function does not mess with visual mode:
fun! MatchCount(pat,...)
if (a:0 > 1)| throw 'too many arguments'| endif
let flags = a:0 == 1 ? a:000[0] : ''
let pos = winsaveview()
redir => output| sil exe '%s/'.a:pat.'//ne'.flags| redir END
call winrestview(pos)
if (match(output,'Pattern not found') != -1)
return 0
else
return str2nr(substitute(output,'^[\s\n]*\(\d\+\).*','\1',''))
endif
return
endfun
And now we need this helper "predicate" function which tells us when it's (not) safe to run the :s command:
fun! IsVisualMode(mode)
return a:mode == 'v' || a:mode == 'V' || a:mode == nr2char(22)
endfun
And now we need a caching layer that branches on the predicate result and only runs the primary function if safe, otherwise it pulls from the buffer-local cache the last-known return value that was captured from the most recent call of the primary function taking those exact arguments:
fun! BufferCallCache(buf,callName,callArgs,callElseCache)
let callCache = getbufvar(a:buf,'callCache')
if (type(callCache) != type({}))
unlet callCache
let callCache = {}
call UnletBufVar(a:buf,'callCache')
call setbufvar(a:buf,'callCache',callCache)
endif
if (a:callElseCache)
let newValue = call(a:callName,a:callArgs)
if (!has_key(callCache,a:callName.':Args') || !has_key(callCache,a:callName.':Value'))
let callCache[a:callName.':Args'] = []
let callCache[a:callName.':Value'] = []
endif
let i = len(callCache[a:callName.':Args'])-1
while (i >= 0)
let args = callCache[a:callName.':Args'][i]
if (args == a:callArgs)
let callCache[a:callName.':Value'][i] = newValue
return newValue
endif
let i -= 1
endwhile
let callCache[a:callName.':Args'] += [a:callArgs]
let callCache[a:callName.':Value'] += [newValue]
return newValue
else
if (has_key(callCache,a:callName.':Args') && has_key(callCache,a:callName.':Value'))
let i = len(callCache[a:callName.':Args'])-1
while (i >= 0)
let args = callCache[a:callName.':Args'][i]
if (args == a:callArgs)
return callCache[a:callName.':Value'][i]
endif
let i -= 1
endwhile
endif
return ''
endif
endfun
For which we need this helper function which I found somewhere on the Internet years ago:
fun! UnletBufVar(bufExpr, varName )
"" source: <http://vim.1045645.n5.nabble.com/unlet-ing-variables-in-buffers-td5714912.html>
call filter(getbufvar(a:bufExpr,''), 'v:key != '''.a:varName.'''' )
endfun
And finally this is how we can set the statusline:
set statusline+=\ [%{BufferCallCache('','MatchCount',['\\t\\t'],!IsVisualMode(mode()))}]
Solution #2: Call match() on every line
I've thought of another possible solution which is actually much simpler, and seems to perform just fine for non-huge files, even though it involves more looping and processing at the VimScript level. This is to loop over every line in the file and call match() on it:
fun! MatchCount(pat)
"" return the number of matches for pat in the active buffer, by iterating over all lines and calling match() on them
"" does not support global matching (normally achieved with the /g flag on :s)
let i = line('$')
let c = 0
while (i >= 1)
let c += match(getline(i),a:pat) != -1
let i -= 1
endwhile
return c
endfun
set statusline+=\ [%{MatchCount('\\t\\t')}]
Solution #3: Call search()/searchpos() repeatedly
I've written some slightly intricate functions to perform global and linewise matching, built around searchpos() and search(), respectively. I've included support for optional start and end bounds as well.
fun! GlobalMatchCount(pat,...)
"" searches for pattern matches in the active buffer, with optional start and end [line,col] specifications
"" useful command-line for testing against last-used pattern within last-used visual selection: echo GlobalMatchCount(#/,getpos("'<")[1:2],getpos("'>")[1:2])
if (a:0 > 2)| echoerr 'too many arguments for function: GlobalMatchCount()'| return| endif
let start = a:0 >= 1 ? a:000[0] : [1,1]
let end = a:0 >= 2 ? a:000[1] : [line('$'),2147483647]
"" validate args
if (type(start) != type([]) || len(start) != 2 || type(start[0]) != type(0) || type(start[1]) != type(0))| echoerr 'invalid type of argument: start'| return| endif
if (type(end) != type([]) || len(end) != 2 || type(end[0]) != type(0) || type(end[1]) != type(0))| echoerr 'invalid type of argument: end'| return| endif
if (end[0] < start[0] || end[0] == start[0] && end[1] < start[1])| echoerr 'invalid arguments: end < start'| return| endif
"" allow degenerate case of end == start; just return zero immediately
if (end == start)| return [0,0]| endif
"" save current cursor position
let wsv = winsaveview()
"" set cursor position to start (defaults to start-of-buffer)
call setpos('.',[0,start[0],start[1],0])
"" accumulate match count and line count in local vars
let matchCount = 0
let lineCount = 0
"" also must keep track of the last line number in which we found a match for lineCount
let lastMatchLine = 0
"" add one if a match exists right at start; must treat this case specially because the main loop must avoid matching at the cursor position
if (searchpos(a:pat,'cn',start[0])[1] == start[1])
let matchCount += 1
let lineCount += 1
let lastMatchLine = 1
endif
"" keep searching until we hit end-of-buffer
let ret = searchpos(a:pat,'W')
while (ret[0] != 0)
"" break if the cursor is now at or past end; must do this prior to incrementing for most recent match, because if the match start is at or past end, it's not a valid match for the caller
if (ret[0] > end[0] || ret[0] == end[0] && ret[1] >= end[1])
break
endif
let matchCount += 1
if (ret[0] != lastMatchLine)
let lineCount += 1
let lastMatchLine = ret[0]
endif
let ret = searchpos(a:pat,'W')
endwhile
"" restore original cursor position
call winrestview(wsv)
"" return result
return [matchCount,lineCount]
endfun
fun! LineMatchCount(pat,...)
"" searches for pattern matches in the active buffer, with optional start and end line number specifications
"" useful command-line for testing against last-used pattern within last-used visual selection: echo LineMatchCount(#/,getpos("'<")[1],getpos("'>")[1])
if (a:0 > 2)| echoerr 'too many arguments for function: LineMatchCount()'| return| endif
let start = a:0 >= 1 ? a:000[0] : 1
let end = a:0 >= 2 ? a:000[1] : line('$')
"" validate args
if (type(start) != type(0))| echoerr 'invalid type of argument: start'| return| endif
if (type(end) != type(0))| echoerr 'invalid type of argument: end'| return| endif
if (end < start)| echoerr 'invalid arguments: end < start'| return| endif
"" save current cursor position
let wsv = winsaveview()
"" set cursor position to start (defaults to start-of-buffer)
call setpos('.',[0,start,1,0])
"" accumulate line count in local var
let lineCount = 0
"" keep searching until we hit end-of-buffer
let ret = search(a:pat,'cW')
while (ret != 0)
"" break if the latest match was past end; must do this prior to incrementing lineCount for it, because if the match start is past end, it's not a valid match for the caller
if (ret > end)
break
endif
let lineCount += 1
"" always move the cursor to the start of the line following the latest match; also, break if we're already at end; otherwise next search would be unnecessary, and could get stuck in an infinite loop if end == line('$')
if (ret == end)
break
endif
call setpos('.',[0,ret+1,1,0])
let ret = search(a:pat,'cW')
endwhile
"" restore original cursor position
call winrestview(wsv)
"" return result
return lineCount
endfun
Maybe not exactly what you are looking for, but if you put a function like the following in your $HOME/.vimrc file you could do:
:set statusline+=%!SearchResults('^I^I')
$HOME/.vimrc
function SearchResults(q)
redir => matches
silent! execute "%s/".a:q."//n"
redir END
return substitute(matches, "^.", "", "")
endfunction
If nothing else, maybe that will get you a little closer.
I edit a large C, C++, or Java file, say, about 15000 lines, with pretty long function definitions, say, about 400 lines. When the cursor is in middle of a function definition, it would be cool to see the function name in Vim status line.
When we set :set ls=2 in Vim, we can get the file path (relative to the current directory), line number, etc. It would be really cool if we could see the function name too. Any ideas how to get it?
Currently I use [[ to go to start of the function and Ctrl-O to get back to the line I'm editing.
To show current function name in C programs add following in your vimrc:
fun! ShowFuncName()
let lnum = line(".")
let col = col(".")
echohl ModeMsg
echo getline(search("^[^ \t#/]\\{2}.*[^:]\s*$", 'bW'))
echohl None
call search("\\%" . lnum . "l" . "\\%" . col . "c")
endfun
map f :call ShowFuncName() <CR>
Or if you need the "f" key, just map the function to whatever you like.
You can use ctags.vim for this, it will show the current function name in the title or status bar.
SOURCE: https://superuser.com/questions/279651/how-can-i-make-vim-show-the-current-class-and-method-im-editing
Based on #manav m-n's answer
The 'n' flag in search() won't move the cursor, so a shorter version of this with the same functionality would be:
fun! ShowFuncName()
echohl ModeMsg
echo getline(search("^[^ \t#/]\\{2}.*[^:]\s*$", 'bWn'))
echohl None
endfun
map f :call ShowFuncName() <CR>
Reference: run :help search()
Having investigated this and the accepted solution, I believe the simplest solution is:
Install Universal Ctags https://ctags.io/
Install Tagbar https://github.com/preservim/tagbar
call tagbar#currenttag() when setting your statusline, e.g. in .vimrc:
:set statusline=%<%f\ %h%m%r%=%{tagbar#currenttag('%s\ ','','f')}%-.(%l,%c%V%)\ %P
Note that:
Spaces must be slash-escaped in the strings you pass to currenttag()...and it's even different between running the command inside vim and putting it in your .vimrc?? Anyway, spaces can be weird, and they're probably something you want when outputting the function name.
It took me some digging but the default statusline is
:set statusline=%<%f\ %h%m%r%=%-14.(%l,%c%V%)\ %P
There are several plugins for status line or on-demand with a mapping, e.g.:
http://www.vim.org/scripts/script.php?script_id=1094
http://www.vim.org/scripts/script.php?script_id=2805
http://www.vim.org/scripts/script.php?script_id=1553
My solution is as follows:
set stl=%f%h%m%r\ %{Options()}%=%l,%c-%v\ %{line('$')}
fu! PlusOpt(opt)
let option = a:opt
if option
return "+"
else
return "-"
endif
endf
fu! Options()
let opt="ic".PlusOpt(&ic)
let opt=opt." ".&ff
let opt=opt." ".&ft
if &ft==?"cpp" || &ft==?"perl"
let text = " {" . FindCurrentFunction() . "}"
let opt= opt.text
endif
return opt
fu! FindCurrentFunction()
let text =''
let save_cursor = getpos(".")
let opening_brace = searchpair('{','','}','bWr', '', '', 100)
if opening_brace > 0
let oldmagic = &magic
let &magic = 1
let operators='operator\s*\%((\s*)\|\[]\|[+*/%^&|~!=<>-]=\?\|[<>&|+-]\{2}\|>>=\|<<=\|->\*\|,\|->\|(\s*)\)\s*'
let class_func_string = '\(\([[:alpha:]_]\w*\)\s*::\s*\)*\s*\%(\~\2\|'.operators
let class_func_string = class_func_string . '\|[[:alpha:]_]\w*\)\ze\s*('
let searchstring = '\_^\S.\{-}\%('.operators
let searchstring = searchstring.'\|[[:alpha:]_]\w*\)\s*(.*\n\%(\_^\s.*\n\)*\_^{'
let l = search(searchstring, 'bW', line(".")-20 )
if l != 0
let line_text = getline(l)
let matched_text = matchstr(line_text, class_func_string)
let matched_text = substitute(matched_text, '\s', '', 'g')
let text = matched_text
endif
call setpos('.', save_cursor)
let &magic = oldmagic
endif
return text
endfunction
I'm actually attempting to match the C/C++/Java allowed names for functions. This generally works for me (including for overloaded operators) but assumes that the opening { is at column 0 on a line by itself.
I just noticed today that it fails if included in a namespace {}, even if otherwise formatted as expected.
I use https://github.com/mgedmin/chelper.vim for this. It doesn't needs a tags file, instead it parses the source code on the fly.
Based on #solidak solution (which was already based on another one :)
I wanted to always show the function name in the bottom of the terminal.
But I had some problems with very large function which I solved that way:
fun! ShowFuncName()
echohl ModeMsg
echo getline(search("^[^ \t#/]\\{2}.*[^:]\s*$", 'bWn'))[:winwidth('%')-3]
echohl None
endfun
augroup show_funcname
autocmd CursorMoved * :call ShowFuncName()
augroup end
In Intellij Idea, there's a feature. Let's say I have used a variable myCamelCase somewhere in my code. Then if I type mCC and press Ctrl-Enter or some such key combination, it expands to myCamelCase. Is there something similar in Vim?
Okay, forgive me for answering twice, but since my first attempt missed the point, I'll have another go. This is more complicated than I thought, but possibly not as complicated as I have made it (!).
This is now modified to suggest all matching variable names.
First of all, here's a function to generate the 'mCC' abbreviation from the 'myCamelCase' string:
function! Camel_Initials(camel)
let first_char = matchstr(a:camel,"^.")
let other_char = substitute(a:camel,"\\U","","g")
return first_char . other_char
endfunction
Now, here's a function that takes an abbreviation ('mCC') and scans the current buffer (backwards from the current line) for "words" that have this abbreviation. A list of all matches is returned:
function! Expand_Camel_Initials(abbrev)
let winview=winsaveview()
let candidate=a:abbrev
let matches=[]
try
let resline = line(".")
while resline >= 1
let sstr = '\<' . matchstr(a:abbrev,"^.") . '[a-zA-Z]*\>'
keepjumps let resline=search(sstr,"bW")
let candidate=expand("<cword>")
if candidate != a:abbrev && Camel_Initials(candidate) == a:abbrev
call add( matches, candidate )
endif
endwhile
finally
call winrestview(winview)
if len(matches) == 0
echo "No expansion found"
endif
return sort(candidate)
endtry
endfunction
Next, here's a custom-completion function that reads the word under the cursor and suggests the matches returned by the above functions:
function! Camel_Complete( findstart, base )
if a:findstart
let line = getline('.')
let start = col('.') - 1
while start > 0 && line[start - 1] =~ '[A-Za-z_]'
let start -= 1
endwhile
return start
else
return Expand_Camel_Initials( a:base )
endif
endfunction
To make use of this, you must define the "completefunc":
setlocal completefunc=Camel_Complete
To use insert-mode completion, type CTRL-X CTRL-U, but I usually map this to CTRL-L:
inoremap <c-l> <c-x><c-u>
With this code in your vimrc you should find that typing mCC followed by CTRL-L will make the expected replacement. If no matching expansion is found, the abbreviation is unchanged.
The code isn't water-tight, but it works in all the simple cases I tested. Hope it helps. Let me know if anything needs elucidating.
There is a plugin for this in Vim called vim-abolish. Use the map crc to expand to camel case.
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 is to return the number of words in the current buffer. This number is then displayed on the status line. This should work nicely as the statusline is updated at just about every possible opportunity so the count will always remain 'live'.
The problem is that the function I have currently defined is slow and so vim is obviously sluggish when it is used for all but the smallest files; due to this function being executed so frequently.
In summary, does anyone have a clever trick for producing a function that is blazingly fast at calculating the number of words in the current buffer and returning the result?
I really like Michael Dunn's answer above but I found that when I was editing it was causing me to be unable to access the last column. So I have a minor change for the function:
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
I've included it in my status line without any issues:
:set statusline=wc:%{WordCount()}
Here's a usable version of Rodrigo Queiro's idea. It doesn't change the status bar, and it restores the statusmsg variable.
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
This seems to be fast enough to include directly in the status line, e.g.:
:set statusline=wc:%{WordCount()}
Keep a count for the current line and a separate count for the rest of the buffer. As you type (or delete) words on the current line, update only that count, but display the sum of the current line count and the rest of the buffer count.
When you change lines, add the current line count to the buffer count, count the words in the current line and a) set the current line count and b) subtract it from the buffer count.
It would also be wise to recount the buffer periodically (note that you don't have to count the whole buffer at once, since you know where editing is occurring).
This will recalculate the number of words whenever you stop typing for a while (specifically, 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
Enjoy!
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!
Since vim version 7.4.1042
Since vim version 7.4.1042, one can simply alter the statusline as follows:
set statusline+=%{wordcount().words}\ words
set laststatus=2 " enables the statusline.
Word count in vim-airline
Word count is provided standard by vim-airline for a number of file types, being at the time of writing:
asciidoc, help, mail, markdown, org, rst, tex ,text
If word count is not shown in the vim-airline, more often this is due to an unrecognised file type. For example, at least for now, the compound file type markdown.pandoc is not being recognised by vim-airline for word count. This can easily be remedied by amending the .vimrc as follows:
let g:airline#extensions#wordcount#filetypes = '\vasciidoc|help|mail|markdown|markdown.pandoc|org|rst|tex|text'
set laststatus=2 " enables vim-airline.
The \v statement overrides the default g:airline#extensions#wordcount#filetypes variable. The last line ensures vim-airline is enabled.
In case of doubt, the &filetype of an opened file is returned upon issuing the following command:
:echo &filetype
Here is a meta-example:
I used a slightly different approach for this. Rather than make sure the word count function is especially fast, I only call it when the cursor stops moving. These commands will do it:
:au CursorHold * exe "normal g\<c-g>"
:au CursorHoldI * exe "normal g\<c-g>"
Perhaps not quite what the questioner wanted, but much simpler than some of the answers here, and good enough for my use-case (glance down to see word count after typing a sentence or two).
Setting updatetime to a smaller value also helps here:
set updatetime=300
There isn't a huge overhead polling for the word count because CursorHold and CursorHoldI only fire once when the cursor stops moving, not every updatetime ms.
Here is a refinement of Abslom Daak's answer that also works in visual mode.
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
Included in the status line as before. Here is a right-aligned status line:
set statusline=%=%{WordCount()}\ words\
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.
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.
I'm new to Vim scripting, but I might suggest
function WordCount()
redir => l:status
exe "silent normal g\<c-g>"
redir END
return str2nr(split(l:status)[11])
endfunction
as being a bit cleaner since it does not overwrite the existing status line.
My reason for posting is to point out that this function has a puzzling bug: namely, it breaks the append command. Hitting A should drop you into insert mode with the cursor positioned to the right of the final character on the line. However, with this custom status bar enabled it will put you to the left of the final character.
Anyone have any idea what causes this?
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
Since vim now supports this natively:
:echo wordcount().words
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!
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
A variation of Guy Gur-Ari's refinement that
only counts words if spell checking is enabled,
counts the number of selected words in visual mode
keeps mute outside of insert and normal mode, and
hopefully is more agnostic to the system language (when different from english)
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
that can be appended to the statusline by, say,
set statusline+=%.10{StatuslineWordCount()} " wordcount
Building upon https://stackoverflow.com/a/60310471/11001018, my suggestion is:
"new in vim 7.4.1042
let g:word_count=wordcount().words
function WordCount()
if has_key(wordcount(),'visual_words')
let g:word_count=wordcount().visual_words."/".wordcount().words
else
let g:word_count=wordcount().cursor_words."/".wordcount().words
endif
return g:word_count
endfunction
And then:
set statusline+=\ w:%{WordCount()},