I would like vim to display the total document word count in the status bar (where the current line and character number are displayed). I have come across similar questions on SO, and have tried all the suggestions mentioned here and here --- and none of them had any effect whatsoever on my status bar.
To explicitly name a few, I tried to paste any of the following in my ~/.vimrc (and ofc subsequently restarted vim):
function! CountNonEmpty()
let l = 1
let char_count = 0
while l <= line("$")
if len(substitute(getline(l), '\s', '', 'g')) > 3
let char_count += 1
endif
let l += 1
endwhile
return char_count
endfunction
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
" If buffer modified, update any 'Last modified: ' in the first 20 lines.
" 'Last modified: ' can have up to 10 characters before (they are retained).
" Restores cursor and window position using save_cursor variable.
function! LastModified()
if &modified
let save_cursor = getpos(".")
let n = min([15, line("$")])
keepjumps exe '1,' . n . 's#^\(.\{,10}LOC:\).*#\1' .
\ ' ' . CountNonEmpty() . '#e'
keepjumps exe '1,' . n . 's#^\(.\{,10}Word Count:\).*#\1' .
\ ' ' . WordCount() . '#e'
call histdel('search', -1)
call setpos('.', save_cursor)
endif
endfun
OR
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()}
OR
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()}
OR
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
or many many more. And as I said there wa no efect. No error message, no visually perceptible change. I guess there may be a common issue which I am missing, but what is it?
Assuming your status line is enabled (set laststatus=2), the following:
set statusline+=%{wordcount().words}\ words
does exactly what you want in Vim version 7.4.1042 and above:
See :help wordcount().
If you absolutely need backward compatibility, the following is pretty much guaranteed to work in Vim 7.x, and will probably also work in earlier versions:
function! WC()
return len(split(join(getline(1,'$'), ' '), '\s\+'))
endfunction
set statusline+=%{WC()}\ words
Some of the answers from those old threads may be faster or smarter, though.
Your comments about those functions from those old threads not changing anything to your status line make me wonder if the problem is in all those old answers or elsewhere. Maybe… you don't have a status line to begin with?
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:
Related
Newcomer to VimL trying to write a mapping that does the following:
foo
|----> cursor
bar
baz
lr2j should repalce foo with baz.
" replace the current line with a line given by a linewise motion
function! s:LineReplace(type)
if a:type !=# 'line'
return
endif
let saved_register = ##
silent execute "normal! S\<esc>my`]dd'yPjddk^ :delmarks y"
let ## = saved_register
endfunction
nnoremap lr :set operatorfunc=<SID>LineReplace<cr>g#
Instead I get
Error detected while processing function <SNR>108_LineReplace: line 5: E20: Mark not set
I've tried different permutations of execute "normal! ..." command to no avail. Can anyone spot the error?
I should note that when I test out the normal commands everything works fine and the mark 'y exists.
Use :move and :delete to simply things:
" replace the current line with a line given by a linewise motion
function! s:LineReplace(type)
if a:type !=# 'line'
return
endif
']move '[
-delete_
endfunction
nnoremap lr :set operatorfunc=<SID>LineReplace<cr>g#
For more help see:
:h :d
:h :m
:h :range
#xaizek is right; the correct way is to store the mark from the motion:
" replace the current line with a line given by a linewise motion
function! s:LineReplace(type)
if a:type !=# 'line'
return
endif
let lnum = getpos("']")[1]
let saved_register = ##
silent execute "normal S\<esc>my" . lnum . "Gdd'yPjddk^ :delmarks y"
let ## = saved_register
endfunction
nnoremap lr :set operatorfunc=<SID>LineReplace<cr>g#
This is not about hitting ‘p’ when over the file name , but essentially having the same result by simply placing the cursor over it.
You can remap some motions to a function that populates the preview window according to the current word under the cursor.
Here's a basic example that handles text files and directories:
set nocompatible
set autochdir
" Previewed files are present in the current directory
let g:netrw_keepdir = 0
function! PreviewFile(...)
let l:wordUnderCursor = a:1
if !empty(glob(l:wordUnderCursor))
let l:type = system('file -ib ' . shellescape(l:wordUnderCursor))
if l:type =~# '^text/plain'
silent! execute 'pedit' l:wordUnderCursor
elseif l:type =~# '^inode/directory'
let l:name = tempname()
set noautochdir
silent! execute 'pedit! ' . l:name
wincmd P
normal! ggdG
silent! execute 'r !ls ' . l:wordUnderCursor
normal! ggdd
wincmd w
endif
endif
endfunction
autocmd FileType netrw
\ nnoremap j j:call PreviewFile(expand("<cWORD>"))<CR> |
\ nnoremap k k:call PreviewFile(expand("<cWORD>"))<CR>
I noticed that if I open a non-existent HTML file with Vim, the filetype is set automatically to html:
$ vim foobar.html
:set filetype # --> filetype=html
On the other hand, the same does not happen with TeX. If I create a non-existent file the filetype is plaintext, and I have to save the file and reopen it to have it correctly set:
$ vim blabla.tex
:set filetype # --> filetype=plaintext
I also tried with Python, C, VimL, Javascript files and the filetype is always set right away. I don't know why this does not happen with TeX files.
The only thing I can think might interfere is vim-latex. Could that be possible? If so, how do I fix this problem?
If it might be of help, here is my not so long .vimrc file.
Vim uses a giant heuristic to determine plaintex vs other variations of tex.
If you look in $VIMRUNTIME/filetype.vim you will find the following function
" Choose context, plaintex, or tex (LaTeX) based on these rules:
" 1. Check the first line of the file for "%&<format>".
" 2. Check the first 1000 non-comment lines for LaTeX or ConTeXt keywords.
" 3. Default to "latex" or to g:tex_flavor, can be set in user's vimrc.
func! s:FTtex()
let firstline = getline(1)
if firstline =~ '^%&\s*\a\+'
let format = tolower(matchstr(firstline, '\a\+'))
let format = substitute(format, 'pdf', '', '')
if format == 'tex'
let format = 'plain'
endif
else
" Default value, may be changed later:
let format = exists("g:tex_flavor") ? g:tex_flavor : 'plain'
" Save position, go to the top of the file, find first non-comment line.
let save_cursor = getpos('.')
call cursor(1,1)
let firstNC = search('^\s*[^[:space:]%]', 'c', 1000)
if firstNC " Check the next thousand lines for a LaTeX or ConTeXt keyword.
let lpat = 'documentclass\>\|usepackage\>\|begin{\|newcommand\>\|renewcommand\>'
let cpat = 'start\a\+\|setup\a\+\|usemodule\|enablemode\|enableregime\|setvariables\|useencoding\|usesymbols\|stelle\a\+\|verwende\a\+\|stel\a\+\|gebruik\a\+\|usa\a\+\|imposta\a\+\|regle\a\+\|utilisemodule\>'
let kwline = search('^\s*\\\%(' . lpat . '\)\|^\s*\\\(' . cpat . '\)',
\ 'cnp', firstNC + 1000)
if kwline == 1 " lpat matched
let format = 'latex'
elseif kwline == 2 " cpat matched
let format = 'context'
endif " If neither matched, keep default set above.
" let lline = search('^\s*\\\%(' . lpat . '\)', 'cn', firstNC + 1000)
" let cline = search('^\s*\\\%(' . cpat . '\)', 'cn', firstNC + 1000)
" if cline > 0
" let format = 'context'
" endif
" if lline > 0 && (cline == 0 || cline > lline)
" let format = 'tex'
" endif
endif " firstNC
call setpos('.', save_cursor)
endif " firstline =~ '^%&\s*\a\+'
" Translation from formats to file types. TODO: add AMSTeX, RevTex, others?
if format == 'plain'
setf plaintex
elseif format == 'context'
setf context
else " probably LaTeX
setf tex
endif
return
endfunc
This function determines which flavor of tex to use. If you look at it you can see that default value is taken from g:tex_flavor (if it exists, defaults to plain). If you set this variable to tex (in you vimrc), vim will default to the tex filetype.
let g:tex_flavor = 'tex'
There is a way to automate the process you're doing manually.
Adding autocmd BufRead,BufNewFile *.tex set filetype=tex to your .vimrc file will set tex filetype for each tex file, once you open them in vim.
My environment is vim with winmanager, minibufexpl, neerdtree and taglist. Now I have a problem, when I open more than one file(also the minibufexpl has two file names), and there are four windows(neerdtree window, taglist window, minibufexpl windows and opened file window). Then I use :q to quit one file, but the opened file is colsed also. I think the correct behavior is to move to the next file.
You probably want to close the buffer instead. That can also close the window, however, which may or may not be what you want.
This plugin should help with that:
http://vim.wikia.com/wiki/Deleting_a_buffer_without_closing_the_window
I additionally have this mapped in my .vimrc so I can just do a <leader>bd to close the buffer.
Edit in response to comments from OP:
Here is the code for the plugin from the link I pasted in:
" Delete buffer while keeping window layout (don't close buffer's windows).
" Version 2008-11-18 from http://vim.wikia.com/wiki/VimTip165
if v:version < 700 || exists('loaded_bclose') || &cp
finish
endif
let loaded_bclose = 1
if !exists('bclose_multiple')
let bclose_multiple = 1
endif
" Display an error message.
function! s:Warn(msg)
echohl ErrorMsg
echomsg a:msg
echohl NONE
endfunction
" Command ':Bclose' executes ':bd' to delete buffer in current window.
" The window will show the alternate buffer (Ctrl-^) if it exists,
" or the previous buffer (:bp), or a blank buffer if no previous.
" Command ':Bclose!' is the same, but executes ':bd!' (discard changes).
" An optional argument can specify which buffer to close (name or number).
function! s:Bclose(bang, buffer)
if empty(a:buffer)
let btarget = bufnr('%')
elseif a:buffer =~ '^\d\+$'
let btarget = bufnr(str2nr(a:buffer))
else
let btarget = bufnr(a:buffer)
endif
if btarget < 0
call s:Warn('No matching buffer for '.a:buffer)
return
endif
if empty(a:bang) && getbufvar(btarget, '&modified')
call s:Warn('No write since last change for buffer '.btarget.' (use :Bclose!)')
return
endif
" Numbers of windows that view target buffer which we will delete.
let wnums = filter(range(1, winnr('$')), 'winbufnr(v:val) == btarget')
if !g:bclose_multiple && len(wnums) > 1
call s:Warn('Buffer is in multiple windows (use ":let bclose_multiple=1")')
return
endif
let wcurrent = winnr()
for w in wnums
execute w.'wincmd w'
let prevbuf = bufnr('#')
if prevbuf > 0 && buflisted(prevbuf) && prevbuf != w
buffer #
else
bprevious
endif
if btarget == bufnr('%')
" Numbers of listed buffers which are not the target to be deleted.
let blisted = filter(range(1, bufnr('$')), 'buflisted(v:val) && v:val != btarget')
" Listed, not target, and not displayed.
let bhidden = filter(copy(blisted), 'bufwinnr(v:val) < 0')
" Take the first buffer, if any (could be more intelligent).
let bjump = (bhidden + blisted + [-1])[0]
if bjump > 0
execute 'buffer '.bjump
else
execute 'enew'.a:bang
endif
endif
endfor
execute 'bdelete'.a:bang.' '.btarget
execute wcurrent.'wincmd w'
endfunction
command! -bang -complete=buffer -nargs=? Bclose call s:Bclose('<bang>', '<args>')
nnoremap <silent> <Leader>bd :Bclose<CR>
Does anyone know if it's possible to create a new op-pending command?
e.g. I'd like to replace a sequence such as vf(r<space>w with ,cf(. Specifically here, the idea is to "clear" the text from the cursor position up to and including the next opening brace and then put the cursor at the beginning of the next word.
I may just be missing something in the help files (or my Google-fu is off today), so a pointer to the right place would be much appreciated.
You want to use :set opfunc and g#. The documentation is pretty good, :h g#.
nnoremap <silent> ,c :set opfunc=Clearing<cr>g#
vnoremap <silent> ,c :<c-u>set opfunc=Clearing<cr>g#
function! Clearing(type, ...)
let sel_save = &selection
let &selection = "inclusive"
let reg_save = ##
if a:0 " Invoked from Visual mode, use '< and '> marks.
silent exe "normal! `<" . a:type . "`>r "
elseif a:type == 'line'
silent exe "normal! '[V']r "
elseif a:type == 'block'
silent exe "normal! `[\<C-V>`]r "
else
silent exe "normal! `[v`]r "
endif
norm! `]w
let &selection = sel_save
let ## = reg_save
endfunction
I think :h map-operator is what you're looking for.