vim: E14 Invalid Address, reassign variable inside if statement - vim

I'm trying to make my tab spacing dependent on file type. But I get the error E14 Invalid Address on line 3.
function! Tabs()
let t = 4
if (&filetype ==? 'yaml') || (&filetype ==? 'yml')
t = 2
endif
" size of a hard tabstop
let &tabstop=t
" size of an "indent"
let &shiftwidth=t
" a combination of spaces and tabs are used to simulate tab stops at a width
" other than the (hard)tabstop
let &softtabstop=t
endfunction
autocmd! BufReadPost,BufNewFile * call Tabs()
Not sure what I'm doing wrong.

You must always use :let to assign a value to a variable:
let t = 2
Note: although it is not strictly required, it is somewhat customary to put spaces around the operator for :let:
let &foo = 1
And it is mandatory to avoid spaces for :set:
set foo=1

Related

How to count characters while typing?

I often use VIM to write comments in newspapers or blog sites.
Often there is a max number of characters to type.
How do I create a counter (p.e. in the statusbar) to see the characters I have typed (including whitespaces) while typing?
The 'statusline' setting allows evaluation of expressions with the %{...} special item.
So if we can come up with an expression that returns the number of characters (not bytes!) in the current buffer we can incorporate it in our statusline to solve the problem.
This command does it:
:set statusline+=\ %{strwidth(join(getline(1,'$'),'\ '))}
For text with CJK characters strwidth() is not good enough, since it returns a display cell count, not a character count. If double-width characters are part of the requirement, use this improved version instead:
:set statusline+=\ %{strlen(substitute(join(getline(1,'$'),'.'),'.','.','g'))}
But be aware that the expression is evaluated on every single change to the buffer.
See :h 'statusline'.
Sunday afternoon bonus – The character position under the cursor can also be packed into a single expression. Not for the faint of heart:
:set statusline+=\ %{strlen(substitute(join(add(getline(1,line('.')-1),strpart(getline('.'),0,col('.')-1)),'.'),'.','.','g'))+1}
By mixing glts answer and this post and a bit of fiddling with the code, I made the following for my self which you can put it in ~/.vimrc file (you need to have 1 second idol cursor so the function calculates the words and characters and the value can be changed by modifying set updatetime=1000):
let g:word_count = "<unknown>"
let g:char_count = "<unknown>"
function WordCount()
return g:word_count
endfunction
function CharCount()
return g:char_count
endfunction
function UpdateWordCount()
let lnum = 1
let n = 0
while lnum <= line('$')
let n = n + len(split(getline(lnum)))
let lnum = lnum + 1
endwhile
let g:word_count = n
let g:char_count = strlen(substitute(join(getline(1,'$'),'.'),'.','.','g'))
endfunction
" Update the count when cursor is idle in command or insert mode.
" Update when idle for 1000 msec (default is 4000 msec).
set updatetime=1000
augroup WordCounter
au! CursorHold,CursorHoldI * call UpdateWordCount()
augroup END
" Set statusline, shown here a piece at a time
highlight User1 ctermbg=green guibg=green ctermfg=black guifg=black
set statusline=%1* " Switch to User1 color highlight
set statusline+=%<%F " file name, cut if needed at start
set statusline+=%M " modified flag
set statusline+=%y " file type
set statusline+=%= " separator from left to right justified
set statusline+=\ %{WordCount()}\ words,
set statusline+=\ %{CharCount()}\ chars,
set statusline+=\ %l/%L\ lines,\ %P " percentage through the file
It will look like this:

vim - set auto indent to fill the leading space with space or tabstop

It seems if we enable 'ai', vim will fill the the leading space with tabstop.
I can make it fill with just space with 'et'. I don't like a C file mixed with space and tabstop.
My vimrc:
set ts=4 et
set ai
set hlsearch
syntax on
filetype plugin indent on
autocmd FileType make setlocal noexpandtab
However, in some condition I do need to input tabstop when I hit the 'TAB' on keyboard, for example, in makefile and some others.
The 'autocmd FileType' command is not good: I can't add every file type in vimrc.
What I want is simple:
autoindent to fill leading area with
space;
when hit 'TAB' on keyboard, tabstop
input, not space (so no 'et')
How to do it?
inoremap <expr> <tab> ((getline('.')[:col('.')-2]=~'\S')?("\<C-v>\t"):(repeat(' ', &ts-((virtcol('.')-1)%&ts))))
It does the same as #Lynch answer if I read it correctly.
You can also use <C-v><Tab>: this will insert <Tab> without invoking any mappings and ignores expandtab unless you remapped <C-v> or <C-v><Tab> for some reason.
If you want to just insert tab do
inoremap <Tab> <C-v><Tab>
It will ignore expandtab setting.
I did it using a function. I tested it, but maybe in some particular case you will have to fix some bugs. Try adding this to your vimrc:
set et
function! Inserttab()
let insert = ""
let line = getline('.')
let pos = getpos('.')[2]
let before = ""
let after = line
if pos != 1
let before = line[ 0: pos - 1]
let after = line[pos : strlen(line) ]
endif
if pos != 1 && substitute(before, "[ \t]", "", "g") != ""
let insert = "\t"
else
let insert = " "
endif
let line = before . insert . after
call setline('.', line)
call cursor(line('.'), strlen(before . insert))
endfunction
inoremap <tab> <esc>:call Inserttab()<CR>a
Basicaly it does remap your key in visual mode to the function Inserttab(). Also note that if you change ts for something other than 4 it will still output 4 spaces instead of two because the value is hard coded.
Also im not very familiar with vim scripts, but I think all the variables used will be global which is a bad thing.
I forgot to mention that to "see" white spaces you can use set list. You disable this with set nolist. Also in normal mode you can use ga to see information about the character your cursor is on.
Edit
I realise that you may want to insert tab at the beginin of the line. My script insert space at the begining and tab anywhere else.
If you really want a tab every time you hit tab key you could simply use this:
set et
function! Inserttab()
let insert = ""
let line = getline('.')
let pos = getpos('.')[2]
let before = ""
let after = line
if pos != 1
let before = line[ 0: pos - 1]
let after = line[pos : strlen(line) ]
endif
let insert = "\t"
let line = before . insert . after
call setline('.', line)
call cursor(line('.'), strlen(before . insert))
endfunction
inoremap <tab> <esc>:call Inserttab()<CR>a
But I dont see the point, with this version you will never be able to indent manually from insert mode.
One way to do it is
:set sw=4 (or whatever you want)
:set ts=46 (or some large number)
Then autoindent will not insert tabs unless you reach 46 spaces, in which case you can put in a higher number.
Only drag about this is if someone else is using tabs, then you have to reset ts to agree with the file you are editing. On the other hand, it will make the tabs immediately obvious, which can be desirable as well.

Two fold methods at the same time

I'd like to set two methods for folding
:set foldmethod=indent and retain all its features
hiding comments with
:set foldmethod=marker
:set foldmarker=/*,*/
I found out that this is not possible. Is it possible to achieve the desired folding and set this in .vimrc or to use some script or plugin for this?
It's not possible to have different foldmethod types in same buffer. How is Vim to know that there are some comments at same indent level as other text that you want to treat as being of a different (higher numbered) level?
I'm sure you can achieve what you want by setting foldmethod to 'expr'. This is most flexible way of doing folds in Vim but can get complicated (and/or slow) depending on what you want. I think it would work fairly easily for your use case, though.
First, somewhere in your vimrc or vimscripts you need to make sure that foldexpr is getting defined for the filetype in question.
set foldexpr=MyFoldLevel(v:lnum)
set foldmethod=expr
" and for last code example
let b:previous_level = 0
and you then have to flesh out your foldexpr function so that it assigns levels in a way that results in behavior you want. Something like the code below might come close to working in cases where each comment line has prefix symbol (i.e., not in your case), but I expect it needs some tweaks. h: fold-expr would be a good place to look for help:
function! MyFoldLevel(linenum)
" assign levels based on spaces indented and tabstop of 4
let level = indent(a:linenum) / 4
if getline(a:linenum) =~ [put line-based comment prefix pattern here]
let level = 20
endif
endfunction
Would need to be modified to assign higher level for lines between comment start and end markers the way you want:
function! MyFoldLevel(linenum)
let linetext = getline(a:linenum)
if linetext =~ [put line-based comment prefix pattern here]
let level = 20
elseif linetext =~ '^\s*/\*'
let level = 20
elseif linetext =~ '^\s*\*/'
let level = 21
else
if b:previous_level == 20
let level = 20
else
"assuming code is space-indented with tabstop of 4
let level = indent(a:linenum) / 4
endif
endif
let b:previous_level = level
return level
endfunction
I don't expect the foldmethod functions I've written would work exactly as written. But they do point the way to something that would work.
Note that use of level of '20' for comments is just arbitrary level that allows them to be folded while all (presumably lower-leveled) indented code could be visible. '21' for last line of comment section is just to differentiate it from the previous lines of comments that have level of 20, to know that next line should be treated as regular line of code.
Also, key ops like 'zc' and 'zo' will not work quite right on comments when they're set to level much higher than surrounding code. Would want to use direct command like :set foldlevel=21 to show all comment lines.
Not pretty, and I expect it could be simplified a little, but something like this is what I think is required for what you want.
Actually, thinking through this a little more, I think you would want the first line of any comment blocks to be at same level as if it were a non-comment line, only subsequent comment lines in same block would need to be of higher level to have them "fold" into the starting comment line. In the code I gave, if it works or comes close to working at all, I think vim would fold all the comment lines behind the preceding non-comment line, which isn't what you want, but I unfortunately don't have more time to devote to this little puzzle. . . I've done this sort of custom-folding quite a few times and generally always have a little bit of trial and error in getting exactly what I want.
I have the same requests as yours, here is my not perfect solution
my maker pair is #<=== and #===> (or #region and #endregion as in pycharm)
let b:inBlock=0
let b:lastLineNum=0
let b:lastLevel=0
let b:lastGoodLine=0
let b:lastGoodBlock=0
let b:startFoldingMark='^\s*.\?#<==*\|^\s*.\?#region'
let b:endFoldingMark='^\s*.\?#=*=>\|^\s*.\?#endregion'
function! MyFold(linenum)
let linetext = getline(a:linenum)
let level = indent(a:linenum) / &shiftwidth
"the first line have 0 fold level
if (a:linenum == 1)
if linetext =~ b:startFoldingMark
let b:inBlock = 1
let b:lastLineNum=a:linenum
let b:lastGoodLine=0
let b:lastGoodBlock=0
let b:lastLevel=level
return level
endif
let b:inBlock=0
let b:lastInBlock=0
let b:lastLineNum=a:linenum
let b:lastGoodLine=0
let b:lastGoodBlock=b:inBlock
let b:lastLevel=level + b:inBlock
return level + b:inBlock
endif
" not calculate from the mid of text
if ((b:lastLineNum+1) != a:linenum)
let level = indent(a:linenum) / &shiftwidth
let lastGoodNum = a:linenum-1
while (lastGoodNum>1 && getline(lastGoodNum) =~? '\v^\s*$' )
let lastGoodNum -= 1
endwhile
if (foldlevel(lastGoodNum)==-1)
let b:inBlock=b:lastGoodBlock
else
let lastlevel = indent(lastGoodNum) / &shiftwidth
let lastlinetext = getline(lastGoodNum)
let lastlinelevel = foldlevel(lastGoodNum)
if lastlinetext =~ b:startFoldingMark
let b:inBlock = lastlinelevel - lastlevel + 1
elseif lastlinetext =~ b:endFoldingMark
let b:inBlock = lastlinelevel - lastlevel - 1
else
let b:inBlock = lastlinelevel - lastlevel
endif
endif
endif
"blank lines have undefined fold level
if getline(a:linenum) =~? '\v^\s*$'
let b:lastLineNum=a:linenum
let b:lastLevel=-1
return -1
endif
"if next line is a start of new marker block, inBlock ++
if linetext =~ b:startFoldingMark
let b:lastLineNum=a:linenum
if (b:lastLevel != -1)
let b:lastGoodLine=a:linenum
let b:lastGoodBlock=b:inBlock
endif
let b:lastLevel=level + b:inBlock - 1
return level + b:inBlock - 1
"if next line is an end of new marker block, inBlock -
elseif linetext =~ b:endFoldingMark
let b:inBlock = b:inBlock - 1
let b:lastLineNum=a:linenum
let b:lastGoodLine=a:linenum
let b:lastGoodBlock=b:inBlock
let b:lastLevel=level + b:inBlock + 1
return level + b:inBlock + 1
endif
let b:lastLineNum=a:linenum
if (b:lastLevel != -1)
let b:lastGoodLine=a:linenum
let b:lastGoodBlock=b:inBlock
endif
let b:lastLevel=level + b:inBlock
return level+b:inBlock
endfunction
now, i can keep all the features when using indent fold method,
and i can fold each #<=, #=> marker block,
also, the lines' indent folding relations are still kept in each block.
In this function, i avoid using "a1", "s1" and "=" level, which will cause iteration for this function and may be slow for large files.
However, when you update lines, the calculation of fold level may be incorrect (because vim may not update all fold level from beginning, and thus have incorrect inBlock value)
you can use zx to update fold levels manually.
see more at https://github.com/Fmajor/configs
Syntax-based folding may be a better way to get what you want than the expr-based method I suggested in different answer to your question. Check :h fold-syn for more info. I think there may be some good solutions already out there for c-based folding. Don't know how good it is, but here is a c-syntax file with support for syntax-based folding:
http://www.vim.org/scripts/script.php?script_id=234
and another:
http://www.vim.org/scripts/script.php?script_id=925
The solutions above are entirely syntax-based, don't involve using indents to determine fold levels. But you could modify syntax-based folding to do the main folding by indented regions if you wanted. If you indent based on syntactical elements the result might be the same anyway.
Here's a tip that shows how to just fold c-style comments (not the actual code)
http://vim.wikia.com/wiki/Fold_C-style_comments

How do I define indents in vim based on curly braces?

I use https://github.com/cakebaker/scss-syntax.vim for syntax highlighting SCSS (or SASS) files on vim, which works very well for syntax highlighting. However, the plugin does not come with an indent file and am having trouble writing one.
I would like to set the indent to look like this:
However, if i do gg=G, I get:
I suspect that it does not understand nested indent based on braces. I tried all the different combinations of
set cindent
set nocindent
set autoindent
set smartindent
and tried to use the code from Tab key == 4 spaces and auto-indent after curly braces in Vim , including
set tabstop=2
set shiftwidth=2
set expandtab
...but nested braces indent never seems to work.
I believe that I might want to write a custom indent file, and all I need is indentation based on braces with nested levels. How should I go about this? If someone has an indentation file for filetypes with similar syntax, that will be great as well.
This is a quick hack, based on the built-in perl indentation code (in indent/perl.vim). Hopefully you can use it to get what you want to do. See the more detailed comments in either the perl indentation code or another one of the files in the indent directory for more details.
setlocal indentexpr=GetMyIndent()
function! GetMyIndent()
let cline = getline(v:lnum)
" Find a non-blank line above the current line.
let lnum = prevnonblank(v:lnum - 1)
" Hit the start of the file, use zero indent.
if lnum == 0
return 0
endif
let line = getline(lnum)
let ind = indent(lnum)
" Indent blocks enclosed by {}, (), or []
" Find a real opening brace
let bracepos = match(line, '[(){}\[\]]', matchend(line, '^\s*[)}\]]'))
while bracepos != -1
let brace = strpart(line, bracepos, 1)
if brace == '(' || brace == '{' || brace == '['
let ind = ind + &sw
else
let ind = ind - &sw
endif
let bracepos = match(line, '[(){}\[\]]', bracepos + 1)
endwhile
let bracepos = matchend(cline, '^\s*[)}\]]')
if bracepos != -1
let ind = ind - &sw
endif
return ind
endfunction
Save that file as ~/.vim/indent/something.vim where something is your file type (replace ~/.vim with the path to vimfiles if you're on Windows.
You might also want to stick this at the start of the file (but only if there isn't some other indent declaration that might be loaded first):
" Only load this indent file when no other was loaded.
if exists("b:did_indent")
finish
endif
let b:did_indent = 1

Fast word count function in Vim

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

Resources